Control.AddRange(...)很慢

时间:2014-04-07 20:17:43

标签: c# .net winforms .net-2.0 flowlayoutpanel

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

enter image description here

问题: 使用大约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();
}

我尝试过的事情:

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

可能相关的事情:

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

编辑:解决方案:

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

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

1 个答案:

答案 0 :(得分:4)

发布此答案是因为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>

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

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

数据项:

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

然后使用ElementHost

将其放入现有的winforms UI中
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

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

        Controls.Add(elementHost);

    }
}

结果:

enter image description here

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