0

I have the following code which creates WPF controls and then adds them to a window in a fashion I need. It works decently, but when trying to create 256 (x4 - two textblocks, combo box, textbox) controls it takes a while to display the tab. Window loads fine but I have many tabs and when I click on point setup tab it lags a little before displaying the tab. It only lags the first time I click on the tab, every time after the first it responds immediately.

At first I thought it was a rendering issue, but after much other research I am of the impression C#/WPF doesn't do well with creating a bunch of objects on the fly and adding them to forms.

If I drop the number of items to 50 it responds immediately, 100 is a slight lag and 200 (256) is a little more of a lag and too much to be acceptable to users.

Any experiences with issues like this before and advice for how to fix it or other tips/tricks.

Thanks in advance! Wesley

public static void pointSetup(VirtualizingStackPanel desc, VirtualizingStackPanel map) //Draws point description and point map table in point setup tab
    {
        StackPanel row;
        TextBlock text;
        TextBox textBox;
        ComboBox comboBox;

        Thickness rowSpacing = new Thickness(0, 0, 0, 5);
        Thickness textSpacing = new Thickness(0, 3, 5, 3);
        List<string> list = new List<string>();

        list.Add("xx");
        for (byte i = 0; i < Global.currentZonesToMap; i++)
        {
            list.Add("Zone  " + (i + 1));
        }

        for (short i = 0; i < 256; i++)
        {
            //desc
            row = new StackPanel();
            row.Margin = rowSpacing;
            row.Orientation = Orientation.Horizontal;

            text = new TextBlock();
            text.Text = "Point " + (i + 1);
            text.Margin = textSpacing;
            text.Width = 50;

            textBox = new TextBox();
            textBox.MaxLength = 28;
            textBox.Text = "";
            textBox.Width = 270;

            row.Children.Add(text);
            row.Children.Add(textBox);

            desc.Children.Add(row);

            //map
            row = new StackPanel();
            row.Margin = rowSpacing;
            row.Orientation = Orientation.Horizontal;

            text = new TextBlock();
            text.Text = "Point " + (i + 1);
            text.Margin = textSpacing;
            text.Width = 50;

            comboBox = new ComboBox();
            comboBox.ItemsSource = list;
            comboBox.Width = 270;

            row.Children.Add(text);
            row.Children.Add(comboBox);

            map.Children.Add(row);
        }
    }

New Code (using DataTemplate and ItemsControl)

    public class DevicePoint
    {
        public string desc { get; set; }

        public int zone { get; set; }

        public List<string> zones { get; set; }
    }

    //Initialized all variables and displays UI (constructor)
    public Dispatcher()
    {
        InitializeComponent();

        List<string> opts = new List<string>();
        opts.Add("xx");
        for (byte i = 0; i < Global.currentZonesToMap; i++)
        {
            opts.Add("Zone  " + (i + 1));
        }

        List<DevicePoint> points = new List<DevicePoint>();
        for (short i = 0; i < 256; i++)
            points.Add(new DevicePoint() { desc = "Point " + (i + 1), zone = 0, zones = opts });
        pointDesc.ItemsSource = points;
        pointZoneMap.ItemsSource = points;

        ... other stuff here ...
    }

        <StackPanel>
                <TextBlock Margin="10" FontWeight="Bold" HorizontalAlignment="Center" Text="Point Descriptions" />
                <ScrollViewer Width="360" Margin="30,10,30,10" MaxHeight="405">
                    <ItemsControl Name="pointDesc" Margin="5">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <StackPanel VirtualizingStackPanel.IsVirtualizing="True" Margin="0,0,0,5" Orientation="Horizontal">
                                    <TextBlock Margin="0,3,5,3" Width="50" Text="{Binding desc}" />
                                    <TextBox MaxLength="28" Width="270" Text="{Binding desc}" />
                                </StackPanel>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </ScrollViewer>
            </StackPanel>
            <StackPanel Grid.Column="1">
                <TextBlock Margin="10" FontWeight="Bold" HorizontalAlignment="Center" Text="Point - Zone Map" />
                <ScrollViewer Width="360" Margin="30,10,30,10" MaxHeight="405">
                    <ItemsControl Name="pointZoneMap" Margin="5">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <StackPanel VirtualizingStackPanel.IsVirtualizing="True" Margin="0,0,0,5" Orientation="Horizontal">
                                    <TextBlock Margin="0,3,5,3" Width="50" Text="{Binding desc}" />
                                    <ComboBox Width="270" ItemsSource="{Binding zones}" SelectedIndex="{Binding zone}" />
                                </StackPanel>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </ScrollViewer>
            </StackPanel>
