如何提高WPF网格控件(.NET 4.0 / 4.5)的性能?

时间:2013-05-20 22:18:15

标签: wpf wpf-controls wpfdatagrid wpf-4.0 wpf-4.5

定义:使用字符串的2D数组(大约10列,1,600行,固定长度为7-char)作为WPF .NET 4.0网格控件的数据源,以下代码片段已被用于使用显示数组值的标签填充网格。 注意:网格被添加到XAML并传递给PopulateGrid函数(参见清单1)。视觉输出本质上是只读模式下的表格数据表示(不需要双向绑定)。

问题:性能是一个关键问题。在功能强大的Intel-i3 / 8GB-DDR3 PC上完成此操作需要3到5秒才能完成令人难以置信的操作。因此,基于与例如类似的控制/任务的比较,该WPF网格性能,恕我直言,至少比预期慢一个数量级。常规的WinForm数据感知控件,甚至Excel工作表。

问题1 :如果有办法在上述方案中提高 WPF网格的性能?请将您的答案/潜在改进指向清单1和清单2中提供的代码段。

问题1a :建议的解决方案可以实现数据绑定到其他数据感知控制,例如DataGridDataTable。我在清单2中已将string[,]添加到DataTable dt转换器,因此其他控件的DataContext(或ItemsSource,无论如何)属性可以绑定到dt.DefaultView。因此,在最简单的形式中,您是否可以提供一个紧凑的(希望在旧式数据感知控件中完成几行代码)和WPF 数据绑定的高效(性能)解决方案DataGridDataTable对象

非常感谢。

清单1 。从2D Grid GridOut

填充WPF string[,] Values的过程
#region Populate grid with 2D-array values
/// <summary>
/// Populate grid with 2D-array values
/// </summary>
/// <param name="Values">string[,]</param>
/// <param name="GridOut">Grid</param>
private void PopulateGrid(string[,] Values, Grid GridOut)
{
    try
    {
        #region clear grid, then add ColumnDefinitions/RowsDefinitions

        GridOut.Children.Clear();
        GridOut.ColumnDefinitions.Clear();
        GridOut.RowDefinitions.Clear();

        // get column num
        int _columns = Values.GetUpperBound(1) + 1;

        // add ColumnDefinitions
        for (int i = 0; i < _columns; i++)
        {
            GridOut.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
        }

        // get rows num
        int _rows = Values.GetUpperBound(0) + 1;

        // add RowDefinitions
        for (int i = 0; i < _rows; i++)
        {
            GridOut.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
        }
        #endregion

        #region populate grid w/labels
        // populate grid w/labels
        for (int i = 0; i < _rows; i++)
        {
            for (int j = 0; j < _columns; j++)
            {
                // new Label control
                Label _lblValue = new Label();

                // assign value to Label
                _lblValue.Content = Values[i, j].ToString();

                // add Label to GRid
                GridOut.Children.Add(_lblValue);
                Grid.SetRow(_lblValue, i);
                Grid.SetColumn(_lblValue, j);
            }
        }
        #endregion
    }
    catch
    {
        GridOut.Children.Clear();
        GridOut.ColumnDefinitions.Clear();
        GridOut.RowDefinitions.Clear();
    }
}
#endregion

清单2 string[,]转换为DataTable

#region internal: Convert string[,] to DataTable
/// <summary>
/// Convert string[,] to DataTable
/// </summary>
/// <param name="arrString">string[,]</param>
/// <returns>DataTable</returns>
internal static DataTable Array2DataTable(string[,] arrString)
{
    DataTable _dt = new DataTable();
    try
    {
        // get column num
        int _columns = arrString.GetUpperBound(1) + 1;

        // get rows num
        int _rows = arrString.GetUpperBound(0) + 1;

        // add columns to DataTable
        for (int i = 0; i < _columns; i++)
        {
            _dt.Columns.Add(i.ToString(), typeof(string));
        }

        // add rows to DataTable
        for (int i = 0; i < _rows; i++)
        {
            DataRow _dr = _dt.NewRow();
            for (int j = 0; j < _columns; j++)
            {
                _dr[j] = arrString[i,j];
            }
            _dt.Rows.Add(_dr);
        }
        return _dt;
    }
    catch { throw; }
}
#endregion

