65.9K
CodeProject 正在变化。 阅读更多。
Home

ObservableCollection 在成员更改时的通知 - 如何使用 ObservableCollectionEx

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (7投票s)

2013年9月29日

CPOL

3分钟阅读

viewsIcon

70865

downloadIcon

944

如何在 ObservableCollection 上进行通知,不仅当集合更改时,而且当集合中任何项目的任何成员更改时。 如何在集合绑定中利用它。

引言

我被敦促写一篇关于如何在真实世界中使用这个 ObservableCollectionEx 的文章。 同时,我介绍了我的 CollectionExtensions 类,以便与 ObservableCollectionObservableCollectionEx 类一起使用。

背景

我正在创建一个可视化查询构建器,用户可以在不知道任何 SQL 的情况下创建数据库查询。 它将基于具有一些属性的表列集合,这些属性将允许我构建查询的 SELECTWHEREGROUP BYORDER BY 子句。 例如,ListBox 将保存数据库表的整个列列表,而在另一个 ListBox 中,将包含在 SELECT 子句中的列列表。 为了使一列出现在第二个 ListBox 中,从而出现在查询的 SELECT 子句中,就像在其他子句要构建的任何窗口部分中一样,我考虑使用 Column 类的一些属性,并将其他 ListBoxItemSource 绑定到完整列集合的过滤子集合。

整个事情将基于单个集合这一事实将使事情变得更容易:单个数据源以及使用持有集合的可绑定的 DependencyProperty 的可能性。

解决方案是在任何绑定到该基本列信息集合的顶部使用值转换器。 当从基本集合中添加或删除项目时,这与 ObservableCollection 配合使用,用于绑定和值转换器,并且需要大量代码来处理由包含完整集合的 ListBoxItemTemplate 的控件中的更改触发的事件。 此事件处理将以触发 CollectionChange 事件的方式更改集合。

这导致了“如果我可以在集合项目成员发生更改时收到通知怎么办?”

我在 ObservableCollectionEx 类中找到了答案。

简化示例

我创建了一个非常简单的项目,用以示例说明 ObservableCollectionEx 的使用,并尝试使其尽可能简单。

项目的关键点

  • ObservableCollectionEx 类;
  • CollectionExtensions 类;
  • 一个具有一些成员的 Item 类;
  • 一个用于保存 Item 集合的 Items 类;
  • 一个 Items 类型的 DependencyProperty
  • 一个 ListBox 用于显示基本集合的所有项目;
  • 一个 ListBox 用于显示基本集合的过滤集合;
  • 一个 值转换器 用于过滤基本集合。

代码

ObservableCollectionEx

public partial class ObservableCollectionEx<T> : ObservableCollection<T>
    where T : INotifyPropertyChanged
{
    public ObservableCollectionEx() : base() { }

    public ObservableCollectionEx(List<T> list)
        : base((list != null) ? new List<T>(list.Count) : list)
    {
        CopyFrom(list);
    }

    public ObservableCollectionEx(IEnumerable<T> collection)
    {
        if (collection == null)
            throw new ArgumentNullException("collection");
        CopyFrom(collection);
    }

    private void CopyFrom(IEnumerable<T> collection)
    {
        IList<T> items = Items;
        if (collection != null && items != null)
        {
            using (IEnumerator<T> enumerator = collection.GetEnumerator())
            {
                while (enumerator.MoveNext())
                {
                    items.Add(enumerator.Current);
                }
            }
        }
    }  
 
    protected override void InsertItem(int index, T item)
    {
        base.InsertItem(index, item);
        item.PropertyChanged += Item_PropertyChanged;
    }

    protected override void RemoveItem(int index)
    {
        Items[index].PropertyChanged -= Item_PropertyChanged;
        base.RemoveItem(index);
    }

    protected virtual void MoveItem(int oldIndex, int newIndex)
    {
        T removedItem = this[oldIndex];
        base.RemoveItem(oldIndex);
        base.InsertItem(newIndex, removedItem);
    } 

    protected override void ClearItems()
    {
        foreach (T item in Items)
        {
            item.PropertyChanged -= Item_PropertyChanged;
        }
        base.ClearItems();
    }

    protected override void SetItem(int index, T item)
    {
        T oldItem = Items[index];
        T newItem = item;
        oldItem.PropertyChanged -= Item_PropertyChanged;
        newItem.PropertyChanged += Item_PropertyChanged;
        base.SetItem(index, item);
    }

    private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        var handler = ItemPropertyChanged;
        if (handler != null) { handler(sender, e); }
    }

    public event PropertyChangedEventHandler ItemPropertyChanged;
}

