Control.AddRange(…) is slow

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:

enter image description here

  • Notice that I added 100,000 records. Still, response time (both when scrolling and interacting with the UI) is immediate due to WPF’s built in UI Virtualization.
  • Also notice that I’m using DataBinding which removes the need to manipulate UI elements in procedural code. This is important because the WPF Visual Tree is a complex structure, and DataBinding is the preferred approach in WPF always.
  • Also notice by resizing the form that the UI is completely resolution independent. You can customize it further by making the ComboBoxes fixed and having the DataGrid stretch to the remaining space. See WPF Layouts.
  • WPF Rocks. – see how much you can achieve with so little code, and without spending lots of $$$ in third party controls. You should really forget winforms forever.
  • You will need to target .Net 3.0 at a minimum, but 4.0/4.5 is highly recommended because WPF had several issues in earlier versions, which were fixed in 4.0.
  • Make sure you reference PresentationCore.dll, PresentationFramework.dll, WindowsBase.dll, System.Xaml.dll and WindowsFormsIntegration.dll, all of which belong to the .Net Framework itself (no 3rd parties)

Leave a Comment