我正在尝试创建 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));
}
}
}
您的代码无法编译。发布实际编译的代码会很有帮助。否则,很难区分错别字和你实际犯的错误。
例如,
ItemsList
类型上没有定义Window
属性
<!-- COMPILER ERROR! -->
<Window ItemsList="{Binding ItemsList}"> </Window>
您的完整代码没有定义 Item_s_List,而是定义 ItemList(没有复数 s)。只有一个类型 Item_s_List 位没有这样的属性。
ItemsControl.ItemsSource
属性绑定到一个也不存在的Items
属性!
<!-- XAML DESIGNER ERROR! -->
<ItemsControl ItemsSource="{Binding Items}">
您无法将
NodeClass
类型的泛型集合分配给 string
类型的泛型集合:
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
// COMPILER ERROR!
this.SetValue(ItemListProperty, new ObservableCollection<NodeClass>());
}
现在,在讨论了您的编程错误之后,我们可以专注于设计错误。
“基本上创建一个自定义依赖属性,它具有两种方式绑定,如文本框中的文本,但对于集合类型属性”
您通常没有
TwoWay
集合绑定,因为绑定目标例如ItemsControl
不会更改属性值,这意味着它会替换集合实例。主要问题:您的
MainWindow
和视图模型类显然适用于不同的集合实例。这就是为什么从代码隐藏更新集合不会反映在视图模型中。实例必须相同。并且有不止一种解决方案可以实现这一点。
事实上,您正在使用三个不同的集合实例:
依赖属性有自己的实例,从构造函数设置:
this.SetValue(ItemListProperty, new ObservableCollection<string>());
请注意,由于 CLR 属性包装器,您可以编写
this.ItemList = new ObservableCollection<string>();
DataContext 返回第二个集合
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
// Property is initialized with another/different instance
this.DataContext = new ItemsList();
}
您的视图模型类
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>