Vấn đề:Cập nhật ItemsControl khi một mục trong một ObservableCollection được cập nhật
- Bạn khai báo một
ItemsControl
(hoặc một điều khiển có nguồn gốc từItemsControl
) trong giao diện . - Bạn liên kết thuộc tính
ItemsControl.ItemsSource
với sốObservableCollection
trong Chế độ xem của bạn. - Chế độ xem của bạn sẽ cập nhật như mong đợi khi một mục được thêm vào/xóa khỏi
ObservableCollection
. - NHƯNG, chế độ xem không cập nhật khi bạn thay đổi thuộc tính của một mục trong
ObservableCollection
.
Bối cảnh:
Có vẻ như rằng đây là một vấn đề thường gặp nhiều nhà phát triển WPF đã gặp phải. Nó đã được hỏi một vài lần:
Notify ObservableCollection when Item changes
ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)
ObservableCollection and Item PropertyChanged
thực hiện của tôi:
tôi đã cố gắng để thực hiện các giải pháp được chấp nhận trong Notify ObservableCollection when Item changes. Ý tưởng cơ bản là kết nối một trình xử lý PropertyChanged
trong MainWindowViewModel của bạn cho từng mục trong ObservableCollection
. Khi một thuộc tính của item được thay đổi, trình xử lý sự kiện sẽ được gọi và bằng cách nào đó View được cập nhật.
Tôi không thể triển khai để hoạt động. Đây là triển khai của tôi.
ViewModels:
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
mục ViewModel:
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);
}
}
Main Window ViewModel:
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");
}
}
Xem:
<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>
Kết quả của tôi:
Để xác minh thực hiện của tôi, tôi có thể tạo một cái nhìn như vậy. TextBlock.Text
bị ràng buộc với mục đầu tiên trong bộ sưu tập. Các ItemsControl
là ràng buộc với bộ sưu tập chính nó.
- Nhấn nút "Thêm nhân viên" thêm một đối tượng
EmployeeViewModel
trong bộ sưu tập và cảTextBlock
vàItemsControl
được cập nhật như mong đợi. - Nhấn lại "Thêm nhân viên",
ItemsControl
được cập nhật với một mục nhập khác. Tuyệt quá! - Nhấn nút "Gia tăng tuổi tác nhân viên". Các tài sản
Age
của mỗi mục được tăng lên 1. Sự kiệnPropertyChanged
được nâng lên. Trình xử lý sự kiệnItemPropertyChanged
được gọi.Textblock
được cập nhật như mong đợi. Tuy nhiên,ItemsControl
không được cập nhật.
Tôi có ấn tượng rằng ItemsControl
cần được cập nhật quá khi Employee.Age
được thay đổi theo câu trả lời trong Notify ObservableCollection when Item changes.
Bạn còn đang cố gắng làm gì nữa? Một bộ sưu tập quan sát được quan sát chính bộ sưu tập, không phải là tài sản của trẻ em. Lợi ích của việc thay đổi bộ sưu tập là gì nếu một tài sản trẻ em thay đổi? – michael
@michael, tôi muốn 'ItemsControl' làm mới khi một mục trong bộ sưu tập được cập nhật. –
Điều đó sẽ đạt được điều gì, tất cả các mục trong bộ sưu tập sẽ được tính. Có giao diện người dùng nhận được tài sản sẽ không thay đổi bất cứ điều gì ... tham chiếu vẫn như cũ. – michael