Donnerstag, 12. Januar 2012

INotifyCollectionChanged and INotifyPropertyChanged when ItemsControl.ItemsSource is databound

When an ItemsControl should update its elements when the collection that is used as ItemsSource via Binding is changed in code behind (best in the Model when using MVVM), you have to implement the INotifyCollectionChanged interface or you have to use a collection which already implements it (like ObservableCollection<>).

Also you will have to implement the INotifyPropertyChanged in the items class.
Here is a small sample how this can work:

First of all there is some base class implementing INotifyPropertyChanged, and a class (the items of the collection) which has some properties using the implementation:
  public class ViewModelBase : INotifyPropertyChanged
  {
    #region INotifyPropertyChanged
    protected void OnPropertyChanged(string propertyName)
    {
      if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion INotifyPropertyChanged
  }


  public class CollectionItem : ViewModelBase
  {
    private string _sSampleString;
    public string SampleString
    {
      get { return this._sSampleString; }
      set
      {
        if (this._sSampleString != value)
        {
          this._sSampleString = value;
          this.OnPropertyChanged("SampleString");
        }
      }
    }
    // ... other properties ...
  }


This way we take care, that every change made in code behind for every item will make the bindings in our ItemsControls Items (user interface, UI) refresh.

Next we have to implement a collection which uses the INotifyCollectionChanged interface (or, as said before, use some given collection which already implements it):

  public class CollectionSample : INotifyCollectionChanged, IEnumerable
  {
    private List<CollectionItem> _lstItems
      = new List<CollectionItem>();

    public void Add(CollectionItem item)
    {
      this._lstItems.Add(item);
      this.OnNotifyCollectionChanged(
        new NotifyCollectionChangedEventArgs(
          NotifyCollectionChangedAction.Add, item));
    }

    public void Remove(CollectionItem item)
    {
      this._lstItems.Remove(item);
      this.OnNotifyCollectionChanged(
        new NotifyCollectionChangedEventArgs(
          NotifyCollectionChangedAction.Remove, item));
    }

    // ... other actions for the collection ...

    public CollectionItem this[Int32 index]
    {
      get
      {
        return this._lstItems[index];
      }
    }

    #region INotifyCollectionChanged
    private void OnNotifyCollectionChanged(NotifyCollectionChangedEventArgs args)
    {
      if (this.CollectionChanged != null)
      {
        this.CollectionChanged(this, args);
      }
    }
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    #endregion INotifyCollectionChanged

    #region IEnumerable
    public List<CollectionItem>.Enumerator GetEnumerator()
    {
      return this._lstItems.GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
      return (IEnumerator)this.GetEnumerator();
    }
    #endregion IEnumerable
  }


Also there is an implementation of the IEnumerator interface and a this-pointer. But our main view is on the INotifyCollectionChanged interface:

For every important event that happens with the items there should be a method, which performs it. But we do not have to take care of the properties of the items itself. This is managed by implementing the INotifyPropertyChanged interface.

So let’s just make some XAML code and not very usefull code behind to see how it works:
<Window x:Class="WpfApplication1.View"
        xmlns:util="clr-namespace:WpfApplication1"
        xmlns:controls="clr-namespace:System.Windows.Controls;assembly=PresentationFramework"
        Initialized="Window_Initialized"
        Title="MainWindow" Height="400" Width="600">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*"/>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <ListView Name="listview"
              Grid.Row="0">
      <ListView.ItemTemplate>
        <DataTemplate>
          <TextBox Text="{Binding SampleString}"/>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
    <Button Grid.Row="1"
            Content="refresh"
            Click="Button_Click"/>
  </Grid>
</Window>


  public partial class View : Window
  {
    public View()
    {
      InitializeComponent();
    }

    private void Window_Initialized(object sender, EventArgs e)
    {
      this.listview.ItemsSource = new CollectionSample();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      (this.listview.ItemsSource as CollectionSample).Add(
        new CollectionItem { SampleString = DateTime.Now.ToString() });
    }
  }
You can manipulate everything in code behind and it will directly be recognized by the UI.
And that’s it J.

Keine Kommentare:

Kommentar veröffentlichen