如何为CollectionViewSource的异步筛选实现进度报告?
CollectionViewSource异步筛选实现进度条的问题
由于集合规模大幅增长,我正将CollectionViewSource上的搜索操作从同步改为异步,目的是向用户提供进度报告。但问题在于,我无法在筛选事件处理程序中引用Progress<int>对象,不知道如何为CollectionViewSource的筛选操作实现ProgressBar。以下是带有进度报告的可行异步方法示例,以及体现该困境的筛选方案框架:
XAML代码
<Window x:Class="testProgressFilter.MainWindow" 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:testProgressFilter" mc:Ignorable="d" Title="MainWindow" Height="400" Width="500"> <Window.Resources> <local:VM x:Key="VM" /> </Window.Resources> <Window.DataContext> <Binding Source="{StaticResource VM}"/> </Window.DataContext> <Grid> <StackPanel Margin="30"> <StackPanel Orientation="Horizontal" > <ProgressBar Minimum="{Binding ProgressBarMinimum}" Maximum="{Binding ProgressBarMaximum}" Value="{Binding ProgressBarValue}" Height="30" Width="300"/> <Button Margin="20,0,0,0" Height="30" Width="75" Command="{Binding CancelCommand}" Content="Cancel"/> </StackPanel> <Button Margin="0,30,0,0" HorizontalAlignment="Center" Height="30" Width="125" Command="{Binding FilterCommand}" Content="Do a filter" IsEnabled="{Binding ButtonsAreEnabled}"/> <Button Margin="0,30,0,0" HorizontalAlignment="Center" Height="30" Width="125" Command="{Binding AltCommand}" Content="Do something else" IsEnabled="{Binding ButtonsAreEnabled}"/> </StackPanel> </Grid> </Window>
视图模型代码
using System; using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; using System.Windows.Data; using System.Windows.Input; using custAssembliesx64; namespace testProgressFilter { internal class VM : Notifier { private int progressBarMinimum = 0; public int ProgressBarMinimum { get { return progressBarMinimum; } set { if (progressBarMinimum != value) { progressBarMinimum = value; OnPropertyChanged("ProgressBarMinimum"); } } } private int progressBarMaximum = 100; public int ProgressBarMaximum { get { return progressBarMaximum; } set { if (progressBarMaximum != value) { progressBarMaximum = value; OnPropertyChanged("ProgressBarMaximum"); } } } private int progressBarValue; public int ProgressBarValue { get { return progressBarValue; } set { if (progressBarValue != value) { progressBarValue = value; OnPropertyChanged("ProgressBarValue"); } } } private bool buttonsAreEnabled = true; public bool ButtonsAreEnabled { get { return buttonsAreEnabled; } set { if (buttonsAreEnabled != value) { buttonsAreEnabled = value; OnPropertyChanged("ButtonsAreEnabled"); } } } internal static bool IsCancelled = false; ObservableCollection<int> ints = new ObservableCollection<int>(); CollectionViewSource CollectionViewSource = new CollectionViewSource(); const int maxvals = 2000; const int halfMaxvals = maxvals / 2; int ItemsCount; int ItemsCountPercent; int prevItemsCountPercent; const float cent = (float)100 / (float)maxvals; public VM() { CollectionViewSource.Source = ints; // populate the collection for (int i = 0; i < maxvals; i++) { ints.Add(i); } } public ICommand FilterCommand => new RelayCommand(DoFilter); private async void DoFilter(object parm) { // filter the collection, showing progress ButtonsAreEnabled = false; int totalItems = maxvals; var progress = new Progress<int>(percent => { ProgressBarValue = percent; }); await Task.Run(() => DoFilterTask(totalItems, progress)); IsCancelled = false; ButtonsAreEnabled = true; CollectionViewSource.Filter -= FilterCriteria; } private void DoFilterTask(int totalItems, IProgress<int> progress) { System.Windows.Application.Current.Dispatcher.Invoke(() => { CollectionViewSource.Filter += FilterCriteria; }); } private void FilterCriteria(object sender, FilterEventArgs e) { if (IsCancelled) return; int j = (int)e.Item; // REPORT PROGRESS HERE! if (j < halfMaxvals) { e.Accepted = false; } else { e.Accepted = true; } } public ICommand AltCommand => new RelayCommand(DoAlt); private async void DoAlt(object parm) { ButtonsAreEnabled = false; int totalItems = maxvals; ProgressBarMinimum = 0; ProgressBarMaximum = 100; ProgressBarValue = 0; var progress = new Progress<int>(percent => { ProgressBarValue = percent; }); await Task.Run(() => DoAltTask(totalItems, progress)); IsCancelled = false; ButtonsAreEnabled = true; } private void DoAltTask(int totalItems, IProgress<int> progress) { // doing something else with the collection, showing progress ItemsCount = 0; ItemsCountPercent = 0; prevItemsCountPercent = 0; double x = 1; for (int i = 0; i < totalItems; i++) { if (IsCancelled) break; x += i / 1048576; Thread.Sleep(1); ItemsCount++; // report progress when > 1 additional percent is surpassed ItemsCountPercent = (int)(cent * (float)ItemsCount); if (ItemsCountPercent > prevItemsCountPercent) { prevItemsCountPercent = ItemsCountPercent; if (progress != null) { System.Windows.Application.Current.Dispatcher.Invoke(() => { progress.Report(ItemsCountPercent); }); } } } } public ICommand CancelCommand => new RelayCommand(DoCancel); private void DoCancel(object parm) { IsCancelled = true; } } }
解决方案
CollectionViewSource.Filter事件由UI线程触发,且是框架自动遍历集合调用筛选方法,无法直接传入Progress<int>。可以换一种思路:在异步任务中完成筛选逻辑并同步进度,再将结果更新到视图。
修改后的核心代码
private async void DoFilter(object parm) { ButtonsAreEnabled = false; IsCancelled = false; int totalItems = maxvals; var progress = new Progress<int>(percent => { ProgressBarValue = percent; }); var filteredItems = new ObservableCollection<int>(); await Task.Run(() => { ItemsCount = 0; ItemsCountPercent = 0; prevItemsCountPercent = 0; foreach (var item in ints) { if (IsCancelled) break; // 执行筛选逻辑 if (item >= halfMaxvals) { System.Windows.Application.Current.Dispatcher.Invoke(() => filteredItems.Add(item)); } ItemsCount++; ItemsCountPercent = (int)(cent * (float)ItemsCount); if (ItemsCountPercent > prevItemsCountPercent) { prevItemsCountPercent = ItemsCountPercent; progress.Report(ItemsCountPercent); } // 模拟耗时操作,实际场景可移除 Thread.Sleep(1); } }); if (!IsCancelled) { System.Windows.Application.Current.Dispatcher.Invoke(() => { CollectionViewSource.Source = filteredItems; }); } IsCancelled = false; ButtonsAreEnabled = true; }
说明
- 将筛选逻辑移至异步
Task中,直接访问Progress<int>实时报告进度 - 通过
Dispatcher将筛选结果更新到UI线程的集合,避免跨线程异常 - 保留原有的
IsCancelled标志实现取消逻辑 - 无需再订阅
CollectionViewSource.Filter事件,直接替换源集合更高效
内容的提问来源于stack exchange,提问作者Moonling
相关产品推荐
相关产品推荐