注2 。建议使用Text属性替换Label w / TextBlock,而不是Label。它会加快执行速度,加上代码片段将与VS 2012 for Win 8向前兼容,其中不包括Label

注3 :到目前为止,我已尝试将DataGrid绑定到DataTable(请参阅清单3中的XAML),但性能非常差(grdOut是一个嵌套的Grid,用作表格数据的容器; _ dataGridDataGrid的数据感知对象类型。

清单3 DataGrid绑定到DataTable:性能很差,所以我删除了ScrollViewer,而不是正常运行。

<ScrollViewer ScrollViewer.CanContentScroll="True" VerticalScrollBarVisibility="Auto" >
    <Grid Name="grdOut">
            <DataGrid AutoGenerateColumns="True" Name="_dataGrid" ItemsSource="{Binding Path=.}" />
    </Grid>
</ScrollViewer>

1 个答案:

答案 0 :(得分:4)

确定。删除所有代码并从头开始。

这是我对Labels的“动态网格”的看法,其中X行数和Y列数基于2D字符串数组:

<Window x:Class="MiscSamples.LabelsGrid"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="LabelsGrid" Height="300" Width="300">
    <DockPanel>

        <Button DockPanel.Dock="Top" Content="Fill" Click="Fill"/>

        <ItemsControl ItemsSource="{Binding Items}"
                      ScrollViewer.HorizontalScrollBarVisibility="Auto"
                      ScrollViewer.VerticalScrollBarVisibility="Auto"
                      ScrollViewer.CanContentScroll="true"
                      ScrollViewer.PanningMode="Both">
            <ItemsControl.Template>
                <ControlTemplate>
                    <ScrollViewer>
                        <ItemsPresenter/>
                    </ScrollViewer>
                </ControlTemplate>
            </ItemsControl.Template>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <ItemsControl ItemsSource="{Binding Items}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Label Content="{Binding}"/>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <UniformGrid Rows="1"/>
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                    </ItemsControl>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel VirtualizationMode="Recycling" IsVirtualizing="True"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </DockPanel>
</Window>

代码背后:

public partial class LabelsGrid : Window
{
    private LabelsGridViewModel ViewModel { get; set; }

    public LabelsGrid()
    {
        InitializeComponent();
        DataContext = ViewModel = new LabelsGridViewModel();
    }

    private void Fill(object sender, RoutedEventArgs e)
    {
        var array = new string[1600,20];

        for (int i = 0; i < 1600; i++)
        {
            for (int j = 0; j < 20; j++)
            {
                array[i, j] = "Item" + i + "-" + j;
            }
        }

        ViewModel.PopulateGrid(array);
    }
}

视图模型:

public class LabelsGridViewModel: PropertyChangedBase
{
    public ObservableCollection<LabelGridItem> Items { get; set; } 

    public LabelsGridViewModel()
    {
        Items = new ObservableCollection<LabelGridItem>();
    }

    public void PopulateGrid(string[,] values)
    {
        Items.Clear();

        var cols = values.GetUpperBound(1) + 1;
        int rows = values.GetUpperBound(0) + 1;

        for (int i = 0; i < rows; i++)
        {
            var item = new LabelGridItem();

            for (int j = 0; j < cols; j++)
            {
                item.Items.Add(values[i, j]);
            }

            Items.Add(item);
        }
    }
}

数据项:

public class LabelGridItem: PropertyChangedBase
{
    public ObservableCollection<string> Items { get; set; }

    public LabelGridItem()
    {
        Items = new ObservableCollection<string>();
    }
}

PropertyChangedBase类(MVVM Helper)

public class PropertyChangedBase:INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        Application.Current.Dispatcher.BeginInvoke((Action) (() =>
                                                                 {
                                                                     PropertyChangedEventHandler handler = PropertyChanged;
                                                                     if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
                                                                 }));
    }
}

