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 (без сторонних)