当更新 ObservableCollection 中的项目时更新 ItemsControl
Update ItemsControl when an item in an ObservableCollection is updated
问题:
- 您在
查看。
- 您将
ItemsControl.ItemsSource
属性 绑定到 ViewModel 中的 ObservableCollection
。
- 将项目添加到
ObservableCollection
或从中删除项目时,您的视图会按预期更新。
- 但是,当您更改
ObservableCollection
中的项目的 属性 时,视图不会更新。
背景:
看来这是很多WPF开发者遇到的通病。已经问过几次了:
Notify ObservableCollection when Item changes
ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)
ObservableCollection and Item PropertyChanged
我的实现:
我尝试在 Notify ObservableCollection when Item changes 中实施公认的解决方案。基本思想是在 MainWindowViewModel 中为 ObservableCollection
中的每个项目连接一个 PropertyChanged
处理程序。当项目的 属性 发生变化时,将调用事件处理程序并以某种方式更新视图。
我无法让实施工作。这是我的实现。
视图模型:
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
项目视图模型:
class EmployeeViewModel : ViewModelBase
{
private int _age;
private string _name;
public int Age
{
get { return _age; }
set
{
_age = value;
RaisePropertyChanged("Age");
}
}
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
public override string ToString()
{
return string.Format("{0} is {1} years old", Name, Age);
}
}
主要 Window 视图模型:
class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<EmployeeViewModel> _collection;
public MainWindowViewModel()
{
_collection = new ObservableCollection<EmployeeViewModel>();
_collection.CollectionChanged += MyItemsSource_CollectionChanged;
AddEmployeeCommand = new DelegateCommand(() => AddEmployee());
IncrementEmployeeAgeCommand = new DelegateCommand(() => IncrementEmployeeAge());
}
public ObservableCollection<EmployeeViewModel> Employees
{
get { return _collection; }
}
public ICommand AddEmployeeCommand { get; set; }
public ICommand IncrementEmployeeAgeCommand { get; set; }
public void AddEmployee()
{
_collection.Add(new EmployeeViewModel()
{
Age = 1,
Name = "Random Joe",
});
}
public void IncrementEmployeeAge()
{
foreach (var item in _collection)
{
item.Age++;
}
}
private void MyItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (EmployeeViewModel item in e.NewItems)
item.PropertyChanged += ItemPropertyChanged;
if (e.OldItems != null)
foreach (EmployeeViewModel item in e.OldItems)
item.PropertyChanged -= ItemPropertyChanged;
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged("Employees");
}
}
查看:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
xmlns:d="clr-namespace:Iress.IosPlus.DynamicOE.Controls"
Title="MainWindow" Height="350" Width="350">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
<ColumnDefinition Width="0.7*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Button Command="{Binding AddEmployeeCommand}">Add Employee</Button>
<Button Command="{Binding IncrementEmployeeAgeCommand}">Increment Employee Age</Button>
</StackPanel>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="0.1*"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Path=Employees[0]}"></TextBlock>
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Employees}" BorderBrush="Red" BorderThickness="1"></ItemsControl>
</Grid>
</Grid>
我的结果:
为了验证我的实现,我创建了一个像这样的视图。 TextBlock.Text
绑定到集合中的第一项。 ItemsControl
绑定到集合本身。
- 按下 "Add Employee" 按钮会在集合中添加一个
EmployeeViewModel
对象,并且 TextBlock
和 ItemsControl
都会按预期更新。
- 再次按 "Add Employee",
ItemsControl
将更新为另一个条目。太棒了!
- 按下 "Increment Employee Age" 按钮。每个项目的
Age
属性 递增 1。引发 PropertyChanged
事件。 ItemPropertyChanged
事件处理程序被调用。 Textblock
按预期更新。但是,ItemsControl
没有更新。
我的印象是,根据 Notify ObservableCollection when Item changes 中的答案更改 Employee.Age
时,ItemsControl
也应该更新。
我找到了使用 Snoop 调试 XAML 的答案。
问题是您正在尝试绑定到 ToString() 方法并且不会引发 PropertyChanged 事件。如果您查看 XAML 绑定,您会注意到 ObservableCollection 实际上正在发生变化。
现在查看每个项目控件及其在 "Text" 属性 中的文本绑定。有none,只是文字
要解决此问题,只需添加一个 ItemsControl ItemTemplate 和一个包含您想要显示的元素的 DataTemplate。
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Employees, UpdateSourceTrigger=PropertyChanged}" BorderBrush="Red" BorderThickness="1" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat=" {0} is {1} years old">
<Binding Path="Name"/>
<Binding Path="Age"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
我们现在对绑定开了绿灯。正在调用 RaisePropertyChanged。
哒哒!
问题:
- 您在 查看。
- 您将
ItemsControl.ItemsSource
属性 绑定到 ViewModel 中的ObservableCollection
。 - 将项目添加到
ObservableCollection
或从中删除项目时,您的视图会按预期更新。 - 但是,当您更改
ObservableCollection
中的项目的 属性 时,视图不会更新。
背景:
看来这是很多WPF开发者遇到的通病。已经问过几次了:
Notify ObservableCollection when Item changes
ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)
ObservableCollection and Item PropertyChanged
我的实现:
我尝试在 Notify ObservableCollection when Item changes 中实施公认的解决方案。基本思想是在 MainWindowViewModel 中为 ObservableCollection
中的每个项目连接一个 PropertyChanged
处理程序。当项目的 属性 发生变化时,将调用事件处理程序并以某种方式更新视图。
我无法让实施工作。这是我的实现。
视图模型:
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
项目视图模型:
class EmployeeViewModel : ViewModelBase
{
private int _age;
private string _name;
public int Age
{
get { return _age; }
set
{
_age = value;
RaisePropertyChanged("Age");
}
}
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
public override string ToString()
{
return string.Format("{0} is {1} years old", Name, Age);
}
}
主要 Window 视图模型:
class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<EmployeeViewModel> _collection;
public MainWindowViewModel()
{
_collection = new ObservableCollection<EmployeeViewModel>();
_collection.CollectionChanged += MyItemsSource_CollectionChanged;
AddEmployeeCommand = new DelegateCommand(() => AddEmployee());
IncrementEmployeeAgeCommand = new DelegateCommand(() => IncrementEmployeeAge());
}
public ObservableCollection<EmployeeViewModel> Employees
{
get { return _collection; }
}
public ICommand AddEmployeeCommand { get; set; }
public ICommand IncrementEmployeeAgeCommand { get; set; }
public void AddEmployee()
{
_collection.Add(new EmployeeViewModel()
{
Age = 1,
Name = "Random Joe",
});
}
public void IncrementEmployeeAge()
{
foreach (var item in _collection)
{
item.Age++;
}
}
private void MyItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (EmployeeViewModel item in e.NewItems)
item.PropertyChanged += ItemPropertyChanged;
if (e.OldItems != null)
foreach (EmployeeViewModel item in e.OldItems)
item.PropertyChanged -= ItemPropertyChanged;
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged("Employees");
}
}
查看:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
xmlns:d="clr-namespace:Iress.IosPlus.DynamicOE.Controls"
Title="MainWindow" Height="350" Width="350">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
<ColumnDefinition Width="0.7*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Button Command="{Binding AddEmployeeCommand}">Add Employee</Button>
<Button Command="{Binding IncrementEmployeeAgeCommand}">Increment Employee Age</Button>
</StackPanel>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="0.1*"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Path=Employees[0]}"></TextBlock>
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Employees}" BorderBrush="Red" BorderThickness="1"></ItemsControl>
</Grid>
</Grid>
我的结果:
为了验证我的实现,我创建了一个像这样的视图。 TextBlock.Text
绑定到集合中的第一项。 ItemsControl
绑定到集合本身。
- 按下 "Add Employee" 按钮会在集合中添加一个
EmployeeViewModel
对象,并且TextBlock
和ItemsControl
都会按预期更新。 - 再次按 "Add Employee",
ItemsControl
将更新为另一个条目。太棒了! - 按下 "Increment Employee Age" 按钮。每个项目的
Age
属性 递增 1。引发PropertyChanged
事件。ItemPropertyChanged
事件处理程序被调用。Textblock
按预期更新。但是,ItemsControl
没有更新。
我的印象是,根据 Notify ObservableCollection when Item changes 中的答案更改 Employee.Age
时,ItemsControl
也应该更新。
我找到了使用 Snoop 调试 XAML 的答案。
问题是您正在尝试绑定到 ToString() 方法并且不会引发 PropertyChanged 事件。如果您查看 XAML 绑定,您会注意到 ObservableCollection 实际上正在发生变化。
现在查看每个项目控件及其在 "Text" 属性 中的文本绑定。有none,只是文字
要解决此问题,只需添加一个 ItemsControl ItemTemplate 和一个包含您想要显示的元素的 DataTemplate。
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Employees, UpdateSourceTrigger=PropertyChanged}" BorderBrush="Red" BorderThickness="1" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat=" {0} is {1} years old">
<Binding Path="Name"/>
<Binding Path="Age"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
我们现在对绑定开了绿灯。正在调用 RaisePropertyChanged。
哒哒!