结果:

enter image description here

  • 表现真棒。请注意,我使用的是20列,而不是您建议的10列。单击按钮时,网格填充即为立即。由于内置的​​UI虚拟化,我确信性能比蹩脚的恐龙胜利要好得多。

  • UI是在XAML中定义的,而不是在过程代码中创建UI元素,这是一种不好的做法。

  • UI和数据保持独立,从而提高了可维护性,可扩展性和清洁度。

  • 将我的代码复制并粘贴到File -> New -> WPF Application中,然后自行查看结果。

  • 另外,请注意,如果您只是要显示文字,最好使用TextBlock而不是Label,这是一个非常轻量级的文本元素。

  • WPF摇摆不定,即使在边缘情况下它可能会出现性能下降,它仍然比目前存在的任何东西都好12837091723.

修改

我继续向行计数(160000)添加0个零。表现仍然可以接受。填充Grid需要不到1秒的时间。

请注意,我的示例中没有虚拟化“列”。如果存在大量问题,这可能会导致性能问题,但这不是您所描述的。

<强> EDIT2:

根据您的评论和澄清,我做了一个新的例子,这次是基于System.Data.DataTable。没有ObservableCollections,没有异步的东西(在我之前的例子中没有任何异步)。只有10列。由于窗口太小(Width="300")并且不足以显示数据,因此存在水平滚动条。 WPF与分辨率无关,与恐龙框架不同,它在需要时显示滚动条,但也将内容扩展到可用空间(您可以通过调整窗口大小等来看到这一点)。

我还将数组初始化代码放在Window的构造函数中(以处理INotifyPropertyChanged的缺失),因此加载和显示它需要更多一点,我注意到这个示例使用{{ 1}}比前一个稍慢。

但是,我必须警告你Binding to Non-INotifyPropertyChanged objects may cause a Memory Leak

但是,您将无法使用简单的System.Data.DataTable控件,因为它不会执行UI虚拟化。如果你想要一个虚拟化网格,你必须自己实现它。

您也无法使用winforms方法。它在WPF中简直无关紧要,毫无用处。

Grid

代码背后:

    <ItemsControl ItemsSource="{Binding Rows}"
                  ScrollViewer.HorizontalScrollBarVisibility="Auto"
                  ScrollViewer.VerticalScrollBarVisibility="Auto"
                  ScrollViewer.CanContentScroll="true"
                  ScrollViewer.PanningMode="Both">
        <ItemsControl.Template>
            <ControlTemplate>
                <ScrollViewer>
                    <ItemsPresenter/>
                </ScrollViewer>
            </ControlTemplate>
        </ItemsControl.Template>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <ItemsControl ItemsSource="{Binding ItemArray}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding}"/>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <UniformGrid Rows="1"/>
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                </ItemsControl>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel VirtualizationMode="Recycling" IsVirtualizing="True"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>

底线是在WPF中做一些事情你必须以WPF的方式做。它不仅仅是一个UI框架,它本身更像是一个应用程序框架。

<强> EDIT3

public partial class LabelsGrid : Window
{
    public LabelsGrid()
    {
        var array = new string[160000, 10];

        for (int i = 0; i < 160000; i++)
        {
            for (int j = 0; j < 10; j++)
            {
                array[i, j] = "Item" + i + "-" + j;
            }
        }

        DataContext = Array2DataTable(array);
        InitializeComponent();
    }

    internal static DataTable Array2DataTable(string[,] arrString)
    {
        //... Your same exact code here
    }
}

对我来说效果很好。 160000行的加载时间不明显。您正在使用什么.Net框架版本?