【问题标题】:WPF Multiple CollectionView with different filters on same collectionWPF 多个 CollectionView 在同一集合上具有不同的过滤器
【发布时间】:2026-02-07 19:50:01
【问题描述】:

我正在使用一个 ObservableCollection 和两个 ICollectionView 用于不同的过滤器。

一个用于按某种类型过滤消息,一个用于对已检查的消息进行计数。 如您所见,消息过滤器和消息计数工作正常,但是当我取消选中时,消息从列表中消失(计数仍在工作)。

顺便说一句,很抱歉这篇文章很长,我想包括所有相关的东西。

XAML 代码:

<!-- Messages List -->
<DockPanel Grid.Row="1"
           Grid.Column="0"
           Grid.ColumnSpan="3"
           Height="500">
  <ListBox Name="listBoxZone"
           ItemsSource="{Binding filteredMessageList}"
           Background="Transparent"
           BorderThickness="0">
    <ListBox.ItemTemplate>
      <DataTemplate>
        <CheckBox Name="CheckBoxZone"
                  Content="{Binding text}"
                  Tag="{Binding id}"
                  Unchecked="CheckBoxZone_Unchecked"
                  Foreground="WhiteSmoke"
                  Margin="0,5,0,0"
                  IsChecked="{Binding isChecked}" />
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
</DockPanel>
<Button Content="Test Add New"
        Grid.Column="2"
        Height="25"
        HorizontalAlignment="Left"
        Margin="34,2,0,0"
        Click="button1_Click" />
<Label Content="{Binding checkedMessageList.Count}"
       Grid.Column="2"
       Height="25"
       Margin="147,2,373,0"
       Width="20"
       Foreground="white" />

截图:

代码:

/* ViewModel Class */
public class MainViewModel : INotifyPropertyChanged
{

    // Constructor
    public MainViewModel()
    {
        #region filteredMessageList
        // connect the ObservableCollection to CollectionView
        _filteredMessageList = CollectionViewSource.GetDefaultView(messageList);
        // set filter 
        _filteredMessageList.Filter = delegate(object item)
        {
            MessageClass temp = item as MessageClass;

            if ( selectedFilter.Equals(AvailableFilters.All) )
            {
                return true;
            }
            else
            {
                return temp.filter.Equals(_selectedFilter);
            }
        };
        #endregion

        #region checkedMessageList
        // connect the ObservableCollection to CollectionView
        _checkedMessageList = CollectionViewSource.GetDefaultView(messageList);
        // set filter 
        _checkedMessageList.Filter = delegate(object item) { return (item as MessageClass).isChecked; };
        #endregion
    }

    // message List
    private ObservableCollection<MessageClass> _messageList =
            new ObservableCollection<MessageClass>();
    public ObservableCollection<MessageClass> messageList
    {
        get { return _messageList; }
        set { _messageList = value; }
    }

    // CollectionView (filtered messageList)
    private ICollectionView _filteredMessageList;
    public ICollectionView filteredMessageList
    {
        get { return _filteredMessageList; }
    }

    // CollectionView (filtered messageList)
    private ICollectionView _checkedMessageList;
    public ICollectionView checkedMessageList
    {
        get { return _checkedMessageList; }
    }

    // SelectedFilter property
    private AvailableFilters _selectedFilter = AvailableFilters.All; // Default is set to all
    public AvailableFilters selectedFilter
    {
        get { return _selectedFilter; }
        set
        {
            _selectedFilter = value;
            RaisePropertyChanged("selectedFilter");
            _filteredMessageList.Refresh(); // refresh list upon update
        }
    }

