Project: I have a parent panel which holds a ComboBox and FlowLayoutPanel. The FlowLayoutPanel holds a variable number of child panels (a custom control that inherits from UserControl). Each child panel contains some labels, two ComboBoxes, a button, and a DataGridView with 3 ComboBox columns and a button column. The DataGridView may have 1-6 rows. The FlowLayoutPanel is populated with child panels when an item is selected from the ComboBox on the parent panel.
Problem: Populating the FlowLayoutPanel with about 50 child panels takes about 2.5 seconds. Specifically, I've determined that the call to FlowLayoutPanel.Controls.AddRange() is the culprit.
Relevant Code: I can't post all of my code here (too much code plus parts of it are confidential), but I'll do my best to explain what is happening.
Parent Panel:
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();
}
Things I've tried:
Things that might be relevant:
EDIT: Solution:
@HighCore's answer is the correct solution. Unfortunately I won't be implementing it at this time (it could happen down the road) because I found a workaround. The workaround doesn't really solve the problem, just masks it, hence why I'm not posting this as an answer. I discovered that the form loads in half the time if the Dependencies tab isn't on top (i.e. the Product Lists tab is selected). This reduces loading time to about 1 second, which is acceptable. When data is being loaded and the Dependencies tab is on top, I switch to the Product Lists tab, throw up a dark grey box over the tab control that says "Loading..." in the middle, load the data, and then switch back to the Dependencies tab.
Thanks all for your comments and suggestions, it was greatly appreciated.
Posting this answer because the OP requested it:
This is how you'd do something like that in 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 (only boilerplate to support the example)
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();
}
}
Data Items:
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; }
}
Then you put that in an existing winforms UI using an ElementHost
:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var elementHost = new ElementHost
{
Dock = DockStyle.Fill,
Child = new ListBoxSample()
};
Controls.Add(elementHost);
}
}
Result:
DataGrid
stretch to the remaining space. See WPF Layouts.PresentationCore.dll
, PresentationFramework.dll
, WindowsBase.dll
, System.Xaml.dll
and WindowsFormsIntegration.dll
, all of which belong to the .Net Framework itself (no 3rd parties)