CollectionExtensions

public static class CollectionExtensions
{
    public static ObservableCollection<T> ToObservableCollection<T>(
      this IEnumerable<T> enumerableList)
    {
        return enumerableList != null ? new ObservableCollection<T>(enumerableList) : null;
    }

    public static ObservableCollectionEx<T> ToObservableCollectionEx<T>(
      this IEnumerable<T> enumerableList) where T : INotifyPropertyChanged
    {
        return enumerableList != null ? new ObservableCollectionEx<T>(enumerableList) : null;
    }
}

ToObservableCollection() 方法未在此项目中用到,但我决定保留它,因为它可能很有用。

Item_Class

public class Item_Class : INotifyPropertyChanged
{
    private int id;
    private string desc;
    private bool show_LB2;
    private bool count_TB;

    public int Id
    {
        get
        {
            return this.id;
        }
        set
        {
            if ((this.id != value))
            {
                this.id = value;
                NotifyPropertyChanged("Id");
            }
        }
    }

    public string Desc
    {
        get
        {
            return this.desc;
        }
        set
        {
            if ((this.desc != value))
            {
                this.desc = value;
                NotifyPropertyChanged("Desc");
            }
        }
    }

    public bool Show_LB2
    {
        get
        {
            return this.show_LB2;
        }
        set
        {
            if ((this.show_LB2 != value))
            {
                this.show_LB2 = value;
                NotifyPropertyChanged("Show_LB2");
            }
        }
    }
 
    public bool Count_TB
    {
        get
        {
            return this.count_TB;
        }
        set
        {
            if ((this.count_TB != value))
            {
                this.count_TB = value;
                NotifyPropertyChanged("Count_TB");
            }
        }
    }

    public Item_Class()
    { }

    public Item_Class(int id,
                      string desc,
                      bool show_LB2,
                      bool count_TB)
    {
        Id = id;
        Desc = desc;
        Show_LB2 = show_LB2;
        Count_TB = count_TB;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string p)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(p));
        }
    }
}

Items_Class – Item_Class 的集合

我们可以说这是魔法发生的地方。 构造函数在 Items 成员上转换CollectionChanged 事件,并将其冒泡Items_Class

对于 Items 成员上的 ItemPropertyChanged 来说,同样如此。 对此类的另一个成员的任何更改都会正常触发 NotifyPropertyChanged (请参见 SomeOtheMember 成员)。

此外,当捕获到 Items 成员上的 Changed 事件时,您可以在其他成员上执行一些操作(此处我重新计算了过滤的 Items 计数)。

public class Items_Class : INotifyPropertyChanged
{
    public Items_Class()
    {
        Items = new ObservableCollectionEx<Item_Class>();
        Items.CollectionChanged += (sender, e) =>
        {
            this.SomeOtheMember = Items.Where(c => c.Count_TB == true).Count();
            NotifyPropertyChanged("Items");
        };
        Items.ItemPropertyChanged += (sender, e) =>
        {
            this.SomeOtheMember = Items.Where(c => c.Count_TB == true).Count();
            NotifyPropertyChanged("Items");
        };
    }

    public ObservableCollectionEx<Item_Class> Items
    {
        get;
        private set;
    }

    private int someOtheMember;
    public int SomeOtheMember
    {
        get
        {
            return this.someOtheMember;
        }
        set
        {
            if ((this.someOtheMember != value))
            {
                this.someOtheMember = value;
                NotifyPropertyChanged("SomeOtheMember");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string p)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(p));
    }
}

值转换器

在这里,完整的集合被过滤以用作第二个 ListBoxItemSource,并且 ToObservableCollectionEx() 的使用变得方便。

