Ale*_*ell 3 wpf wpf-controls wpfdatagrid wpf-4.0 wpf-4.5
定义:将字符串的二维数组(约 10 列、1,600 行、7 个字符的固定长度)用作 WPF .NET 4.0 网格控件的数据源,使用以下代码片段来填充网格标签显示数组中的值。注意:Grid 被添加到 XAML 并传递给函数 PopulateGrid(参见清单 1)。视觉输出本质上是只读模式下的表格数据表示(不需要双向绑定)。
问题:性能是一个关键问题。在功能强大的 Intel-i3/8GB-DDR3 PC 上完成此操作花了令人难以置信的 3...5 秒;因此,恕我直言,基于与类似控件/任务(例如常规 WinForm 数据感知控件,甚至 Excel 工作表)中的类似控件/任务的比较,此 WPF 网格性能至少比预期慢一个数量级。
问题1:是否有办法在上述场景中提高WPF Grid的性能?请将您的答案/潜在改进指向下面清单 1 和清单 2 中提供的代码片段。
问题 1a:建议的解决方案可以将数据绑定到其他数据感知控件,例如DataGridto DataTable。我在清单 2 中添加string[,]了DataTable dt转换器,以便可以将附加控件DataContext(或ItemsSource其他)的属性绑定到dt.DefaultView. 那么,以最简单的形式,您能否提供一个紧凑的(最好是几行代码,因为它是在旧式数据感知控件中完成的)和高效(性能方面)的解决方案,将 WPF 的数据绑定DataGrid到DataTable对象?
非常感谢。
清单 1。Grid GridOut从 2D填充 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
Run Code Online (Sandbox Code Playgroud)
清单 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
Run Code Online (Sandbox Code Playgroud)
注2。建议使用其 Text 属性而不是 Content替换Label控件TextBlock,如Label. 它会稍微加快执行速度,而且代码片段将向前兼容 VS 2012 for Win 8,其中不包括Label.
注 3:到目前为止,我已经尝试绑定DataGrid到DataTable(参见清单 3 中的 XAML),但性能很差(grdOut是一个嵌套的Grid,用作表格数据的容器;_dataGrid是 的数据感知对象类型DataGrid)。
清单 3。DataGrid绑定到DataTable: 性能很差,所以我已经删除了它ScrollViewer,但它运行不正常。
<ScrollViewer ScrollViewer.CanContentScroll="True" VerticalScrollBarVisibility="Auto" >
<Grid Name="grdOut">
<DataGrid AutoGenerateColumns="True" Name="_dataGrid" ItemsSource="{Binding Path=.}" />
</Grid>
</ScrollViewer>
Run Code Online (Sandbox Code Playgroud)
好的。删除所有代码并重新开始。
这是我对Labels基于二维字符串数组的 X 行数和 Y 列数的“动态网格”的看法:
<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>
Run Code Online (Sandbox Code Playgroud)
背后的代码:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
视图模型:
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
数据项:
public class LabelGridItem: PropertyChangedBase
{
public ObservableCollection<string> Items { get; set; }
public LabelGridItem()
{
Items = new ObservableCollection<string>();
}
}
Run Code Online (Sandbox Code Playgroud)
PropertyChangedBase 类(MVVM 帮助程序)
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));
}));
}
}
Run Code Online (Sandbox Code Playgroud)
结果:

性能很棒。请注意,我使用的是 20 列而不是您建议的 10 列。当您单击按钮时,网格的填充是立即的。由于内置 UI 虚拟化,我确信性能比蹩脚的恐龙 winforms 好得多。
UI 是在 XAML 中定义的,而不是在程序代码中创建 UI 元素,这是一种不好的做法。
UI 和数据保持分离,从而提高可维护性、可扩展性和清洁度。
将我的代码复制并粘贴到 a 中,File -> New -> WPF Application然后自己查看结果。
另外,请记住,如果您只想显示文本,最好使用 aTextBlock而不是 a Label,后者是一种轻量级的 Text 元素。
WPF 摇摆不定,即使在边缘情况下它可能会出现性能下降,它仍然比目前存在的任何东西都要好 12837091723。
编辑:
我继续向行数 (160000) 添加 0 个零。性能还是可以接受的。填充网格用时不到 1 秒。
请注意,“列”在我的示例中没有被虚拟化。如果数量很多,这可能会导致性能问题,但这不是您所描述的。
编辑2:
根据您的评论和说明,我制作了一个新示例,这次基于System.Data.DataTable. 没有 ObservableCollections,没有异步的东西(反正在我之前的例子中没有任何异步)。而且只有 10 列。由于窗口太小 ( Width="300") 并且不足以显示数据,因此出现了水平滚动条。WPF 与分辨率无关,与恐龙框架不同,它在需要时显示滚动条,但也会将内容拉伸到可用空间(您可以通过调整窗口大小等来查看这一点)。
我还将数组初始化代码放在 Window 的构造函数中(以处理缺少INotifyPropertyChanged),因此加载和显示它需要更多时间,而且我注意到这个示例使用System.Data.DataTable比前一个稍微慢一些。
但是,我必须警告您绑定到非INotifyPropertyChanged对象可能会导致内存泄漏。
不过,您将无法使用简单的Grid控件,因为它不进行 UI 虚拟化。如果您想要一个虚拟化网格,则必须自己实现。
您也将无法对此使用 winforms 方法。它在 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>
Run Code Online (Sandbox Code Playgroud)
背后的代码:
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
}
}
Run Code Online (Sandbox Code Playgroud)
底线是在 WPF 中做一些事情,你必须以 WPF 的方式来做。它不仅仅是一个 UI 框架,它本身更像是一个应用程序框架。
编辑3:
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding}"/>
DataContext = Array2DataTable(array).DefaultView;
Run Code Online (Sandbox Code Playgroud)
对我来说非常好。160000 行的加载时间并不明显。您使用的是什么 .Net 框架版本?