如何提高WPF网格控件(.NET 4.0 / 4.5)的性能? [英] How to improve performance of WPF Grid control (.NET 4.0/4.5)?

查看:108
本文介绍了如何提高WPF网格控件(.NET 4.0 / 4.5)的性能?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Definition: Having 2D-array of string (about 10 columns, 1,600 rows, fixed length of 7-char) serving as a data source for WPF .NET 4.0 Grid control, the following code snippet has been used in order to populate the Grid with labels displaying values from array. Note: Grid was added to XAML and passed to the function PopulateGrid (see Listing 1.). The visual output is essentially a tabular data representation in read-only mode (no need for two-way binding).

问题:性能为一个关键问题。完成此操作花费了令人难以置信的3 ... 5秒钟,以在功能强大的Intel-i3 / 8GB-DDR3 PC上运行;因此,基于与WPF Grid中类似控件/任务的比较,该WPF Grid性能比预期慢了至少一个数量级。常规的WinForm数据感知控件,甚至Excel工作表。

Problem: Performance is a key issue. It took a mind-boggling 3...5 sec to complete this operation running on powerful Intel-i3/8GB-DDR3 PC; therefore, this WPF Grid performance is, IMHO, at least an order of magnitude slower than expected, based on comparison with similar controls/tasks in, e.g. regular WinForm data-aware controls, or even Excel worksheet.

问题1 :是否有方法可以改善在上述情况下是WPF网格?请将您的答案/可能的改进指向清单1和清单2中下面提供的代码段。

Question 1: if there is a way to improve the performance of WPF Grid in scenario described above? Please direct your answer/potential improvement to the code snippet provided below in Listing 1 and Listing 2.

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

Question 1a: proposed solution could implement data binding to additional data-aware control, like for example DataGrid to DataTable. I've added string[,] to DataTable dt converter in Listing 2, so that additional control's DataContext (or ItemsSource, whatever) property could be bound to dt.DefaultView. So, in the simplest form, could you please provide a compact (desirably about couple lines of code as it was done in old-style data-aware controls) and efficient (performance-wise) solution on data-binding of WPF DataGrid to DataTable object ?

非常感谢。

清单1 。从2D string [,]值

#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属性代替Content替换 Label 控件w / TextBlock ,以防标签。它将加快执行速度,并且代码片段将与Win 2012的VS 2012向前兼容,其中不包括 Label

Note 2. It's recommended to replace Label control w/TextBlock using its Text property instead of Content as in case of Label. It will speed up the execution a little bit, plus the code snippet will be forward compatible with VS 2012 for Win 8, which doesn't include Label.

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

Note 3: So far I've tried binding DataGrid to DataTable (see XAML in Listing 3), but performance is very poor (grdOut is a nested Grid, that was used as a container for tabular data; _dataGrid is a data-aware object type of DataGrid).

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

Listing 3. DataGrid binding to DataTable: performance was poor, so I've removed that ScrollViewer and not it's running OK.

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


推荐答案

好。删除所有代码并重新开始。

Ok. Delete all your code and start all over.

这是我对 Label 标签的动态网格的看法X的行数和Y的列数基于2D字符串数组:

This is my take on a "Dynamic Grid" of Labels with X number of rows and Y number of columns based off a 2D string array:

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

ViewModel:

ViewModel:

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帮助器)

PropertyChangedBase class (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));
                                                                 }));
    }
}

结果:


  • 性能超赞。请注意,我使用的是20列,而不是您建议的10列。当您单击按钮时,网格的填充立即。我确信由于内置的​​UI虚拟化,性能比笨拙的恐龙winform好得多。

  • Performance is AWESOME. Notice I'm using 20 columns instead of the 10 you suggested. The Filling of the grid is IMMEDIATE when you click the button. I'm sure performance is much better than crappy dinosaur winforms due to Built-in UI Virtualization.

UI是用XAML定义的,而不是创建UI程序代码中的元素,这是一个坏习惯。

The UI is defined in XAML, as opposed to creating UI elements in procedural code, which is a bad practice.

UI和数据保持分离,从而提高了可维护性,可伸缩性和整洁性。

The UI and data are kept separate, thus increasing maintainability and scalability and cleanliness.

将我的代码复制并粘贴到 File->新-> WPF应用程序,然后亲自查看结果。

Copy and paste my code in a File -> New -> WPF Application and see the results for yourself.

此外,请记住,如果您仅要显示文本,您最好使用 TextBlock 代替 Label ,这是一个轻量级的Text元素。

Also, keep in mind that if you're only going to display text, you'd better use a TextBlock instead of a Label, which is a much lightweight Text element.

WPF岩石,即使在边缘情况下可能会导致性能下降,但仍然比当前存在的性能好12837091723。

WPF rocks, even if at edge cases it might present performance degradation, it's still 12837091723 better than anything currently in existence.

编辑:

我继续往行计数中添加了0个零(160000)。性能仍然可以接受。填充网格花费了不到1秒的时间。

I went ahead and added 0 zeros to the row count (160000). Performance is still acceptable. It took less than 1 second to populate the Grid.

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

Notice that the "Columns" are NOT being virtualized in my example. This can lead to performance issues if there's a big number of them, but that's not what you described.

Edit2:

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

Based on your comments and clarifications, I made a new example, this time based in a System.Data.DataTable. No ObservableCollections, no async stuff (there was nothing async in my previous example anyways). And just 10 columns. Horizontal Scrollbar was there due to the fact that the window was too small (Width="300") and was not enough to show the data. WPF is resolution independent, unlike dinosaur frameworks, and it shows scrollbars when needed, but also stretches the content to the available space (you can see this by resizing the window, etc).

我还将数组初始化代码放在Window的构造函数中(以解决缺少 INotifyPropertyChanged 的问题),因此要花更多的时间来加载和显示它,我注意到使用 System.Data.DataTable 的示例比上一个示例要慢一些。

I also put the array initializing code in the Window's constructor (to deal with the lack of INotifyPropertyChanged) so it's going to take a little bit more to load and show it, and I noticed this sample using System.Data.DataTable is slightly slower than the previous one.

但是,我必须警告您,绑定到非 INotifyPropertyChanged 对象可能会导致内存泄漏

However, I must warn you that Binding to Non-INotifyPropertyChanged objects may cause a Memory Leak.

不过,您将无法使用简单的 Grid 控件,因为它不执行UI虚拟化。如果您需要虚拟化网格,则必须自己实现。

Still, you will NOT be able to use a simple Grid control, because it does not do UI Virtualization. If you want a Virtualizing Grid, you will have to implement it yourself.

您也将无法使用Winforms方法。

You will also NOT be able to use a winforms approach to this. It's simply irrelevant and useless in WPF.

    <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>

后面的代码:

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

最重要的是要在WPF中执行某些操作,您必须以WPF方式执行。它不仅仅是一个UI框架,它本身更是一个应用程序框架。

Bottom line is to do something in WPF you have to do it the WPF way. It's not just a UI framework, it's more of an Application Framework by itself.

Edit3

<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding}"/>

 DataContext = Array2DataTable(array).DefaultView;

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

Works perfectly fine for me. Loading time is not noticeable with 160000 rows. What .Net framework version are you using?

这篇关于如何提高WPF网格控件(.NET 4.0 / 4.5)的性能?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