WPF多个CollectionView在同一个集合上使用不同的过滤器

52

我正在使用一个包含两个 ICollectionView 用于不同筛选条件的 ObservableCollection

其中一个用于按某些类型过滤消息,另一个用于统计已选中消息的数量。如您所见,消息过滤和消息计数都正常工作,但当我取消选中消息时,该消息会从列表中消失(计数仍然有效)。

顺便说一下,对于篇幅较长的文章我表示抱歉,我想包含所有相关的内容。

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

Dave,这不是一个答案,但可能会对你有所帮助。最近我在做一些WPF合同工作时,我无法找到我想要的过滤、分页、排序的正确解决方案。我建立了这个通用类。我想你可能会喜欢研究一下它。http://www.origin1.com/downloads/PagedObservableCollection.txt。显然需要更改扩展名。 - origin1tech
在寻找类似问题时,我遇到了这个问题和答案。很难理解这里发生了什么--MessageClassAvailableFilters的定义没有包含在内,并且有许多C#/.NET特性可以使此代码更加简洁并且一目了然--自动属性、Lambda表达式、LINQ。 - Zev Spitz
2个回答

117

这个答案 帮助我解决了这个具体的问题。静态方法CollectionViewSource.GetDefaultView(coll)将始终针对给定的集合返回相同的引用,因此基于相同引用构建多个集合视图会适得其反。通过以下方式实例化视图:

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

现在,可以独立于其他视图对视图进行过滤/排序/分组。 然后,您可以应用您的筛选。


5
谢谢,实际上这非常有帮助,因为我无法实现它并导致了一个丑陋的大绕路。 - drtf
我有一个问题:filteredView似乎没有观察messageList,因此它不会对源集合的任何更改做出反应。 - Jakub Pawlinski
@JakubPawlinski,我也遇到了同样的问题。你找到解决方案了吗? - Pancake
10
@JakubPawlinski, @Pancake。我找到了解决方案。直接创建列表集合视图。ICollectionView filteredView = new ListCollectionView(sourceCollection);谢谢。 - Null Pointer
3
这个答案解决了我所寻找的问题的一半。另一半来自这里。基本上,在某些情况下,刷新视图会抛出空引用异常,因为支持视图的源已经被垃圾回收。修复方法是将源存储在类范围内的变量中,而不是将其丢弃: _viewSource = new CollectionViewSource { Source = messageList }; var filteredView = _viewSource.View; - Travis
显示剩余2条评论

3

对于那些在过滤视图(filteredView)无法观察源集合(messageList在本例中)的问题上遇到困难的人:

我提出了这个解决方案:

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

因此,每当源的CollectionChanged事件被触发时,它将刷新我们的filteredView。当然,您也可以像这样实现:

messageList.CollectionChanged += messageList_CollectionChanged;

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

考虑使用 PropertyChanged-Events 来实现对特定属性的筛选。

如果您希望您的过滤视图在相关属性(集合中任何项的任何属性)更改时自动重新评估,请考虑使用LiveFilteringProperties,这样您就不必显式编写刷新代码。 https://learn.microsoft.com/de-de/dotnet/api/system.windows.data.collectionviewsource.livefilteringproperties 但是,据我所知,它仅设计为对Item的PropertyChanged事件做出反应。它不会监听CollectionChanged事件(如果我记得正确的话)。 - lidqy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接