2015-02-10 17 views
8

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ả TextBlockItemsControl đượ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ện PropertyChanged được nâng lên. Trình xử lý sự kiện ItemPropertyChanged đượ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.

enter image description here

+0

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

+0

@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. –

+0

Đ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

Trả lời

9

Tôi tìm thấy câu trả lời bằng Snoop để gỡ lỗi XAML.

Vấn đề là bạn đang cố gắng liên kết với phương thức ToString() và điều đó không làm tăng sự kiện PropertyChanged. Nếu bạn nhìn vào các ràng buộc XAML, bạn sẽ nhận thấy rằng ObservableCollection thực sự đang thay đổi.

Snoop Showing Correct Binding

Bây giờ nhìn vào từng điều khiển mục và đó là văn bản ràng buộc trong "Văn bản" bất động sản. Không có, nó chỉ là văn bản.

Snoop showing no data binding to elements in the items control

Để sửa lỗi này chỉ cần thêm một ItemsControl ItemTemplate với một DataTemplate có chứa các yếu tố mà bạn muốn được hiển thị.

<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> 

Hiện tại, chúng tôi có đèn xanh lục trên ràng buộc. RaisePropertyChanged đang được gọi.

Binding correctly shows green

Ta-da!

Solution shown

+0

Cảm ơn bạn đã trả lời. Tôi đã thêm một setter cho thuộc tính 'Employees' (ObservableCollection) và được gọi là' RaisePropertyChanged' trong setter. Nhưng nó không kích hoạt 'ItemsControl' để cập nhật khi nhấn nút tuổi nhân viên tăng. –

+0

Câu trả lời hay. Tôi hiểu bằng cách sử dụng ItemTemplate để liên kết với thuộc tính của mục có thể làm việc xung quanh vấn đề này. Trong thực tế, nếu bạn sử dụng ItemTemplate, chúng tôi thậm chí cần xử lý sự kiện CollectionChanged để kết nối trình xử lý ItemPropertyChanged với mỗi mục. Tôi nghĩ rằng có thể có một cách để sửa lỗi này mà không cần sử dụng ItemTemplate. Nếu không có câu trả lời khác được trình bày. Tôi sẽ đánh dấu của bạn là chấp nhận. –

+2

@FrankLiu Khai báo một ItemTemplate là cách tiêu chuẩn để hiển thị các mục trong một ItemsControl hoặc một điều khiển có nguồn gốc như ListBox. Bạn có thể muốn đọc [Tổng quan về dữ liệu Templating] (https://msdn.microsoft.com/en-us/library/ms742521.aspx) bài viết trên MSDN. – Clemens

Các vấn đề liên quan