4

My application is loading a bunch of the same user control into a ScrollPanel. The problem is, this is very slow.

The profiler shows that the bottleneck is the method Application.LoadComponent(), which is called internally from the constructor of my user control. The documentation of this method says that this method loads XAML files.

The question is, how can I use BAML instead of XAML? How can I accomplish that the XAML for my user control must not be parse again and again when creating new instances from it? Is there another way to make loading my user controls faster?

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
codymanix
  • 28,510
  • 21
  • 92
  • 151

2 Answers2

5

LoadComponent() already loads .baml, don't worry about this bit of the question. Microsoft did this intentionally, to not let developers make dependencies on baml format. Going forward they can improve the format without breaking any existing applications.

Yes, there are another ways of making it work faster. The first optimization to take is UI virtualization. WPF already comes with handy VirtualizingStackPanel. It works in tandem with ItemsControls, and has certain limitations (e.g. if you create items containers and add them by your own, you loose virtualization, or if you set ScrollViewer.CanContentScroll="False" you loose it again). To use virtualization you will probably have to rewrite your application to use ItemsControl + DataBinding style (ListBox already has virtualization enabled)

If you feel like you need even more information on UI virtualization refer to Dan Crevier's blog.

And final advice. You can try rewriting your UserControls to Custom Controls. My simple performance test showed the following figures. To create 10K controls with the same visual tree it took:

  • for UserControl: 4932ms;
  • for CustomControl: 86ms; (~57 times faster)

Hope this helps

Anvaka
  • 15,658
  • 2
  • 47
  • 56
  • The MSDN page (http://msdn.microsoft.com/de-de/library/system.windows.application.loadcomponent.aspx) states explicitly that LoadComponent loads XAML, not BAML, that's the problem. Since my UserControl is a composition of multiple other controls, a custom control won't be the right choice here. – codymanix Jan 09 '11 at 00:30
  • 1
    You want to use UI Virtualization as suggested, which will only display the items on screen. Using BAML won't improve your performance. BAML is just a compiled version of XAML. – kevindaub Jan 09 '11 at 00:32
  • 3
    Can you expand upon "You can try rewriting your UserControls to Custom Controls"? I understand the difference so far as inheritance vs. composition, but I don't understand why there is such a dramatic performance difference. – Trinition Aug 21 '13 at 13:45
  • I think you could also do the composition along with the inheritance by writing Custom Control. The performance difference might be caused by the different approaches of building visual and logical tree based on xmal/baml. The UserControl itself is a ContentControl while the Custom Controls(in most cases) just subclass of Control. It will need to handle the ContentTemplate in the case of UserControl. P.S. I never did this kind of test. – terry Oct 24 '13 at 03:28
2

When a user control is instantiated the call chain is something like this:

  • UserControl.Constructor
    • UserControl.InitializeComponent()
      • Application.LoadComponent()
        • XamlReader.LoadBAML()

In my experience its the final call which actually consumes all the time. That is the step where the XAML file (stored in BAML format) is loaded and converted into actual objects for use.

I have found two general approaches to reducing or eliminating the time taken here.


The following simple refactoring is easy to do and in the cases I've worked on may reduce load time by about 75% (very roughly!) Typically you would start out having something like this:

Typical UserControl starting point

MyControl.xaml file:

<UserControl x:Class="MyNamespace.MyControl" ... >

   <Grid ... >
     ...
   </Grid>

</UserControl>

Corresponding MyControl.xaml.cs file:

namespace MyNamespace
{
    partial class MyControl : UserControl
    {
        public MyControl()
        {
             InitializeComponent();

             // other stuff...
        }
    }
}

Refactoring 1: Control with xaml/xaml.cs files

From that starting point, modify it to derive from Control instead of UserControl and set the ControlTemplate explictly:

MyControl.xaml file:

<Control x:Class="MyNamespace.MyControl" ... >
 <Control.Template>
  <ControlTemplate>
    <Grid ... >
      ...
    </Grid>
  </ControlTemplate>
 </Control.Template>
</Control>

Corresponding MyControl.xaml.cs file:

namespace MyNamespace
{
    partial class MyControl : Control
    {
        public MyControl()
        {
             InitializeComponent();

             // other stuff...
        }
    }
}

For whatever reason in all the examples I had tried, just doing this knocked off about 75% (roughly) of the load time. This can be a very quick and easy refactoring that has a substantial advantage.

Visual Studio is perfectly happy with this xaml / xaml.cs usage even though its a little nonstandard.

However note that the XAML will still be loaded and processed in each instantiation of the control. This is avoidable however, see the next section.


Refactoring 2: Control with resource dictionary

Even better, in terms of performance, is to refactor the ControlTemplate into a resource dictionary. This totally eliminates the per-control instance XAML load and (in my experience) results in almost no delay at all.

Replace the original .xaml file with "MyControlResources.xaml" such as the following:

<ResourceDictionary ... >

    <ControlTemplate x:Key="MyControlTemplateKey">
       <Grid ... >
           ...
       </Grid>
    </ControlTemplate>

    <Style TargetType="{x:Type MyNamespace:MyControl}">
        <Setter
            Property="Template"
            Value="{StaticResource MyControlTemplateKey}"
            />
    </Style>

</ResourceDictionary>

This resource dictionary needs to be added to a merged dictionary in app.xaml.

Replace the original xaml.cs file with a plain MyControl.cs file containing:

namespace MyNamespace
{
    class MyControl : Control
    {
        public MyControl()
        {
             // other stuff...
        }
    }
}

Note that there is no longer any call to InitializeComponent(). The style system is used to apply the control template whose XAML is loaded only once.

This is a somewhat more substantial refactoring but still relatively easy to carry out as usually few other code modifications are needed. Bindings originally made by ElementName to the top level control directly should be changed to use TemplateBinding.

You could organize the control template & style resources in other ways, this just shows an approach which directly corresponds to the original organization of code for the user control. Personally I like to have one resource dictionary corresponding to one control.

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81