如何使用 ObservableCollection<string> 作为自定义依赖属性

问题描述 投票:0回答:1

我正在尝试创建 ObservableCollection 类型的自定义依赖属性。我希望能够从 XAML 后面的代码向其中添加项目。但我可以看到,当我从 ViewModel 向其中添加项目时,绑定会更新,但当我从后面的代码向其中添加项目时,绑定不会更新。这是不可能的还是我错过了一些关键的东西。预先感谢

XAML


<Window x:Class="NodeEditor.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:views="clr-namespace:NodeEditor.Views"
        xmlns:viewModels="clr-namespace:NodeEditor.ViewModel"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance viewModels:ItemsList}"
        MouseDown="AddItems"
        ItemsList="{Binding ItemsList}"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
            <ItemsControl ItemsSource="{Binding Items}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Vertical">
                            <TextBlock Text="{Binding Name}"></TextBlock>
                            <Rectangle Fill="Red" Width="100" Height="100"></Rectangle>
                        </StackPanel>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas Height="500" Width="500"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>

    </Grid>
</Window>

隐藏代码

public partial class MainWindow : Window
{
    public static readonly DependencyProperty ItemListProperty =  DependencyProperty.Register(
                                                                      name: "ItemList",
                                                                      propertyType: typeof(ObservableCollection<string>),
                                                                      ownerType: typeof(MainWindow));

    public MainWindow()
    {
        InitializeComponent();
        this.Loaded += MainWindow_OnLoaded;
        this.SetValue(ItemListProperty, new ObservableCollection<string>());
    }

    private void AddItems(object sender, MouseButtonEventArgs args)
    {
        this.ItemList.Add("newItem");
    }

    private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
    {
        this.DataContext = new ItemsList();
        this.SetValue(ItemListProperty, new ObservableCollection<NodeClass>());
    }

    public ObservableCollection<string> ItemList
    {
        get => (ObservableCollection<string>)this.GetValue(ItemListProperty);
        set
        {
            this.SetValue(ItemListProperty, value);
        }
    }
}

视图模型

public class ItemsList : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    private ObservableCollection<string> _itemList;

    public ItemsList()
    {
        this._itemList = new ObservableCollection<string>();
        this.ItemList = new ObservableCollection<string> { 
            "Item1","Item2"
        };
    }

    public ObservableCollection<string> ItemList
    {
        get => this._itemList;
        set
        {
            this._itemList = value;
            this.RaisePropertyChanged(nameof(ItemList));
        }
    }

    private void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
c# wpf xaml
1个回答
0
投票

您的代码无法编译。发布实际编译的代码会很有帮助。否则,很难区分错别字和你实际犯的错误。

例如,

  1. ItemsList
    类型上没有定义
    Window
    属性

    <!-- COMPILER ERROR! -->
    <Window ItemsList="{Binding ItemsList}"> </Window>
    
  2. 您的完整代码没有定义 Item_s_List,而是定义 ItemList(没有复数 s)。只有一个类型 Item_s_List 位没有这样的属性。

  3. ItemsControl.ItemsSource
    属性绑定到一个也不存在的
    Items
    属性!

    <!-- XAML DESIGNER ERROR! -->
    <ItemsControl ItemsSource="{Binding Items}">
    
  4. 您无法将

    NodeClass
    类型的泛型集合分配给
    string
    类型的泛型集合:

     private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
     {
       // COMPILER ERROR!
       this.SetValue(ItemListProperty, new ObservableCollection<NodeClass>());
     }
    

现在,在讨论了您的编程错误之后,我们可以专注于设计错误。

“基本上创建一个自定义依赖属性,它具有两种方式绑定,如文本框中的文本,但对于集合类型属性”

您通常没有

TwoWay
集合绑定,因为绑定目标例如
ItemsControl
不会更改属性值,这意味着它会替换集合实例。
相反,这个集合中的值充其量只是发生了变化。

主要问题:您的

