我几个小时以来一直在努力解决一个非常棘手的问题:
当 ObservableCollection DataGrid 绑定到 ObservableCollection 正确更新 > DataGrid 是否会发生变化?
到目前为止,当我点击相应的单元格时,DataGrid只会刷新。
我准备了一个完整的源代码示例来说明以下(非常简单)的情况:
有一个包含List的ViewModel。这个List是一个ObservableCollection,它包含两个东西:一个整数和另一个包含四个整数的List(同样是一个ObservableCollection)。 然后有一个DataGrid有两列。一列用于整数,一列用于整数列表。 这个小应用程序有按钮来修改嵌套列表中的整数,即向四个整数中的一个添加+1。 GOAL 是嵌套列表的修改由DataGrid反映出来。 到目前为止,问题是,这只发生在带有外部触发器的情况下(例如,单击相应的单元格,或单击对列进行排序的一个列标题等)。
所以这是完整的代码:
这是DataGrid绑定的ViewModel代码:
public class ViewModel: INotifyPropertyChanged {
private Items items;
public Items Items {
get { return items; }
set {
items = value;
firePropertyChanged("Items");
}
}
public ViewModel() {
Items = new Items();
}
private void firePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Items: ObservableCollection<Item> {
public Items()
: base() {
this.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
}
private void OnCollectionChanged(object o, NotifyCollectionChangedEventArgs e) {
if (e.NewItems != null) {
foreach (Object item in e.NewItems) {
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null) {
foreach (Object item in e.OldItems) {
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e) {
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
}
}
public class List: ObservableCollection<NumberItem> {
public List()
: base() {
this.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
}
private void OnCollectionChanged(object o, NotifyCollectionChangedEventArgs e) {
if (e.NewItems != null) {
foreach (Object item in e.NewItems) {
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null) {
foreach (Object item in e.OldItems) {
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e) {
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
}
}
public class NumberItem : INotifyPropertyChanged {
private int number;
public int Number {
get { return number; }
set {
number = value;
firePropertyChanged("Number");
}
}
public NumberItem(int i) {
Number = i;
}
private void firePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Item : INotifyPropertyChanged {
private List list;
public List List {
get { return list; }
set {
list = value;
firePropertyChanged("List");
}
}
private int numberOne;
public int NumberOne {
get { return numberOne; }
set {
numberOne = value;
firePropertyChanged("NumberOne");
}
}
private void firePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
/// <summary>
/// This converter simply transforms the list of integers into a string.
/// </summary>
public class Converter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
List l = (List)value;
string s = "";
return s + l[0].Number + " " + l[1].Number + " " + l[2].Number + " " + l[3].Number;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return null;
}
}
操纵嵌套列表整数的按钮代码如下:
private void plus1L(object sender, RoutedEventArgs e) {
vm.Items[0].List[0].Number += 1;
}
最后这是DataGrid绑定的XAML:
<sdk:DataGrid x:Name="dg" Margin="17,139,21,0" ItemsSource="{Binding Items}" AutoGenerateColumns="False" VerticalAlignment="Top" Height="164" d:LayoutOverrides="Width, HorizontalMargin">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn x:Name="A" Header="A" Binding="{Binding NumberOne}"/>
<sdk:DataGridTextColumn x:Name="List" Header="List" Binding="{Binding List, Converter={StaticResource Converter}}"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>*emphasized text*
答案 0 :(得分:3)
I already told you当您更改任何时,您只需触发List
属性的更改事件,这并不难......
修改:在处理程序中,您以某种方式更改列表 ,但您什么都不做。
private void plus1L(object sender, RoutedEventArgs e)
{
vm.Items[0].List[0].Number += 1;
vm.Items[0].OnPropertyChanged("List"); // This is needed if you bind to List.
}
更明确地说明:绑定不关心绑定到的属性路径以外的任何内容。在绑定属性中发生的所有内容都是未知的,因此您需要转发内部更改。
答案 1 :(得分:1)
为什么人们坚持创建继承自ObservableCollection<SomeObject>
的类?他们认为使用ObservableCollection<Item>
作为数据类型并使用内置更改通知会出现问题???
无论如何,这样做:
public class SomeViewModel : INotifyPropertyChanged
{
public ObservableCollection<MyItem> OuterCollection { get; set; }
}
public class MyItem : INotifyPropertyChanged
{
public int SomeInt { get; set; }
public ObservableCollection<int> InnerCollection { get; set; }
}
您的XAML看起来很正常,但是如果更改InnerCollection
中的值,则WPF不知道它,因为ObservableCollection
应该监视集合的更改,而不是更改到集合中的项目。
要更新用户界面,您需要针对PropertyChange
提出InnerCollection
通知。
myItem.InnerCollection[0]++;
myItem.RaisePropertyChanged("InnerCollection");
如果InnerCollection
包含实施INotifyPropertyChanged
的对象,您可以订阅他们的PropertyChanged
个事件,以便在PropertyChanged
之前提升InnerCollection
个事件物品变化。
void SomeConstructor()
{
InnerCollection = new ObservableCollection<SomeItem>();
InnerCollection.CollectionChanged += InnerCollection_CollectionChanged;
}
void InnerCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
if (e.NewItems != null)
for each (SomeItem item in e.NewItems)
item.PropertyChanged += SomeItem_PropertyChanged;
if (e.OldItems!= null)
for each (SomeItem item in e.OldItems)
item.PropertyChanged -= SomeItem_PropertyChanged;
}
void SomeItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged("InnerCollection");
}