wpf 异步CollectionViewSource过滤与进度报告?

aoyhnmkz  于 2023-04-07  发布在  其他
关注(0)|答案(2)|浏览(97)

异步CollectionViewSource过滤与进度报告?
我正在修改CollectionViewSource上的搜索,从同步到异步,因为集合的典型大小已经大量增加。原因是给予用户进度报告。问题是我没有看到在过滤事件处理程序中引用Progress<int>对象的方法。下面的例子有一个可以工作的异步方法,带有进度报告和Filter方法的框架,显示了我的困惑。I don’我不知道在CollectionViewSource上实现ProgressBar以进行Filter操作的方法。

<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;
        }
    }
}
vvppvyoh

vvppvyoh1#

首先,创建一个后台线程,除了将它的全部工作返回给UI线程(参见你的DoFilterTask方法)之外什么也不做,这是非常无用和浪费资源的。它会产生不必要的开销。
同样适用于这个代码气味:

System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
  progress.Report(ItemsCountPercent);
});

使用IProgress<T>的全部目的是将进度委托的执行返回到原始的调度器线程。那么为什么还要调用Dispatcher:Invoke呢?Dispatcher.Invoke是多余的。在这一点上,我不想提到你的代码的其余部分,没有多大意义。只是必须强调IProgress<T>Dispatcher的错误使用。(尤其是在后台线程的上下文中)。
第二,只有在没有启用UI虚拟化或不支持UI虚拟化的视图(如普通的ItemsControl)中显示数据项,或者加载了太多项时,才会遇到糟糕的过滤性能。
如果我们有这么多项,我们通常从数据库或某种数据服务中获取它们。在这种情况下,您不能筛选视图的源集合,而是查询数据源以获得筛选结果。例如,将工作卸载到服务器或数据库将显著提高性能。
如果您希望在对源代码集合应用重大更改(如过滤)时减少UI的不稳定感,则必须使用支持UI虚拟化的ItemsControl(例如ListBox)。
我也希望Thread.Sleep不在你的产品代码中,因为你为什么要人为地减慢你的算法(然后显示一个进度条)?
但是,由于CollectionViewDispatcher关联性(如果用作Binding.Source),您不能在与CollectionView关联的调度器线程之外的其他线程上操作它。
出于这个原因,您必须创建一个临时的CollectionView,它与UI线程断开连接,以便从后台线程操作它。
在您的情况下,创建一个临时集合会更容易,稍后将其分配给ListBox(或任何其他支持UI虚拟化的ItemsControl)的ItemsSource属性。
下面的示例在过滤视图中的大型源集合时保持UI响应:MainWindow.xaml

<ProgressBar x:Name="ProgressBar" 
             Maximum="{Binding NumericItems.Count}" 
             Height="8" />
<Button Content="Filter Items"
        Click="Button_Click" />
<ListBox ItemsSource="{Binding NumericItems}" 
         Height="500" />

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public ObservableCollection<int> NumericItems
  {
    get => (ObservableCollection<int>)GetValue(NumericItemsProperty);
    set => SetValue(NumericItemsProperty, value);
  }

  public static readonly DependencyProperty NumericItemsProperty = DependencyProperty.Register(
    "NumericItems", 
    typeof(ObservableCollection<int>), 
    typeof(MainWindow), 
    new PropertyMetadata(default));

  public MainWindow()
  {
    InitializeComponent();

    this.DataContext = this;
    this.NumericItems = new ObservableCollection<int>();
    this.Loaded += OnLoaded;
  }

  private void OnLoaded(object sender, RoutedEventArgs e)
  {
    var rndGenerator = new Random();
    int itemCount = 10000
    for (int i = 0; i < itemCount; i++)
    {
      int number = rndGenerator.Next(0, itemCount);
      this.NumericItems.Add(number);
    }
  }

  private async void Button_Click(object sender, RoutedEventArgs e)
  {
    var tempList = this.NumericItems.ToList();
    IProgress<int> progressReporter = new Progress<int>(progressValue => this.ProgressBar.Value = progressValue);
    int processedItemCount = 0;
    await Task.Run(() =>
    {
      for (int index = tempList.Count - 1; index >= 0; index--)
      {
        int value = tempList[index];
        if (!IsItemIncluded(value))
        {
          tempList.RemoveAt(index);
        }

        progressReporter.Report(++processedItemCount);
      }
    });

    // If the data view is not using UI virtualization, 
    // this operation will cause the UI to hang until the data view is initialized.
    this.NumericItems = new ObservableCollection<int>(tempList);
  }

  private bool IsItemIncluded(int item) => item % 2 == 0;
}
ctrmrzij

ctrmrzij2#

第一个解决方案非常好
另一种仅使用调度器的解决方案:
1.在XAML上的进度栏定义中创建加载的事件(称为ProgressBarLoaded)
在视图模型中使用它

public void ProgressBarLoaded(object sender, RoutedEventArgs e)
    {
        bar = sender as ProgressBar;
        ItemsCount = 0;
        ItemsCountPercent = 0;
    }

并在FilterCriteria中更新值:

private void FilterCriteria(object sender, FilterEventArgs e)
    {
        if (IsCancelled) return;
        int j = (int)e.Item;

        if (j < halfMaxvals)
        {
            e.Accepted = false;
        }
        else
        {
            e.Accepted = true;
        }

        ItemsCountPercent = ++ItemsCount * 100 / maxvals;
        bar.Dispatcher.Invoke(() => bar.Value = ItemsCountPercent, DispatcherPriority.Background);
    }

无需定义绑定到ProgressBarValue..
在这个示例中,我在过滤集合的过程中使用了进度条

相关问题