2015-02-10 16 views
8

problem:Aktualizacja ItemsControl gdy pozycja w ObservableCollection jest aktualizowana

  • deklarujesz ItemsControl (lub kontrola pochodzi z ItemsControl) w widoku .
  • Powiąż właściwość ItemsControl.ItemsSource z ObservableCollection w swoim ViewModel.
  • Twój widok aktualizuje się zgodnie z oczekiwaniami, gdy element zostanie dodany do/usunięty z ObservableCollection.
  • ALE, widok nie aktualizuje się po zmianie właściwości elementu w ObservableCollection.

Tło:

Wydaje się, że jest to częsty problem wielu deweloperów WPF spotkałem. Został poproszony kilka razy:

Notify ObservableCollection when Item changes

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

ObservableCollection and Item PropertyChanged

Moje Wykonanie:

starałem się wdrożyć przyjętego rozwiązania w Notify ObservableCollection when Item changes. Podstawową ideą jest podpięcie się handlerkiem PropertyChanged w MainWindowViewModel dla każdego elementu w ObservableCollection. Gdy właściwość elementu zostanie zmieniona, procedura obsługi zdarzenia zostanie wywołana i jakoś zaktualizuje widok.

Nie mogłem uruchomić wdrożenia. Oto moja implementacja.

ViewModels:

class ViewModelBase : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

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

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

Okno główne 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"); 
    } 
} 

Widok:

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

Moi Wyniki:

zweryfikować swoją realizację, tworzę widok jak tak. Numer TextBlock.Text jest powiązany z pierwszym elementem w kolekcji. ItemsControl jest związany z samą kolekcją.

  • Naciśnięcie przycisku „Dodaj pracownicze” dodaje obiekt EmployeeViewModel w zbiorach i zarówno TextBlock i ItemsControl są aktualizowane zgodnie z oczekiwaniami.
  • Po ponownym naciśnięciu przycisku "Dodaj pracownika", ItemsControl zostaje zaktualizowany o kolejny wpis. Wspaniały!
  • Naciśnięcie przycisku "Przyrost wieku pracownika". Właściwość Age każdej pozycji zwiększa się o 1. Wydarzenie PropertyChanged zostało podniesione. Obsługa zdarzenia ItemPropertyChanged jest wywoływana. Numer Textblock jest aktualizowany zgodnie z oczekiwaniami. Jednak wersja ItemsControl nie jest aktualizowana.

Jestem pod wrażeniem, że ItemsControl należy zaktualizować zbyt kiedy Employee.Age zmienia się w zależności od odpowiedzi w Notify ObservableCollection when Item changes.

enter image description here

+0

Co jeszcze próbujesz zrobić? Obserwowalna kolekcja obserwuje samą kolekcję, a nie właściwości dzieci. Jaka jest korzyść z przeprowadzania zdarzenia zmiany kolekcji, jeśli zmieni się właściwość podrzędna? – michael

+0

@ Michael, chcę 'ItemsControl' odświeżyć, gdy element w kolekcji jest aktualizowany. –

+0

Co by się udało, wszystkie pozycje w kolekcji zostały uwzględnione. Posiadanie interfejsu użytkownika powoduje, że właściwość niczego nie zmieni ... odniesienie jest wciąż takie samo. – michael

Odpowiedz

9

Znalazłem odpowiedź korzystając Snoop debugować XAML.

Problem polega na tym, że próbujesz powiązać metodę ToString() i nie powoduje to zdarzenia PropertyChanged. Jeśli spojrzysz na powiązania XAML, zauważysz, że ObservableCollection faktycznie się zmienia.

Snoop Showing Correct Binding

Spójrzmy teraz na każdym punkcie kontroli i jest wiążące teksty w „Tekst” mienia. Nie ma żadnych, to tylko tekst.

Snoop showing no data binding to elements in the items control

Aby rozwiązać ten problem wystarczy dodać ItemsControl ItemTemplate z DataTemplate, który zawiera elementy, które chcesz zostać wyświetlona.

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

Teraz mamy zielone światło na oprawę. Zostanie wywołany RaisePropertyChanged.

Binding correctly shows green

Ta-da!

Solution shown

+0

Dzięki za odpowiedź. Dodałem narzędzie ustawiające dla właściwości 'Employees' (ObservableCollection) i nazwałem' RaisePropertyChanged' w ustawiaczu. Ale to nie uruchamia "ItemsControl", aby zaktualizować po naciśnięciu przycisku wzrostu wieku pracownika. –

+0

Świetna odpowiedź. Rozumiem, że użycie ItemTemplate do powiązania z właściwością elementu może obejść ten problem. W rzeczywistości, jeśli używasz ItemTemplate, potrzebujemy nawet obsługi zdarzenia CollectionChanged, aby podłączyć handler ItemPropertyChanged do każdego elementu. Myślę, że może istnieć sposób, aby to naprawić bez użycia ItemTemplate. Jeśli nie podano innej odpowiedzi. Zaznaczę twoje jako zaakceptowane. –

+2

@FrankLiu Deklarowanie ItemTemplate to standardowy sposób wizualizacji elementów w ItemControl lub pochodnej kontrolce, takiej jak ListBox. Możesz przeczytać artykuł [Data Templating Overview] (https://msdn.microsoft.com/en-us/library/ms742521.aspx) na temat MSDN. – Clemens

Powiązane problemy