Poor Performance when Sorting on WPF DataGrid without Virtualization
We have a simple WPF demo app with 1000 rows and 32 columns (see below for code).
We've done nothing special with it other than disable virtualization, which our users require as otherwise the scrolling is too sluggish (they do this all day long on large data sets, so responsiveness/snappiness is important for them).
The problem we have is if you click on one of the headers to sort the data, this will take ~ 20 seconds (on a 2x3GHz Core 2 Duo machine here). Is there any way to speed this up?
It seems as if it's reconstructing the entire visual tree when sorting, and it seems unnecessary. Any pointers to how to speed up this specific situation will be appreciated, even if it goes as far as compiling our own version of the grid.
Thanks.
<Window x:Class="WpfGridTest1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
开发者_如何学Goxmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid Name="dataGrid" VirtualizingStackPanel.IsVirtualizing="False">
</DataGrid>
</Grid>
</Window>
using System.Collections.Generic;
namespace WpfGridTest1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
List<Row> rows = new List<Row>();
for (int i = 0; i < 1000; i++)
{
Row row = new Row
{
Column0 = i,
Column1 = i,
Column2 = i,
Column3 = i,
Column4 = i,
Column5 = i,
Column6 = i,
Column7 = i,
Column8 = i,
Column9 = i,
Column10 = i,
Column11 = i,
Column12 = i,
Column13 = i,
Column14 = i,
Column15 = i,
Column16 = i,
Column17 = i,
Column18 = i,
Column19 = i,
Column20 = i,
Column21 = i,
Column22 = i,
Column23 = i,
Column24 = i,
Column25 = i,
Column26 = i,
Column27 = i,
Column28 = i,
Column29 = i,
Column30 = i,
Column31 = i
};
rows.Add(row);
}
dataGrid.ItemsSource = rows;
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace WpfGridTest1
{
class Row : INotifyPropertyChanged
{
private double column0 ;
private double column1 ;
private double column2 ;
private double column3 ;
private double column4 ;
private double column5 ;
private double column6 ;
private double column7 ;
private double column8 ;
private double column9 ;
private double column10;
private double column11;
private double column12;
private double column13;
private double column14;
private double column15;
private double column16;
private double column17;
private double column18;
private double column19;
private double column20;
private double column21;
private double column22;
private double column23;
private double column24;
private double column25;
private double column26;
private double column27;
private double column28;
private double column29;
private double column30;
private double column31;
public double Column0 { get { return column0 ; } set { column0 = value; NotifyPropertyChanged("Column0 "); } }
public double Column1 { get { return column1 ; } set { column1 = value; NotifyPropertyChanged("Column1 "); } }
public double Column2 { get { return column2 ; } set { column2 = value; NotifyPropertyChanged("Column2 "); } }
public double Column3 { get { return column3 ; } set { column3 = value; NotifyPropertyChanged("Column3 "); } }
public double Column4 { get { return column4 ; } set { column4 = value; NotifyPropertyChanged("Column4 "); } }
public double Column5 { get { return column5 ; } set { column5 = value; NotifyPropertyChanged("Column5 "); } }
public double Column6 { get { return column6 ; } set { column6 = value; NotifyPropertyChanged("Column6 "); } }
public double Column7 { get { return column7 ; } set { column7 = value; NotifyPropertyChanged("Column7 "); } }
public double Column8 { get { return column8 ; } set { column8 = value; NotifyPropertyChanged("Column8 "); } }
public double Column9 { get { return column9 ; } set { column9 = value; NotifyPropertyChanged("Column9 "); } }
public double Column10 { get { return column10; } set { column10 = value; NotifyPropertyChanged("Column10"); } }
public double Column11 { get { return column11; } set { column11 = value; NotifyPropertyChanged("Column11"); } }
public double Column12 { get { return column12; } set { column12 = value; NotifyPropertyChanged("Column12"); } }
public double Column13 { get { return column13; } set { column13 = value; NotifyPropertyChanged("Column13"); } }
public double Column14 { get { return column14; } set { column14 = value; NotifyPropertyChanged("Column14"); } }
public double Column15 { get { return column15; } set { column15 = value; NotifyPropertyChanged("Column15"); } }
public double Column16 { get { return column16; } set { column16 = value; NotifyPropertyChanged("Column16"); } }
public double Column17 { get { return column17; } set { column17 = value; NotifyPropertyChanged("Column17"); } }
public double Column18 { get { return column18; } set { column18 = value; NotifyPropertyChanged("Column18"); } }
public double Column19 { get { return column19; } set { column19 = value; NotifyPropertyChanged("Column19"); } }
public double Column20 { get { return column20; } set { column20 = value; NotifyPropertyChanged("Column20"); } }
public double Column21 { get { return column21; } set { column21 = value; NotifyPropertyChanged("Column21"); } }
public double Column22 { get { return column22; } set { column22 = value; NotifyPropertyChanged("Column22"); } }
public double Column23 { get { return column23; } set { column23 = value; NotifyPropertyChanged("Column23"); } }
public double Column24 { get { return column24; } set { column24 = value; NotifyPropertyChanged("Column24"); } }
public double Column25 { get { return column25; } set { column25 = value; NotifyPropertyChanged("Column25"); } }
public double Column26 { get { return column26; } set { column26 = value; NotifyPropertyChanged("Column26"); } }
public double Column27 { get { return column27; } set { column27 = value; NotifyPropertyChanged("Column27"); } }
public double Column28 { get { return column28; } set { column28 = value; NotifyPropertyChanged("Column28"); } }
public double Column29 { get { return column29; } set { column29 = value; NotifyPropertyChanged("Column29"); } }
public double Column30 { get { return column30; } set { column30 = value; NotifyPropertyChanged("Column30"); } }
public double Column31 { get { return column31; } set { column31 = value; NotifyPropertyChanged("Column31"); } }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
UPDATE: I have tried AngelWPF's suggestion as follows:
private void dataGrid_Sorting(object sender, System.Windows.Controls.DataGridSortingEventArgs e)
{
e.Handled = true;
IQueryable<Row> iqueryable = _rows.AsQueryable();
var v = iqueryable.OrderBy(row => row.Column0);
foreach (Row row in v)
System.Diagnostics.Debug.WriteLine("Row " + row.Column0);
_rows = new ObservableCollection<Row>(v.ToList());
dataGrid.ItemsSource = _rows;
}
Although the performance problem still exists, as it's rebuilding the grid.
Without virtualization, there is no chance of sorting performance to improve!
Why to loose virtualization? The problem of sluggish datagrid scrolling can be addressed ... like these posts may help... WPF Datagrid Performance, Slow and Stuttery WPF Grid Scrolling when loaded with large amounts of data (40 columns, 2000 rows), http://www.codeproject.com/KB/WPF/WpfDataVirtualization.aspx.
Having said that there is one way to improve the sorting on a non-virtualized data grid.
- Handle the
DataGrid.Sorting
event and sete.Handled = true
in its handler. This way datagrid wont perform the sorting. - In the above handler by looking at the column name you will know the property that the column is representing or is bound to. So use that property name and use LINQ's AsQueryable() interface to sort. This will be fastest way to sort the
ItemsSource
of the datagrid. - Set the sorted collection back to the
ItemsSource
.
We tried the above approach for a readonly non-virtualized datagrid that displayed variety of colors, effects and 30000 rows of 30 columns, and the result was astounding.
Queryable LINQ can do wonders!
I had a problem with the DataGrid in which it took literally seconds to refresh after a column sort, resize etc. and locked up the window UI while it was doing so (1000 rows, 5 columns).
It came down to an issue with the WPF sizing calculations. I had it in a grid with the RowDefinition Height="Auto" which was causing the rendering system to try and recalculate the size of the DataGrid at runtime by measuring the size of each and every column and row, presumably by filling the whole grid (as I understand it). It is supposed to handle this intelligently somehow but in this case it was not.
A quick check to see if this is a related problem is to set the Height and Width properties of the DataGrid to a fixed size for the duration of the test, and try running again. If your performance is restored, a permanent fix may be among these options:
- Change the sizes of the containing elements to be relative (*) or fixed values
- Set MaxHeight and MaxWidth of the DataGrid to a fixed value larger than it could get in normal use
- Try another container type with different resizing strategy (Grid, DockPanel, etc)
From the updated section:
foreach (Row row in v)
System.Diagnostics.Debug.WriteLine("Row " + row.Column0);
_rows = new ObservableCollection<Row>(v.ToList());
Why don't you disable the WriteLine? That can't be good from a performance point of view. Additionally, is there a reason you are re-initializing the _rows variable to a new ObservableCollection? If possible, convert _rows to a property and then raise the NotifyPropertyChanged event when change the value.
Of course, you'd also have to change your data binding for the grid to this new property.
精彩评论