? 從普通 ItemsControl 到支持篩選的 ItemsControl:深入掌握 CollectionViewSource 用法
在日常 WPF 開發中,我們經常需要對數據進行篩選、排序、分組等操作,而原生的 ItemsControl
并不直接支持這些功能。本文將介紹如何通過 CollectionViewSource
給一個普通的 ItemsControl
加上 動態篩選功能,并指出其中的一些關鍵注意點。
📦 一、初始結構:普通 ItemsControl 顯示列表
我們從一個最簡單的 ItemsControl
開始:
<ItemsControl ItemsSource="{Binding MBConfigList}"><ItemsControl.ItemTemplate><DataTemplate><TextBlock Text="{Binding FileName}" /></DataTemplate></ItemsControl.ItemTemplate>
</ItemsControl>
這個控件可以顯示綁定的 MBConfigList
數據,但沒有任何過濾能力。
🛠? 二、目標:支持按光源類型篩選
我們希望最終能夠讓用戶勾選光源類型,動態篩選出指定類型的數據。例如:
- 支持“全選”
- 多個
CheckBox
動態刷新列表 - 用戶操作后立即生效
🧰 三、改造步驟
1?? 引入 CollectionViewSource
我們在 ViewModel 中定義一個 CollectionViewSource
并設置過濾事件:
private CollectionViewSource _viewSource;public ICollectionView FilteredMBConfigList
{get => _viewSource?.View;private set => SetProperty(ref _filteredView, value); // 實現 INotifyPropertyChanged
}public void InitializeFilteredView()
{_viewSource = new CollectionViewSource{Source = GlobalData.Instance.saveInfo.MBConfigList};_viewSource.Filter += ApplyFilter;FilteredMBConfigList = _viewSource.View;
}
FilteredMBConfigList 必須是支持通知的不然界面是不會變化的。
2?? 編寫篩選邏輯 ApplyFilter
private void ApplyFilter(object sender, FilterEventArgs e)
{if (e.Item is MBConfigInfo item){var selectedTypes = LightSourceItems.Where(x => x.IsChecked).Select(x => x.Name).ToHashSet();//ToHashSet()是為了更快的查詢// 顯示所有或匹配項e.Accepted = selectedTypes.Count == 0 || selectedTypes.Contains(item.LightSourceType);}
}
private CollectionViewSource _viewSource; 不能申明成局部變量,不然ApplyFilter只能生效一次。
e.Accepted == true 的選項才會被顯示出來。
3?? UI 綁定到新集合
<ItemsControl ItemsSource="{Binding FilteredMBConfigList}"><ItemsControl.ItemTemplate><DataTemplate><TextBlock Text="{Binding FileName}" /></DataTemplate></ItemsControl.ItemTemplate>
</ItemsControl>
這樣,控件就用上了支持篩選的視圖。
🚨 四、關鍵注意事項
?? 1. CollectionViewSource 必須緩存
不能每次 get
都創建新的 CollectionViewSource
,否則不會觸發 .Refresh()
。
// 錯誤寫法(每次都 new):
public ICollectionView Filtered => new CollectionViewSource { Source = xxx }.View;// 正確:只創建一次
?? 2. 調用 View.Refresh() 以應用篩選
數據變化時,你必須手動調用:
FilteredMBConfigList.Refresh();
建議綁定項(如 CheckBox
)中 PropertyChanged
事件或命令中執行刷新。
FilteredMBConfigList.Refresh();調用后會重新觸發ApplyFilter 以到達篩選的目的。
?? 3. 篩選邏輯不要復雜耗時
FilterEventArgs
的處理函數會在每次刷新時對 所有項 執行。避免長計算!
?? 4. UI 綁定的是 View,不是 CollectionViewSource 本身
綁定語法是:
ItemsSource="{Binding FilteredMBConfigList}"
不是 _viewSource
,而是其 .View
。
? 五、示例:支持多選篩選項結構
使用一個簡單的模型:
public class LightSourceFilterItem : INotifyPropertyChanged
{public string Name { get; }public bool IsChecked { get; set; } // 實現通知
}
ViewModel:
public ObservableCollection<LightSourceFilterItem> LightSourceItems { get; set; }LightSourceItems = new ObservableCollection<LightSourceFilterItem>(new[] { "上光源", "下光源", "側光源" }.Select(name =>{var item = new LightSourceFilterItem(name) { IsChecked = true };item.PropertyChanged += (_, __) => FilteredMBConfigList?.Refresh();return item;}));
XAML:
<ItemsControl ItemsSource="{Binding LightSourceItems}"><ItemsControl.ItemTemplate><DataTemplate><CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked, Mode=TwoWay}" /></DataTemplate></ItemsControl.ItemTemplate>
</ItemsControl>
🏁 六、總結
優點 ? | 注意點 ?? |
---|---|
支持篩選、排序、分組 | .Refresh() 要手動調用 |
與 ItemsControl , ListBox , DataGrid 搭配無縫 | Filter 邏輯要快 |
不改變原始集合 | 不要頻繁 new CollectionViewSource |
使用 CollectionViewSource
是 WPF 中實現數據視圖分離的經典方式,非常適合構建支持過濾的 UI。