填充动态DataGrid时,BackgroundWorker无法阻止ProgressBar冻结

1-将以下代码复制并粘贴到 MainWindow.xaml 文件中。

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
    <DataGrid x:Name="DataGrid1"/>
</Grid>
</Window>

2-将以下代码复制并粘贴到 MainWindow.xaml.cs 文件中。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Threading;

namespace WpfApplication1
{

public partial class MainWindow : Window
{
    BackgroundWorker BackgroundWorker1 = new BackgroundWorker();
    BackgroundWorker BackgroundWorker2 = new BackgroundWorker();
    System.Data.DataTable DataTable1 = new System.Data.DataTable();

    public MainWindow()
    {
        InitializeComponent();
        BackgroundWorker1.DoWork += BackgroundWorker1_DoWork;
        BackgroundWorker2.DoWork += BackgroundWorker2_DoWork;
    }

    void Window_Loaded(object sender,RoutedEventArgs e)
    {
        BackgroundWorker1.RunWorkerAsync();
        BackgroundWorker2.RunWorkerAsync();
    }

    private void BackgroundWorker1_DoWork(System.Object sender,System.ComponentModel.DoWorkEventArgs e)
    {
        Dispatcher.Invoke(() =>
        {
            Window1 myWindow1 = new Window1();
            myWindow1.ShowDialog();
        });
    }

    private void BackgroundWorker2_DoWork(System.Object sender,System.ComponentModel.DoWorkEventArgs e)
    {
        for (int i = 1; i <= 7; i++)
            DataTable1.Columns.Add();
        for (int i = 1; i <= 1048576; i++)
            DataTable1.Rows.Add(i);
        Dispatcher.Invoke(() =>
        {
            DataGrid1.ItemsSource = DataTable1.DefaultView;
        });
    }

}
}

3-创建一个新窗口,并将其命名为 Window1

4-将以下代码复制并粘贴到 Window1.xaml 文件中。

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="1000" ContentRendered="Window_ContentRendered">
<Grid>
    <ProgressBar x:Name="ProgressBar1" Height="25" Width="850"/>
</Grid>
</Window>

5-将以下代码复制并粘贴到 Window1.xaml.cs 文件中。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace WpfApplication1
{
public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
    }

    private void Window_ContentRendered(object sender,EventArgs e)
    {
        ProgressBar1.IsIndeterminate = true;
    }
}
}

6-当您运行此项目时,您会看到 ProgressBar1 冻结了两三秒,而下一行的运行是由于向DataGrid添加了1048576行。 (大行)

DataGrid1.ItemsSource = DataTable1.DefaultView;

我不希望 ProgressBar1 被冻结。

那为什么 BackgroundWorker 无法阻止 ProgressBar 冻结?

shangxinde86 回答:填充动态DataGrid时,BackgroundWorker无法阻止ProgressBar冻结

尝试首先将项目放入ObservableCollection中。暂停要简短得多。它不知道您可以完全消除它,因为网格需要绑定到UI线程上。

private void BackgroundWorker2_DoWork(System.Object sender,System.ComponentModel.DoWorkEventArgs e)
{
    for (int i = 1; i <= 7; i++)
        DataTable1.Columns.Add();
    for (int i = 1; i <= 1048576; i++)
        DataTable1.Rows.Add(i);
    var col = new ObservableCollection<MyItem>();
    foreach (DataRow row in DataTable1.Rows) col.Add(new MyItem(row));
    Dispatcher.Invoke(() =>
    {
        DataGrid1.ItemsSource = col;
    });
}

public class MyItem
{
    public MyItem() { }
    public MyItem(DataRow row)
    {
        int.TryParse(row[0].ToString(),out int item1);
        int.TryParse(row[1].ToString(),out int item2);
        int.TryParse(row[2].ToString(),out int item3);
        int.TryParse(row[3].ToString(),out int item4);
        int.TryParse(row[4].ToString(),out int item5);
        int.TryParse(row[5].ToString(),out int item6);
        int.TryParse(row[6].ToString(),out int item7);
    }
    public int item1 { get; set; }
    public int item2 { get; set; }
    public int item3 { get; set; }
    public int item4 { get; set; }
    public int item5 { get; set; }
    public int item6 { get; set; }
    public int item7 { get; set; }
}
,

当唯一的一个UI线程正忙于将数据绑定到DataGrid时,UI似乎冻结。除了避免绑定大量数据或使用数据virtualization外,没有什么要修复的。但是,您仍然可以通过使事情异步来优化此代码。

private Task<DataView> GetDataAsync()
    {
        return Task.Run(() =>
        {
            for (int i = 1; i <= 7; i++)
                DataTable1.Columns.Add();
            for (int i = 1; i <= 1048576; i++)
                DataTable1.Rows.Add(i);
            return DataTable1.DefaultView;
        });
    }

 private  void BackgroundWorker2_DoWork(System.Object sender,System.ComponentModel.DoWorkEventArgs e)
    {
        Dispatcher.Invoke((Action)(async () =>
        {
            DataGrid1.ItemsSource = await GetDataAsync();
        }));

    }
,

在您的情况下,我认为问题是在GUI线程上生成了DefaultView。将其移至BG-Worker:

private void BackgroundWorker2_DoWork(System.Object sender,System.ComponentModel.DoWorkEventArgs e)
{
    for (int i = 1; i <= 7; i++)
        DataTable1.Columns.Add();
    for (int i = 1; i <= 1048576; i++)
        DataTable1.Rows.Add(i);

    var dv = DataTable1.DefaultView; //generating the default view takes ~ 2-3 sec.

    Dispatcher.Invoke(() =>
    {
        DataGrid1.ItemsSource = dv;
    });
}

