You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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;
}

说明

  1. 将筛选逻辑移至异步Task中,直接访问Progress<int>实时报告进度
  2. 通过Dispatcher将筛选结果更新到UI线程的集合,避免跨线程异常
  3. 保留原有的IsCancelled标志实现取消逻辑
  4. 无需再订阅CollectionViewSource.Filter事件,直接替换源集合更高效

内容的提问来源于stack exchange,提问作者Moonling

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 22:25:02