2015-02-10 14 views
8

Il problema:Aggiornamento ItemsControl quando un elemento in un ObservableCollection viene aggiornato

  • si dichiara un ItemsControl (o un controllo derivato da ItemsControl) nella vista .
  • Si associa la proprietà ItemsControl.ItemsSource a un ObservableCollection nel ViewModel.
  • La visualizzazione viene aggiornata come previsto quando un articolo viene aggiunto/rimosso da ObservableCollection.
  • MA, la vista non si aggiorna quando si modifica una proprietà di un articolo in ObservableCollection.

Background:

Sembra che questo è un problema comune molti sviluppatori WPF hanno incontrato. E 'stato chiesto un paio di volte:

Notify ObservableCollection when Item changes

ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)

ObservableCollection and Item PropertyChanged

mia implementazione:

ho cercato di implementare la soluzione accettata in Notify ObservableCollection when Item changes. L'idea di base è quella di collegare un gestore PropertyChanged nel MainWindowViewModel per ciascun articolo nello ObservableCollection. Quando viene modificata la proprietà di un oggetto, viene richiamato il gestore eventi e in qualche modo la vista viene aggiornata.

Non sono riuscito a far funzionare l'implementazione. Ecco la mia implementazione.

ViewModels:

class ViewModelBase : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    protected void RaisePropertyChanged(string propertyName = "") 
    { 
     var handler = PropertyChanged; 
     if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

Articolo 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); 
    } 
} 

Finestra principale 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"); 
    } 
} 

Vista:

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

mie Risultati:

per verificare la mia realizzazione, creo una vista in questo modo. Lo TextBlock.Text è associato al primo elemento della raccolta. Lo ItemsControl è associato alla raccolta stessa.

  • Premendo il pulsante "Aggiungi Dipendente" aggiunge un oggetto EmployeeViewModel nella raccolta e sia il TextBlock e ItemsControl vengono aggiornati come previsto.
  • Premendo di nuovo "Aggiungi dipendente", lo ItemsControl viene aggiornato con un'altra voce. Grande!
  • Premere il pulsante "Increment Employee Age". La proprietà Age di ciascun elemento viene incrementata di 1. Viene generato l'evento PropertyChanged. Il gestore di eventi ItemPropertyChanged viene richiamato. Textblock viene aggiornato come previsto. Tuttavia, lo ItemsControl non è aggiornato.

Ho l'impressione che il ItemsControl deve essere aggiornato anche quando il Employee.Age viene modificato in base alla risposta in Notify ObservableCollection when Item changes.

enter image description here

+0

Cosa stai cercando di fare? Una collezione osservabile sta osservando la collezione stessa, non le proprietà dei bambini. Qual è il vantaggio di un evento modificato di una raccolta se viene modificata una proprietà figlio? – michael

+0

@michael, voglio che 'ItemsControl' si aggiorni quando un oggetto della collezione viene aggiornato. –

+0

Ciò che comporterà comunque, tutti gli elementi della collezione sono stati conteggiati. Avere l'interfaccia utente per ottenere la proprietà non cambierà nulla ... il riferimento è sempre lo stesso. – michael

risposta

9

ho trovato la risposta utilizzando Snoop per eseguire il debug XAML.

Il problema è che si sta tentando di eseguire il binding al metodo ToString() e che non si genera l'evento PropertyChanged. Se osservi i binding XAML, noterai che ObservableCollection sta effettivamente cambiando.

Snoop Showing Correct Binding

ora un'occhiata ad ogni controllo voce ed è testi vincolanti nella proprietà "Testo". Non ce ne sono, è solo testo.

Snoop showing no data binding to elements in the items control

Per correggere questo è sufficiente aggiungere un ItemsControl ItemTemplate con un DataTemplate che contiene gli elementi che desideri visualizzare.

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

Ora abbiamo una luce verde sul rilegatura. RaisePropertyChanged viene chiamato.

Binding correctly shows green

Ta-da!

Solution shown

+0

Grazie per la risposta. Ho aggiunto un setter per la proprietà 'Employees' (ObservableCollection) e ho chiamato' RaisePropertyChanged' nel setter. Ma non fa scattare il 'ItemsControl' per aggiornare quando si preme il pulsante di incremento dei dipendenti. –

+0

Ottima risposta. Capisco che usare ItemTemplate per associare alla proprietà dell'oggetto possa aggirare questo problema. Infatti, se si utilizza ItemTemplate, è necessario anche gestire l'evento CollectionChanged per collegare il gestore ItemPropertyChanged a ciascun elemento. Penso che ci potrebbe essere un modo per risolvere questo problema senza utilizzare ItemTemplate. Se non viene presentata un'altra risposta. Segnerò i tuoi come accettati. –

+2

@FrankLiu La dichiarazione di un ItemTemplate è il modo standard di visualizzare gli elementi in un controllo ItemsControl o un controllo derivato come ListBox. Si consiglia di leggere l'articolo [Panoramica templating dati] (https://msdn.microsoft.com/en-us/library/ms742521.aspx) su MSDN. – Clemens

Problemi correlati