不要忘记在XAML中为EnableRowVirtualization="True"设置MaxHeightDataGrid

,

您的方法存在的问题是stackNavigation没有实现DataTable。因此,添加一行不会更新视图(更准确地说,绑定)。要强制刷新,您必须在每次添加行时重置INotifyPropertyChanged或在创建所有 n 行之后将其重置。

这会导致一个UI线程忙于一次绘制例如1,048,576行* 7列-没有资源来绘制ItemsSource,它将冻结。
在无法容忍冻结时间的情况下,在处理大量数据时,ProgressBar就是一个错误的选择。

解决方案是选择一个数据源,该数据源允许逐行添加数据,而不必强制重画整个视图。

只要启用了虚拟化(默认情况下,对于DataTable而言,以下解决方案就可以完全消除冻结,并且列数不超过关键计数(虚拟化仅适用于行而不适用于列) ):

解决方案1 ​​

DataGrid仅允许绘制新行/更改过的行。它将引发ObservableCollection,这会触发INotifyCollectionChanged.CollectionChanged仅添加/删除/移动更改的项目:

MainWindow.xaml

DataGrid

MainWindow.xaml.cs

<Window>
  <StackPanel>
    <DataGrid x:Name="DataGrid1" 
              AutoGenerateColumns="False" 
              ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType=local:MainWindow},Path=DataTableRowCollection}"/>
  </StackPanel>
</Window>

RowItem.cs

public ObservableCollection<RowItem> DataTableRowCollection { get; } = new ObservableCollection<RowItem>();

private async void BackgroundWorker2_DoWork(System.Object sender,System.ComponentModel.DoWorkEventArgs e)
{
  for (int i = 1; i <= 1048576; i++)
  {
    // Use Dispatcher because 
    // INotifyCollectionChanged.CollectionChanged is not raised on the UI thread 
    // (opposed to INotifyPropertyChanged.PropertyChanged)
    await Application.Current.Dispatcher.InvokeAsync(
      () => this.DataTableRowCollection.Add(new RowItem(i)),DispatcherPriority.Background);
  }
}

注意

缺点是列数与数据模型有关。无法在运行时向public class RowItem { public RowItem(int value) { this.Value = value; } public int Value { get; set; } } 添加列,除非您还在运行时动态创建类型(使用反射)或使用嵌套的数据集合来表示列。
但是,添加列将始终导致重新绘制整个表(最好是重新添加列的所有新单元格),除非使用了虚拟化。


解决方案2

如果需要动态列数,则可以使用封装在DataGrid扩展类或托管控件背后的代码中的C#直接处理DataGrid。但是,您绝对不应该在视图模型中处理列或行容器。

这个想法是将DataGrid元素手动添加到DataGridColumn集合中。以下示例一次仅绘制新添加列的所有单元格。

下面的示例使用DataGrid.Columns每次按下时动态添加新列(在Button被初始化为1,576行之后):

MainWindow.xaml

DataGrid

MainWindow.xaml.cs

<Window>
  <StackPanel>
    <Button Content="Add Column" Click="AddColumn_OnClick"/>
    <DataGrid x:Name="DataGrid1" 
              AutoGenerateColumns="False" 
              ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,Path=DataTableRowCollection}"/>
  </StackPanel>
</Window>

RowItem.cs

private async void BackgroundWorker2_DoWork(System.Object sender,System.ComponentModel.DoWorkEventArgs e)
{
  // Create 7 columns in the view
  for (int columnIndex = 0; columnIndex < 7; columnIndex++)
  {
    await Application.Current.Dispatcher.InvokeAsync(
      () =>
      {
        var textColumn = new DataGridTextColumn
        {
          Header = $"Column {columnIndex + 1}",Binding = new Binding($"ColumnItems[{columnIndex}].Value")
        };
        this.DataGrid1.Columns.Add(textColumn);
      },DispatcherPriority.Background);
  }

  // Create the data models for 1,576 rows with 7 columns
  for (int rowCount = 0; rowCount < 1048576; rowCount++)
  {
    int count = rowCount;
    await Application.Current.Dispatcher.InvokeAsync(() =>
    {
      var rowItem = new RowItem();
      for (; count < 7 + rowCount; count ++)
      {
        rowItem.ColumnItems.Add(new ColumnItem((int) Math.Pow(2,count)));
      }
      this.DataTableRowCollection.Add(rowItem);
    },DispatcherPriority.Background);
  }
}

private void AddColumn_OnClick(object sender,RoutedEventArgs e)
{
  int newColumnIndex = this.DataTableRowCollection.First().ColumnItems.Count;
  this.DataGrid1.Columns.Add(
    new DataGridTextColumn()
    {
      Header = $"Dynamically Added Column {newColumnIndex}",Binding = new Binding($"ColumnItems[{newColumnIndex}].Value")
    });

  int rowCount = 0;

  // Add a new column data model to each row data model
  foreach (RowItem rowItem in this.DataTableRowCollection)
  {
    var columnItem = new ColumnItem((int) Math.Pow(2,newColumnIndex + rowCount++);
    rowItem.ColumnItems.Add(columnItem);
  }
}

ColumnItem.cs

public class RowItem
{
  public RowItem()
  {
    this.ColumnItems = new ObservableCollection<ColumnItem>();
  }
  public ObservableCollection<ColumnItem> ColumnItems { get; }
}
本文链接:https://www.f2er.com/3106283.html

大家都在问