Control.AddRange(…)很慢

项目:我有一个父面板,它包含一个ComboBox和FlowLayoutPanel。 FlowLayoutPanel包含可变数量的子面板(inheritance自UserControl的自定义控件)。 每个子面板包含一些标签,两个ComboBox,一个按钮和一个带有3个ComboBox列和一个按钮列的DataGridView。 DataGridView可能有1-6行。 当从父面板上的ComboBox中选择一个项目时,FlowLayoutPanel将填充子面板。

在此处输入图像描述

问题:使用大约50个子面板填充FlowLayoutPanel大约需要2.5秒。 具体来说,我已经确定对FlowLayoutPanel.Controls.AddRange()的调用是罪魁祸首。

相关代码:我不能在这里发布我的所有代码(代码太多,部分内容是保密的),但我会尽力解释发生了什么。

家长小组:

private void displayInformation(Suite suite) { this.SuspendLayout(); // Get dependencies. List dependents = new List(suite.dependencies.Keys); dependents.Sort(SuiteRange.Compare); // Create a ChildPanel for each dependent. List rangePanels = new List(); foreach (SuiteRange dependent in dependents) { ChildPanel sdp = new ChildPanel(); sdp.initialize(initialSuite.name, dataAccess); sdp.displayInformation(dependent, suite.dependencies[dependent]); rangePanels.Add(sdp); } // Put the child panels in the FlowLayoutPanel. flpDependencyGroups.SuspendLayout(); // Takes ~2.5 seconds flpDependencyGroups.Controls.AddRange(rangePanels.ToArray()); flpDependencyGroups.ResumeLayout(); // Takes ~0.5 seconds updateChildPanelSizes(); this.ResumeLayout(); } 

我尝试过的事情:

  • 在父面板和/或FlowLayoutPanel上调用SuspendLayout()/ ResumeLayout()。 最小的性能提升(~0.2秒)。
  • 在ComboBoxes,Buttons和DataGridView列上使用Control.FlatStyle.Flat。 最小的性能提升(~0.1秒)。
  • validation我的控件都不使用透明背景颜色。
  • 将ChildPanel.DoubleBuffered和ParentPanel.DoubleBuffered设置为true。
  • 在调用AddRange()之前从其父级中删除FlowLayoutPanel并在之后重新添加它。

可能相关的事情:

  • 面板和控件使用锚点(而不是自动resize或停靠)。
  • 我的控件是手动填充的,不使用DataSource属性。

编辑:解决方案:

@ HighCore的答案是正确的解决方案。 不幸的是,我现在不会实现它(它可能会发生在路上),因为我找到了一个解决方法。 解决方法并没有真正解决问题,只是掩盖它,因此我不会将此作为答案发布。 我发现,如果“依赖关系”选项卡不在顶部(即选择了“产品列表”选项卡),则表单会加载一半的时间。 这将加载时间减少到约1秒,这是可以接受的。 当加载数据并且Dependencies选项卡位于顶部时,我切换到Product Lists选项卡,在选项卡控件上方抛出一个深灰色框,中间显示“Loading …”,加载数据,然后切换返回“依赖关系”选项卡。

感谢大家的意见和建议,非常感谢。

发布此答案是因为OP要求:

这就是你在WPF中做类似的事情:

                                                

Code Behind(仅支持示例的样板)

 public partial class ListBoxSample : UserControl { public ListBoxSample() { InitializeComponent(); } public void LoadData() { Task.Factory.StartNew(() => { var list = new List(); for (int i = 0; i < 100000; i++) { var item = new DataItem() { From = "1", To = "2", ChildItems = { new ChildItem() { DependeeFrom = i.ToString(), DependeeTo = (i + 10).ToString(), XXXX = "XXXX" }, new ChildItem() { DependeeFrom = i.ToString(), DependeeTo = (i + 10).ToString(), XXXX = "XXXX" }, new ChildItem() { DependeeFrom = i.ToString(), DependeeTo = (i + 10).ToString(), XXXX = "XXXX" } } }; list.Add(item); } return list; }).ContinueWith(t => { Dispatcher.Invoke((Action) (() => DataContext = t.Result)); }); } private void Load_Click(object sender, System.Windows.RoutedEventArgs e) { LoadData(); } } 

数据项:

 public class DataItem { public List ChildItems { get; set; } public List FromOptions { get; set; } public List ToOptions { get; set; } public string From { get; set; } public string To { get; set; } public DataItem() { ChildItems = new List(); FromOptions = Enumerable.Range(0,10).Select(x => x.ToString()).ToList(); ToOptions = Enumerable.Range(0, 10).Select(x => x.ToString()).ToList(); } } public class ChildItem { public string XXXX { get; set; } public string DependeeFrom { get; set; } public string DependeeTo { get; set; } } 

然后使用ElementHost将其放在现有的winforms UI中:

 public partial class Form1 : Form { public Form1() { InitializeComponent(); var elementHost = new ElementHost { Dock = DockStyle.Fill, Child = new ListBoxSample() }; Controls.Add(elementHost); } } 

结果:

在此处输入图像描述

  • 请注意,我添加了100,000条记录 。 由于WPF内置的UI虚拟化 ,响应时间(在滚动和与UI交互时)仍然是立即的。
  • 另请注意,我正在使用DataBinding ,这样就无需在过程代码中操作UI元素。 这很重要,因为WPF Visual Tree是一个复杂的结构,而DataBinding始终是WPF中的首选方法。
  • 另请注意,通过调整UI的forms,UI完全独立分辨率 。 您可以通过固定ComboBox并将DataGrid拉伸到剩余空间来进一步自定义它。 请参阅WPF布局 。
  • WPF Rocks。 – 看看你能用这么少的代码实现多少,并且不需要在第三方控件中花费大量$$$。 你应该永远忘记winforms。
  • 您将需要至少以.Net 3.0为目标,但强烈建议使用4.0 / 4.5,因为WPF在早期版本中有几个问题,已在4.0中修复。
  • 请确保您引用PresentationCore.dllPresentationFramework.dllWindowsBase.dllSystem.Xaml.dllWindowsFormsIntegration.dll ,所有这些都属于.Net Framework本身(没有第三方)