    // FilterList (Convert Enum To Collection)
    private List<KeyValuePair<string, AvailableFilters>> _AvailableFiltersList;
    public List<KeyValuePair<string, AvailableFilters>> AvailableFiltersList
    {
        get
        {
            /* Check if such list available, if not create for first use */
            if (_AvailableFiltersList == null)
            {
                _AvailableFiltersList = new List<KeyValuePair<string, AvailableFilters>>();
                foreach (AvailableFilters filter in Enum.GetValues(typeof(AvailableFilters)))
                {
                    string Description;
                    FieldInfo fieldInfo = filter.GetType().GetField(filter.ToString());
                    DescriptionAttribute[] attributes =
                                (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

                    /* if not null get description */
                    if (attributes != null && attributes.Length > 0)
                    {
                        Description = attributes[0].Description;
                    }
                    else
                    {
                        Description = string.Empty;
                    }

                    /* add as new item to filterList */
                    KeyValuePair<string, AvailableFilters> TypeKeyValue =
                                new KeyValuePair<string, AvailableFilters>(Description, filter);

                    _AvailableFiltersList.Add(TypeKeyValue);
                }
            }
            return _AvailableFiltersList;
        }
    }

    #region Implement INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    public void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

取消勾选功能代码

private void CheckBoxZone_Unchecked(object sender, RoutedEventArgs e)
{
    CheckBox chkZone = (CheckBox)sender;
    ucSystemMessageVM.checkedMessageList.Refresh();
}

【问题讨论】:

  • 戴夫这不是一个答案,但可能会帮助你。最近在做一些 WPF 合同工作,我只是找不到合适的过滤器、分页、排序解决方案。我建立了这个通用类。想你可能想在里面闲逛。 origin1.com/downloads/PagedObservableCollection.txt。显然改变了分机。
  • 在搜索类似问题时,我遇到了这个问题和答案。很难理解这里发生了什么——MessageClassAvailableFilters 的定义不包括在内,并且有许多 C#/.NET 特性可以使这段代码更加简洁和易于理解一目了然——自动属性、lambda 表达式、LINQ。

标签: c# wpf observablecollection collectionviewsource icollectionview


【解决方案1】:

对于任何为过滤视图没有观察到 sourceCollection(本示例中的 messageList)问题而苦苦挣扎的人:

我想出了这个解决方案:

ICollectionView filteredView = new CollectionViewSource { Source=messageList }.View;
messageList.CollectionChanged += delegate { filteredView.Refresh(); };

所以它会在源 get 的 CollectionChanged 事件被触发时刷新我们的过滤视图。当然你也可以这样实现:

messageList.CollectionChanged += messageList_CollectionChanged;

private void messageList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    filteredView.Refresh(); 
}

在需要过滤特定属性时考虑使用 PropertyChanged-Events。

【讨论】:

  • 如果您希望过滤后的视图在相关属性(集合中的任何项目)更改时自动重新评估,请考虑使用 LiveFilteringProperties 然后您不必显式编写代码来刷新@ 987654321@ 但是,它仅设计用于对 Item 的 PropertyChanged 事件做出反应。它不听 CollectionChanged(如果我没记错的话)。
【解决方案2】:

This answer 帮我解决了这个问题。静态CollectionViewSource.GetDefaultView(coll) 方法将始终为给定集合返回相同的引用,因此基于相同引用的多个集合视图将适得其反。通过按如下方式实例化视图:

ICollectionView filteredView = new CollectionViewSource { Source=messageList }.View;

视图现在可以独立于任何其他视图进行过滤/排序/分组。然后你就可以应用你的过滤器了。

我知道已经过去几个月了,你现在可能已经解决了你的问题,但是当我遇到同样的问题时我遇到了这个问题,所以我想我会添加一个答案。

【讨论】:

  • 谢谢,实际上这很有帮助,因为我无法做到这一点并导致了一个丑陋的解决方法
  • 我对这个方法有一个问题:filteredView 似乎没有观察到 messageList,所以它不会对源集合的任何更改做出反应
  • @JakubPawlinski,我也看到了同样的情况。你找到解决方案了吗?
  • @JakubPawlinski,@Pancake。我找到了解决方案。直接创建 List Collection 视图。 ICollectionView filterdView=new ListCollectionView(sourceCollection);谢谢
  • 这个答案提供了我正在寻找的一半。另一半来自here。基本上,在某些情况下,刷新视图会引发空引用异常,因为支持视图的源已被 GC'd。解决方法是将源存储在类范围变量中,而不是将其丢弃:_viewSource = new CollectionViewSource { Source = messageList }; var filteredView = _viewSource.View;