Control.AddRange(...) медленный

Проект: у меня есть родительская панель, которая содержит ComboBox и FlowLayoutPanel. FlowLayoutPanel содержит переменное количество дочерних панелей (пользовательский элемент управления, который наследуется от UserControl). Каждая дочерняя панель содержит несколько меток, два ComboBox, кнопку и DataGridView с 3 столбцами ComboBox и столбцом кнопки. DataGridView может иметь 1-6 строк. FlowLayoutPanel заполняется дочерними панелями, когда элемент выбирается из ComboBox на родительской панели.

Проблема: заполнение FlowLayoutPanel примерно 50 дочерними панелями занимает около 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();
}

Вещи, которые я пробовал:

  • Вызовите SuspendLayout() / ResumeLayout() на родительской панели и / или FlowLayoutPanel. Минимальное увеличение производительности (~0,2 секунды).
  • Используйте Control.FlatStyle.Flat для столбцов ComboBoxes, Buttons и DataGridView. Минимальное увеличение производительности (~0,1 секунды).
  • Проверено, что ни один из моих элементов управления не использует прозрачный цвет фона.
  • Установите значение ChildPanel.DoubleBuffered и ParentPanel.DoubleBuffered в значение true.
  • Удалите FlowLayoutPanel из его родителя перед вызовом AddRange () и добавьте его снова после.

Вещи, которые могут быть актуальны:

  • Панели и элементы управления используют якоря (в отличие от автоматического изменения размера или стыковки).
  • Мои элементы управления заполняются вручную и не используют свойство DataSource.

РЕДАКТИРОВАТЬ: Решение:

Ответ @HighCore - правильное решение. К сожалению, я не буду реализовывать это в настоящее время (это может произойти в будущем), потому что я нашел обходной путь. Обходной путь на самом деле не решает проблему, просто маскирует ее, поэтому я не публикую это как ответ. Я обнаружил, что форма загружается в два раза быстрее, если вкладка Зависимости не находится сверху (т.е. выбрана вкладка Списки продуктов). Это сокращает время загрузки до 1 секунды, что является приемлемым. Когда данные загружаются и вкладка "Зависимости" находится сверху, я переключаюсь на вкладку "Списки продуктов", бросаю темно-серое поле над элементом управления вкладкой с надписью "Загрузка..." в середине, загружаю данные и затем переключаюсь. вернуться на вкладку Зависимости.

Спасибо всем за ваши комментарии и предложения, это было высоко оценено.

1 ответ

Решение

Публикация этого ответа, потому что 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>

Код сзади (только шаблон для поддержки примера)

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

Затем вы помещаете это в существующий пользовательский интерфейс winforms, используя ElementHost:

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

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

        Controls.Add(elementHost);

    }
}

Результат:

  • Обратите внимание, что я добавил 100000 записей. Тем не менее, время отклика (как при прокрутке, так и при взаимодействии с пользовательским интерфейсом) является немедленным благодаря встроенной в WPF виртуализации пользовательского интерфейса.
  • Также обратите внимание, что я использую DataBinding, что устраняет необходимость манипулировать элементами пользовательского интерфейса в процедурном коде. Это важно, потому что WPF Visual Tree представляет собой сложную структуру, а DataBinding всегда является предпочтительным подходом в WPF.
  • Также обратите внимание, изменяя размеры формы, что пользовательский интерфейс полностью независим от разрешения. Вы можете настроить его дальше, сделав ComboBox фиксированным и имея DataGrid растянуть до оставшегося пространства. Смотрите макеты WPF.
  • WPF Rocks. - посмотрите, сколько вы можете достичь с помощью такого небольшого кода и не тратя много $$$ на сторонние элементы управления. Вы должны действительно забыть winforms навсегда.
  • Вам нужно как минимум настроить таргетинг на.Net 3.0, но настоятельно рекомендуется 4.0/4.5, потому что в WPF было несколько проблем в более ранних версиях, которые были исправлены в 4.0.
  • Убедитесь, что вы ссылаетесь PresentationCore.dll, PresentationFramework.dll, WindowsBase.dll, System.Xaml.dll а также WindowsFormsIntegration.dll все из которых принадлежат самой.Net Framework (без сторонних)
Другие вопросы по тегам