Control.AddRange(...)很慢

Eri*_*mus 2 .net c# flowlayoutpanel .net-2.0 winforms

项目: 我有一个父面板,其中包含一个ComboBox和FlowLayoutPanel。FlowLayoutPanel拥有可变数量的子面板(从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<SuiteRange> dependents = new List<SuiteRange>(suite.dependencies.Keys);
    dependents.Sort(SuiteRange.Compare);

    // Create a ChildPanel for each dependent.
    List<ChildPanel> rangePanels = new List<ChildPanel>();
    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();
}
Run Code Online (Sandbox Code Playgroud)

我尝试过的事情:

  • 在父面板和/或FlowLayoutPanel上调用SuspendLayout()/ ResumeLayout()。性能提升最小(〜0.2秒)。
  • 在ComboBoxes,Button和DataGridView列上使用Control.FlatStyle.Flat。性能提升最小(〜0.1秒)。
  • 验证我的控件均未使用透明背景色。
  • 将ChildPanel.DoubleBuffered和ParentPanel.DoubleBuffered设置为true。
  • 在调用AddRange()之前从其父级中移除FlowLayoutPanel,然后再重新添加它。

可能相关的事情:

  • 面板和控件使用锚点(与自动调整大小或停靠相对)。
  • 我的控件是手动填充的,并且不使用DataSource属性。

编辑:解决方案:

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

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

Fed*_*gui 5

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

这就是您在WPF中要做的事情:

<UserControl x:Class="WpfApplication7.ListBoxSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <DockPanel>
        <Button Content="Load" Click="Load_Click" DockPanel.Dock="Top"/>

        <ListBox ItemsSource="{Binding}"
                 HorizontalContentAlignment="Stretch">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="LightGray" BorderThickness="1" Padding="5"
                            Background="#FFFAFAFA">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition/>
                            </Grid.RowDefinitions>

                            <Grid.ColumnDefinitions>
                                <ColumnDefinition/>
                                <ColumnDefinition/>
                                <ColumnDefinition/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>

                            <TextBlock Text="Dependent Versions" FontWeight="Bold"
                                       Grid.ColumnSpan="2" HorizontalAlignment="Center"/>

                            <TextBlock Text="From:" FontWeight="Bold"
                                       Grid.Row="1" HorizontalAlignment="Center"/>

                            <TextBlock Text="To (exclusive):" FontWeight="Bold"
                                       Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center"/>

                            <ComboBox SelectedItem="{Binding From}"
                                      ItemsSource="{Binding FromOptions}"
                                      Grid.Row="2" Margin="5"/>

                            <ComboBox SelectedItem="{Binding To}"
                                      ItemsSource="{Binding ToOptions}"
                                      Grid.Row="2" Grid.Column="1" Margin="5"/>

                            <DataGrid ItemsSource="{Binding ChildItems}"
                                      AutoGenerateColumns="False" CanUserAddRows="False"
                                      Grid.Column="2" Grid.RowSpan="4">
                                <DataGrid.Columns>
                                    <DataGridTextColumn Header="XXXX" Binding="{Binding XXXX}"/>
                                    <DataGridTextColumn Header="Dependee From" Binding="{Binding DependeeFrom}"/>
                                    <DataGridTextColumn Header="Dependee To" Binding="{Binding DependeeTo}"/>
                                    <DataGridTemplateColumn Width="25">
                                        <DataGridTemplateColumn.CellTemplate>
                                            <DataTemplate>
                                                <Button Content="X"/>
                                            </DataTemplate>
                                        </DataGridTemplateColumn.CellTemplate>
                                    </DataGridTemplateColumn>

                                </DataGrid.Columns>
                            </DataGrid>

                            <Button Content="Delete"
                                    Grid.Column="3"
                                    HorizontalAlignment="Right" VerticalAlignment="Top"/>

                        </Grid>
                    </Border>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </DockPanel>
</UserControl>
Run Code Online (Sandbox Code Playgroud)

背后的代码(仅支持示例的样板)

public partial class ListBoxSample : UserControl
{
    public ListBoxSample()
    {
        InitializeComponent();
    }

    public void LoadData()
    {
        Task.Factory.StartNew(() =>
        {
            var list = new List<DataItem>();

            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();
    }
}
Run Code Online (Sandbox Code Playgroud)

数据项:

public class DataItem
{
    public List<ChildItem> ChildItems { get; set; }

    public List<string> FromOptions { get; set; }

    public List<string> ToOptions { get; set; }

    public string From { get; set; }

    public string To { get; set; }

    public DataItem()
    {
        ChildItems = new List<ChildItem>();

        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; }
}
Run Code Online (Sandbox Code Playgroud)

然后,使用以下命令将其放在现有的winforms UI中ElementHost:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        var elementHost = new ElementHost
        {
            Dock = DockStyle.Fill,
            Child = new ListBoxSample()
        };

        Controls.Add(elementHost);

    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

在此处输入图片说明

  • 注意,我添加了100,000条记录。尽管如此,由于WPF内置的UI虚拟化,响应时间(滚动和与UI交互时)都是立即的。
  • 还要注意,我正在使用DataBinding,它消除了在过程代码中操作UI元素的需要。这很重要,因为WPF可视树是一个复杂的结构,而DataBinding始终是WPF中的首选方法。
  • 还要通过调整表单大小来注意UI完全独立于分辨率。您可以通过使ComboBoxes固定并DataGrid拉伸到剩余空间来进一步自定义它。请参阅WPF布局。
  • WPF摇滚。-看看用很少的代码就可以实现多少目标,而无需在第三方控件上花费很多的钱。您应该永远忘记Winform。
  • 您将需要至少定位.Net 3.0,但是强烈建议使用4.0 / 4.5,因为WPF在早期版本中存在多个问题,这些问题已在4.0中修复。
  • 请确保您参考PresentationCore.dll,PresentationFramework.dll,WindowsBase.dll,System.Xaml.dll并且WindowsFormsIntegration.dll,所有这些都属于.Net框架本身(无第三方)