MainWindow
和视图模型类显然适用于不同的集合实例。这就是为什么从代码隐藏更新集合不会反映在视图模型中。实例必须相同。并且有不止一种解决方案可以实现这一点。

事实上,您正在使用三个不同的集合实例:

  1. 依赖属性有自己的实例,从构造函数设置:

    this.SetValue(ItemListProperty, new ObservableCollection<string>());
    

    请注意,由于 CLR 属性包装器,您可以编写

    this.ItemList = new ObservableCollection<string>();
    
  2. DataContext 返回第二个集合

     private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
     {
       // Property is initialized with another/different instance
       this.DataContext = new ItemsList();
     }
    
  3. 您的视图模型类

    ItemsList
    提供了第三个实例

     public ItemsList()
     {
       // This line is redundant as it is set from the property setter
       //this._itemList = new ObservableCollection<string>();
    
       // The third instance
       this.ItemList = new ObservableCollection<string> { 
         "Item1","Item2"
       };
     }
    

如果您希望所有集合的更改反映在不同的范围内,则所有集合必须是同一实例。 例如,您可以(并且应该)使用视图模型类的实例

ItemsList

// Constructor of MainWindow
public MainWindow()
{
  InitializeComponent();

  var viewModel = new ItemsList();
  this.DataContext = viewModel;

  // make this.ItemList return the same instance 
  // that is used in the ItemsList view model
  this.ItemList = viewModel.ItemList;

  // Now modifying this.ItemList will also modify ItemsList.ItemList
  this.ItemList.Add("newItem");

  viewModel.ItemList.Contains("newItem") // TRUE

  // However, the this.ItemList dependency property is redundant
  // because controls  should bind to the view model (this.DataContext)
  // and modifications can be made by referencing this view model instance.
  // Optionally, this view model instance can be stored 
  // in a private field to avoid the following casting:
  ((ItemsList)this.DataContext).ItemList.Add("newItem");
}
<!-- Bind to the DataContext (the ItemsList view model class) -->
<ItemsControl ItemsSource="{Binding ItemList}"> </ItemsControl>

这些更改旨在突出您的错误,而不是最终解决方案。

但是,他们并没有强调 MVVM 方面的错误。
在 MVVM 应用程序中,根据所使用的术语(如“视图模型”),我假设您正在实现 MVVM,视图不允许操作数据。视图仅呈现数据。


在 MVVM 中,数据操作发生在视图模型中,通过数据绑定显式或隐式进行。

正确的 MVVM 解决方案可能如下所示:

ItemsList.cs
视图模型负责修改数据源集合。
为简单起见,此示例不使用

ICommand
来触发更新操作。不需要命令,但可以简化实现。

class ItemsList : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler? PropertyChanged;
  
  private ObservableCollection<string> itemList;
  public ObservableCollection<string> ItemList
  {
    get => this.itemList;
    set
    {
      this.itemList = value;
      RaisePropertyChanged(nameof(this.ItemList));
    }
  }

  public ItemsList()
  {
    this.ItemList = new ObservableCollection<string> 
    {
      "Item1",
      "Item2"
    };
  }

  public void AddNewItem()
    => this.ItemList.Add("newItem");

  private void RaisePropertyChanged(string propertyName)
    => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

MainWindow.xaml.cs

partial class MainWindow : Window
{
  private ItemsList mainViewModel;

  public MainWindow()
  {
    InitializeComponent();

    this.mainViewModel = new ItemsList();
    this.DataContext = this.mainViewModel;
  }

  private void OnAddNewItemButtonClicked(object sender, RoutedEventArgs e)
    => this.this.mainViewModel.AddNewItem();
}

MainWindow.xaml

<Window>
  <StackPanel>
    <Button Content="New item"
            Click="OnAddNewItemButtonClicked" />

    <!-- Bind to the DataContext (the view model class) -->
    <ItemsControl ItemsSource=²{Binding ItemList}" />
  </StackPanel>
</Window>
© www.soinside.com 2019 - 2024. All rights reserved.