Athari
  • 33,702
  • 16
  • 105
  • 146
Wesley Carlsen
  • 137
  • 3
  • 19
  • 7
    Note that by creating your objects using code, you're effectively negating the virtualization system that helps to overcome the performance hit from creating large numbers of controls at once. I recommend restructuring this code to use an `ItemsControl` with an `ItemTemplate` instead of using the old WinForms approach of adding controls directly via code. – Dan Bryant Jul 09 '13 at 16:10
  • There could be some method on the 'parent' control called `BeginUpdate` which suspends updating the UI until you call `EndUpdate`, but I am not 100% sure. – Silvermind Jul 09 '13 at 16:12
  • You're also explicitly taking VirtualizingStackPanels but then preventing them from doing any virtualization by directly adding items. – John Bowen Jul 09 '13 at 16:12
  • 2
    As a secondary note, the `ItemsControl` does not natively support virtualization (it was added primarily for `ListBox`.) You can still get the desired behavior using a technique described [here](http://stackoverflow.com/questions/2783845/virtualizing-an-itemscontrol). – Dan Bryant Jul 09 '13 at 16:12
  • @Dan Bryant How would I go about restructuring it to use an ItemsControl and ItemTemplate? Any good tutorials or explanations of that would be very helpful as I have never used that before? I have many other methods just like this one all contained in a class called 'Draw' so that may affect how I restructure it. I am not opposed to recoding most of it to make it efficient and responsive but please give me a good starting point! Thanks – Wesley Carlsen Jul 09 '13 at 17:17
  • @Silvermind The parent elements is a to the stack panels is a scroll viewer and I tried this but it doesn't appear to have any being/end update methods – Wesley Carlsen Jul 09 '13 at 17:18
  • @John Bowen I was unaware my method of adding items affected the VirtualizingStackPanels. Originally I was only using these special objects to test and see if it was a consistent rendering issue. Once I realized it wasn't (that it only took long to load the first time) I took away these special objects and went back to regular StackPanesl – Wesley Carlsen Jul 09 '13 at 17:20
  • 1
    @WesleyCarlsen, this is a big topic; [this article](http://drwpf.com/blog/2008/01/03/itemscontrol-d-is-for-datatemplate/) talks a little bit about the WPF content model and how it is meant to be used. Once you start to grok DataTemplates, the rest will fall into place. – Dan Bryant Jul 09 '13 at 17:21
  • I finished implementing my code using the ItemsControl and DataTemplate setup, but there is still a delay when I load the tab the first time. I will edit the code above and add my new code so you can view it. – Wesley Carlsen Jul 09 '13 at 19:43
  • @DanBryant The new code has been posted up above; first the class with my variables, then my window logic and finally my window XAML – Wesley Carlsen Jul 09 '13 at 19:57

1 Answers1

0
  1. If your computer have multiple cores, and I am assuming it have, try to perform the for loop in parallel (parallelfor (from .net 4 or above).
  2. You can set the points List size during creation to 256, this will prevent memory allocations during the items adding operation.
  3. Consider use a StringBuilder if Global.currentZonesToMap is large.
  4. Use StringBuilder to build the value for the DevicePoint.desc string property.

Good luck,

M. Moshe

Moran Moshe
  • 114
  • 1
  • Unfortunately all controls need to be added on the UI thread, which means that Parallel is out. I also doubt that reallocation of the list's backing store is noticeable and StringBuilder may actually be slower than the implicitly generated string.Concat call in this case. – Dan Bryant Jul 10 '13 at 02:01