public class ToListBox2Converter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null) return null;
        return ((ObservableCollectionEx<Item_Class>)value).Where(c => c.Show_LB2 == true).ToObservableCollectionEx();
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

MainPage

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:ObservableCollectionExExample" 
    xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" 
    x:Name="userControl" 
    x:Class="ObservableCollectionExExample.MainPage"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
 
    <UserControl.Resources>
        <local:ToListBox2Converter x:Key="ToListBox2Converter"/>
        <DataTemplate x:Key="DataTemplate1">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="20"/>
                    <ColumnDefinition Width="80"/>
                    <ColumnDefinition Width="16"/>
                    <ColumnDefinition Width="16"/>
                </Grid.ColumnDefinitions>
                <sdk:Label x:Name="LabelId" Margin="0,0,0,4" 
                  d:LayoutOverrides="Width, Height" Content="{Binding Id}"/>
                <sdk:Label x:Name="LabelDesc" Margin="0,0,0,4" 
                  d:LayoutOverrides="Width, Height" Grid.Column="1" 
                  Content="{Binding Desc}"/>
                <CheckBox x:Name="CheckBoxShow_LB2" Content="" 
                  Margin="0,0,0,3" d:LayoutOverrides="Width, Height" 
                  Grid.Column="2" IsChecked="{Binding Show_LB2, Mode=TwoWay}"/>
                <CheckBox x:Name="CheckBoxCount_TB2" Content="" 
                  Margin="0,0,0,3" d:LayoutOverrides="Width, Height" 
                  Grid.Column="3" IsChecked="{Binding Count_TB, Mode=TwoWay}"/>
            </Grid>
        </DataTemplate>
    </UserControl.Resources>
 
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="10"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ListBox x:Name="ListBox1" 
          ItemsSource="{Binding ITEMS.Items, ElementName=userControl}" 
          ItemTemplate="{StaticResource DataTemplate1}"/>
        <ListBox x:Name="ListBox2" Grid.Column="2" 
          ItemsSource="{Binding ITEMS.Items, Converter=
            {StaticResource ToListBox2Converter}, ElementName=userControl}" 
          ItemTemplate="{StaticResource DataTemplate1}"/>
        <TextBlock x:Name="TextBox1" HorizontalAlignment="Center" 
          TextWrapping="Wrap" Text="{Binding ITEMS.SomeOtheMember, ElementName=userControl}" 
          VerticalAlignment="Top" Grid.Row="2" Grid.ColumnSpan="3"/>
 
    </Grid>
</UserControl>

ListBox1 绑定到 ITEMS,保存了整个项目的集合。 ListBox2 绑定到 ITEMS,使用 ToListBox2Converter 转换器,保存了 Show_LB2 设置为 true 的项目的集合。 TextBox1 保存了 ITEMSSomeOtherMember 成员的值。

public partial class MainPage : UserControl
{
    private static DependencyProperty ITEMSProperty = 
      DependencyProperty.Register("ITEMS", typeof(Items_Class), 
      typeof(MainPage), new PropertyMetadata(new Items_Class()));
    public Items_Class ITEMS
    {
        get { return (Items_Class)GetValue(ITEMSProperty); }
        set { SetValue(ITEMSProperty, value); }
    }

    public MainPage()
    {
        InitializeComponent();

        //Populate ITEMS
        ITEMS.Items.Add(new Item_Class(1, "desc 1", false, false));
        ITEMS.Items.Add(new Item_Class(2, "desc 2", true, true));
        ITEMS.Items.Add(new Item_Class(3, "desc 3", true, false));
        ITEMS.Items.Add(new Item_Class(4, "desc 4", false, true));
        ITEMS.Items.Add(new Item_Class(5, "desc 5", true, false));
        ITEMS.Items.Add(new Item_Class(6, "desc 6", false, true));
        ITEMS.Items.Add(new Item_Class(7, "desc 7", true, false));
        ITEMS.Items.Add(new Item_Class(8, "desc 8", false, false));
    }
}

关注点

请注意,我无法真正解释它,但 TwoWay 绑定似乎运行良好。

© . All rights reserved.