Search code examples
c#wpfmultithreadingstackpanelbegininvoke

How to load usercontrols into a stackpanel asynchronously whilst showing progress bar


I am developing a WPF appl using C#.

I am trying to get my head around the async threading and tasks etc.

Basically I have a usercontrol, that i add to a stackpanel in my page. There can be many iterations of this usercontrol added to the stackpanel.

i.txtItemDescription.Text = "Item description " + ii.ToString() + ". Put a description here";
i.txtItemTitle.Text = "Item title " + ii.ToString() + ". Put a title here";
MainStack.Children.Add(i);

This works fine. However, when i transfer this appl to my windows 8.1 tablet it slows the display of these usercontrols(UCs) down dramatically.

I have tried using virtulization which had no effect as the data access and generation of the UCs poses no performance impact. It is the physical drawing of the controls on the screen.

So my question is as follows:

  1. Would running this on a background thread help performance?
  2. If so, how on earth does one load the stackpanel with BeginInvoke or Tasks? I have tried almost every answer mentioned on this site and either I get an STA error or it simply does not do what I would like.
  3. My thoughts behind point 2 is that I would like to show a wait progress bar (IsIndefinite=true) on the form until such time as the stackpanel has finished loaded. But I'm totally stumped. In all cases the forms UI does not update the progress bar until after the stackpanel has fully loaded. In which case it is too late.

Any thoughts on how to would be appreciated!

Regds

Paul


Solution

  • First, you cannot create UI elements on a different thread than the one they will ultimately be rendered on, so forget about that possibility.

    If it is the physical drawing of the controls that is causing performance problems, then you are probably not using UI virtualization correctly. The whole point of UI virtualization is to only perform layout and rendering for those controls which are actually in view, which means letting some sort of items host (like an ItemsControl) generate the corresponding UI elements as needed. Pre-populating the entire panel defeats the purpose, and a regular StackPanel does not support virtualization anyway. I suggest the following:

    1. Instead of using a StackPanel directly, use an ItemsControl with a VirtualizingStackPanel in its ItemsPanelTemplate.

    2. Instead of adding the user controls manually, bind the ItemsSource to the underlying list of items, and let the ItemsControl generate containers for the items as they are brought into view.

    3. Use an ItemTemplate to define how the items are rendered, e.g., the template content should be your user control with the appropriate bindings.

    You can experiment with enabling container recycling on the VirtualizingStackPanel; depending on your use case, it may help or hurt performance.

    Anyway, if you follow this advice, you should not need a progress indicator, as the UI elements for your items will be generated as they are are needed, i.e., as they are scrolled into view.


    If you've never used virtualization with a plain old ItemsControl before (the default style does not support it), you can use the style below as a starting point. Note that it supports scrolling, unlike the default ItemsControl style.

    <Style TargetType="{x:Type ItemsControl}">
      <Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
              Value="Auto" />
      <Setter Property="ScrollViewer.VerticalScrollBarVisibility"
              Value="Auto" />
      <Setter Property="ScrollViewer.CanContentScroll"
              Value="True" />
      <Setter Property="ScrollViewer.PanningMode"
              Value="Both" />
      <Setter Property="Stylus.IsFlicksEnabled"
              Value="False" />
      <Setter Property="VerticalContentAlignment"
              Value="Center" />
      <Setter Property="VirtualizingStackPanel.IsVirtualizing"
              Value="True" />
      <Setter Property="VirtualizingStackPanel.VirtualizationMode"
              Value="Recycling" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type ItemsControl}">
            <Border x:Name="OuterBorder"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    SnapsToDevicePixels="True">
              <ScrollViewer Padding="{TemplateBinding Padding}"
                            Focusable="False">
                <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
              </ScrollViewer>
            </Border>
            <ControlTemplate.Triggers>
              <Trigger Property="IsEnabled"
                       Value="False">
                <Setter TargetName="OuterBorder"
                        Property="Background"
                        Value="{DynamicResource {x:Static apthemes:AssetResourceKeys.ListBackgroundDisabledBrushKey}}" />
              </Trigger>
              <Trigger Property="IsGrouping"
                       Value="True">
                <Setter Property="ScrollViewer.CanContentScroll"
                        Value="False" />
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
      <Style.Triggers>
        <Trigger Property="VirtualizingStackPanel.IsVirtualizing"
                 Value="True">
          <Setter Property="ItemsPanel">
            <Setter.Value>
              <ItemsPanelTemplate>
                <VirtualizingStackPanel />
              </ItemsPanelTemplate>
            </Setter.Value>
          </Setter>
        </Trigger>
      </Style.Triggers>
    </Style>