Compartir a través de


Optimizing WPF Application Performance

This topic is intended as a reference for Windows Presentation Foundation (WPF) application developers who are looking for ways to improve the performance of their applications. If you are a developer who is new to the Microsoft .NET Framework version 3.0 and WPF, you should first familiarize yourself with both platforms. This topic assumes working knowledge of both, and is written for programmers who already know enough to get their applications up and running. Information in this topic is based on version 1.0 of WPF.

NoteNote:

The performance data provided in this topic are based on WPF applications running on a 2.8 GHz PC with 512 RAM and an ATI Radeon 9700 graphics card.

This topic contains the following sections.

  • Planning for Performance
  • Taking Advantage of Hardware
  • Layout and Design
  • 2D Graphics and Imaging
  • Object Behavior
  • Application Resources
  • Text
  • Data Binding
  • Other Performance Recommendations
  • WPF Performance Tools and Resources
  • Related Topics

Planning for Performance

The success of achieving your performance goals depends on how well you develop your performance strategy. Planning is the first stage in developing any product. The following are a few very simple rules for developing a good performance strategy.

Think in Terms of Scenarios

Scenarios can help you focus on the critical components of your application. Scenarios are generally derived from your customers, as well as competitive products. Always study your customers and find out what really makes them excited about your product, and your competitors' products. Your customers' feedback can help you to determine your application's primary scenario. For instance, if you are designing a component that will be used at startup, it is likely that the component will be called only once, when the application starts up. Startup time becomes your key scenario. Other examples of key scenarios could be the desired frame rate for animation sequences, or the maximum working set allowed for the application.

Define Goals

Goals help you to determine whether an application is performing faster or slower. You should define goals for all of your scenarios. All performance goals that you define should be based on your customers' expectations. It may be difficult to set performance goals early on in the application development cycle, when there are still many unresolved issues. However, it is better to set an initial goal and revise it later than not to have a goal at all.

Understand Your Platform

Always maintain the cycle of measuring, investigating, refining/correcting during your application development cycle. From the beginning to the end of the development cycle, you need to measure your application's performance in a reliable, stable environment. You should avoid variability caused by external factors. For example, when testing performance, you should disable anti-virus or any automatic update such as SMS, in order not to impact performance test results. Once you have measured your application's performance, you need to identify the changes that will result in the biggest improvements. Once you have modified your application, start the cycle again.

Make Performance Tuning an Iterative Process

You should know the relative cost of each feature you will use. For example, the use of reflection in Microsoft .NET Framework version 3.0 is generally performance intensive in terms of computing resources, so you would want to use it judiciously. This does not mean to avoid the use of reflection, only that you should be careful to balance the performance requirements of your application with the performance demands of the features you use.

Build Towards Graphical Richness

A key technique for creating a scalable approach towards achieving WPF application performance is to build towards graphical richness and complexity. Always start with using the least performance intensive resources to achieve your scenario goals. Once you achieve these goals, build towards graphic richness by using more performance intensive features, always keeping your scenario goals in mind. Remember, WPF is a very rich platform and provides very rich graphic features. Using performance intensive features without thinking can negatively impact your overall application performance.

WPF controls are inherently extensible by allowing for wide-spread customization of their appearance, while not altering their control behavior. By taking advantage of styles, data templates, and control templates, you can create and incrementally evolve a customizable user interface (UI) that adapts to your performance requirements. The Photo Store Demo illustrates how you can easily create a separation between the basic user interface (UI) and the logic of the application. Once you have created this separation, if gives you the option of building towards graphical richness.

Taking Advantage of Hardware

The internal architecture of WPF has two rendering pipelines, hardware and software.

Hardware Rendering Pipeline

One of the most important factors in determining WPF performance is that it is render bound—the more pixels you have to render, the greater the performance cost. However, the more rendering that can be offloaded to the graphics processing unit (GPU), the more performance benefits you can gain. The WPF application hardware rendering pipeline takes full advantage of Microsoft DirectX features on hardware that supports a minimum of Microsoft DirectX version 7.0. Further optimizations can be gained by hardware that supports Microsoft DirectX version 7.0 and PixelShader 2.0+ features.

Software Rendering Pipeline

The WPF software rendering pipeline is entirely CPU bound. WPF takes advantage of the SSE and SSE2 instruction sets in the CPU to implement an optimized, fully-featured software rasterizer. Fallback to software is seamless any time application functionality cannot be rendered using the hardware rendering pipeline.

The biggest performance issue you will encounter when rendering in software mode is related to fill rate, which is defined as the number of pixels that you are rendering. If you are concerned about performance in software rendering mode, try to minimize the number of times a pixel is redrawn. For example, if you have an application with a blue background, which then renders a slightly transparent image over it, you will render all of the pixels in the application twice. As a result, it will take twice as long to render the application with the image than if you had only the blue background.

Graphics Rendering Tiers

It may be very difficult to predict the hardware configuration that your application will be running on. However, you might want to consider a design that allows your application to seamlessly switch features when running on different hardware, so that it can take full advantage of each different hardware configuration.

To achieve this, WPF provides functionality to determine the graphics capability of a system at runtime. Graphics capability is determined by categorizing the video card as one of three rendering capability tiers. WPF exposes an API that allows an application to query the rendering capability tier. Your application can then take different code paths at run time depending on the rendering tier supported by the hardware.

The features of the graphics hardware that most impact the rendering tier levels are:

  • Video RAM The amount of video memory on the graphics hardware determines the size and number of buffers that can be used for compositing graphics.

  • Pixel Shader A pixel shader is a graphics processing function that calculates effects on a per-pixel basis. Depending on the resolution of the displayed graphics, there could be several million pixels that need to be processed for each display frame.

  • Vertex Shader A vertex shader is a graphics processing function that performs mathematical operations on the vertex data of the object.

  • Multitexture Support Multitexture support refers to the ability to apply two or more distinct textures during a blending operation on a 3D graphics object. The degree of multitexture support is determined by the number of multitexture units on the graphics hardware.

The pixel shader, vertex shader, and multitexture features are used to define specific DirectX version levels, which, in turn, are used to define the different rendering tiers in WPF.

The features of the graphics hardware determine the rendering capability of a WPF application. The WPF system defines three rendering tiers:

  • Rendering Tier 0 No graphics hardware acceleration. The DirectX version level is less than version 7.0.

  • Rendering Tier 1 Partial graphics hardware acceleration. The DirectX version level is greater than or equal to version 7.0, and lesser than version 9.0.

  • Rendering Tier 2 Most graphics features use graphics hardware acceleration. The DirectX version level is greater than or equal to version 9.0.

For more information on WPF rendering tiers, see Graphics Rendering Tiers.

Layout and Design

The design of your WPF application can impact its performance by creating unnecessary overhead in calculating layout and validating object references. For more information, see The Layout System.

Layout

The term "layout pass" describes the process of measuring and arranging the members of a Panel-derived object's collection of children, and then drawing them onscreen. The layout pass is a mathematically-intensive process—the larger the number of children in the collection, the greater the number of calculations required. For example, each time a child UIElement object in the collection changes its position, it has the potential to trigger a new pass by the layout system. Because of the close relationship between object characteristics and layout behavior, it's important to understand the type of events that can invoke the layout system. Your application will perform better by reducing as much as possible any unnecessary invocations of the layout pass.

The layout system completes two passes for each child member in a collection: a measure pass, and an arrange pass. Each child object provides its own overridden implementation of the Measure and Arrange methods in order to provide its own specific layout behavior. At its simplest, layout is a recursive system that leads to an element being sized, positioned, and drawn onscreen.

  • A child UIElement object begins the layout process by first having its core properties measured.

  • The object's FrameworkElement properties that are related to size, such as Width, Height, and Margin, are evaluated.

  • Panel-specific logic is applied, such as the Dock property of the DockPanel, or the Orientation property of the StackPanel.

  • Content is arranged, or positioned, after all child objects have been measured.

  • The collection of child objects is drawn to the screen.

The layout pass process is invoked again if any of the following actions occur:

  • A child object is added to the collection.

  • A LayoutTransform is applied to the child object.

  • The UpdateLayout method is called for the child object.

  • When a change occurs to the value of a dependency property that is marked with metadata affecting the measure or arrange passes.

Use the Most Efficient Panel where Possible

The complexity of the layout process is directly based on the layout behavior of the Panel-derived elements you use. For example, a Grid or StackPanel control provides much more functionality than a Canvas control. The price for this greater increase in functionality is a greater increase in performance costs. However, if you do not require the functionality that a Grid control provides, you should use the less costly alternatives, such as a Canvas or a custom panel.

For more information, see Panels Overview.

Update Rather than Replace a RenderTransform

You may be able to update a Transform rather than replacing it as the value of a RenderTransform property. This is particularly true in scenarios that involve animation. By updating an existing Transform, you avoid initiating an unnecessary layout calculation.

Design

The construction of objects, particularly at run time, can affect the performance characteristics of your application.

Build Your Tree Top-Down

When a node is added or removed from the logical tree, property invalidations are raised on the node's parent and all its children. As a result, a top-down construction pattern should always be followed to avoid the cost of unnecessary invalidations on nodes that have already been validated. The following table shows the difference in execution speed between building a tree top-down versus bottom-up, where the tree is 150 levels deep with a single TextBlock and DockPanel at each level.

Action Tree building (in ms) Render—includes tree building (in ms)

Bottom-up

366

454

Top-down

11

96

The following code example demonstrates how to create a tree top down.

private void OnBuildTreeTopDown(object sender, RoutedEventArgs e)
{
    TextBlock textBlock = new TextBlock();
    textBlock.Text = "Default";

    DockPanel parentPanel = new DockPanel();
    DockPanel childPanel;

    myCanvas.Children.Add(parentPanel);
    myCanvas.Children.Add(textBlock);

    for (int i = 0; i < 150; i++)
    {
        textBlock = new TextBlock();
        textBlock.Text = "Default";
        parentPanel.Children.Add(textBlock);

        childPanel = new DockPanel();
        parentPanel.Children.Add(childPanel);
        parentPanel = childPanel;
    }
}

For more information on the logical tree, see Element Tree.

2D Graphics and Imaging

WPF provides a wide range of 2D graphics and imaging functionality that can be optimized for your application requirements.

Drawings and Shapes

WPF provides both Drawing and Shape objects to represent graphical drawing content. However, Drawing objects are simpler constructs than Shape objects and provide better performance characteristics.

A Shape allows you to draw a graphical shape to the screen. Because they are derived from the FrameworkElement class, Shape objects can be used inside panels and most controls.

WPF offers several layers of access to graphics and rendering services. At the top layer, Shape objects are easy to use and provide many useful features, such as layout and event handling. WPF provides a number of ready-to-use shape objects. All shape objects inherit from the Shape class. Available shape objects include Ellipse, Line, Path, Polygon, Polyline, and Rectangle.

Drawing objects, on the other hand, do not derive from the FrameworkElement class and provide a lighter-weight implementation for rendering shapes, images, and text.

There are four types of Drawing objects:

The GeometryDrawing object is used to render geometry content. The Geometry class and the concrete classes which derive from it, such as CombinedGeometry, EllipseGeometry, and PathGeometry, provide a means for rendering 2D graphics, as well as providing hit-testing and clipping support. Geometry objects can be used to define the region of a control, for example, or to define the clip region to apply to an image. Geometry objects can be simple regions, such as rectangles and circles, or composite regions created from two or more geometry objects. More complex geometric regions can be created by combining PathSegment-derived objects, such as ArcSegment, BezierSegment, and QuadraticBezierSegment.

On the surface, the Geometry class and the Shape class are quite similar. Both are used in the rendering of 2D graphics and both have similar concrete classes which derive from them, for example, EllipseGeometry and Ellipse. However, there are important differences between these two sets of classes. For one, the Geometry class lacks some of the functionality of the Shape class, such as the ability to draw itself. To draw a geometry object, another class such as DrawingContext, Drawing, or a Path (it is worth noting that a Path is a Shape) must be used to perform the drawing operation. Rendering properties such as fill, stroke, and the stroke thickness are on the class which draws the geometry object, while a shape object contains these properties. One way to think of this difference is that a geometry object defines a region, a circle for example, while a shape object defines a region, defines how that region is filled and outlined, and participates in the layout system.

Since Shape objects derive from the FrameworkElement class, using them can add significantly more memory consumption in your application. If you really do not need the FrameworkElement features for your graphical content, consider using the lighter-weight Drawing objects.

For more information on Drawing objects, see Drawing Objects Overview.

StreamGeometry Objects

The StreamGeometry object is a light-weight alternative to PathGeometry for creating geometric shapes. Use a StreamGeometry when you need to describe a complex geometry. StreamGeometry is optimized for handling many PathGeometry objects and performs better when compared to using many individual PathGeometry objects.

The following example uses attribute syntax to create a triangular StreamGeometry in XAML.

<Page xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
  <StackPanel>
  
    <Path Data="F0 M10,100 L100,100 100,50Z" 
      StrokeThickness="1" Stroke="Black"/>

  </StackPanel>
</Page>

For more information on StreamGeometry objects, see How to: Create a Shape Using a StreamGeometry.

DrawingVisual Objects

The DrawingVisual object is a lightweight drawing class that is used to render shapes, images, or text. This class is considered lightweight because it does not provide layout or event handling, which improves its performance. For this reason, drawings are ideal for backgrounds and clip art. For more information, see Using DrawingVisual Objects.

Images

WPF imaging provides a significant improvement over the imaging capabilities in previous versions of Windows. Imaging capabilities, such as displaying a bitmap or using an image on a common control were primarily handled by the Microsoft Windows Graphics Device Interface (GDI) or Microsoft Windows GDI+ application programming interface (API). These API provided baseline imaging functionality, but lacked features such as support for codec extensibility and high fidelity image support. WPF Imaging API have been redesigned to overcome the shortcomings of GDI and GDI+ and provide a new set of API to display and use images within your applications.

When using images, consider the following recommendations for gaining better performance:

  • If your application requires you to display thumbnail images, consider creating a reduced-sized version of the image. By default, WPF loads your image and decodes it to its full size. If you only want a thumbnail version of the image, WPF unnecessary decodes the image to its full-size and then scales it down to a thumbnail size. To avoid this unnecessary overhead, you can either request WPF to decode the image to a thumbnail size, or request WPF to load a thumbnail size image.

  • Always decode the image to desired size and not to the default size. As mentioned above, request WPF to decode your image to a desired size and not the default full size. You will reduce not only your application's working set, but execution speed as well.

  • If possible, combine the images into a single image, such as a film strip composed of multiple images.

  • For more information, see Imaging Overview.

BitmapScalingMode

When animating the scale of any bitmap, the default high-quality image re-sampling algorithm can sometimes consume sufficient system resources to cause frame rate degradation, effectively causing animations to stutter. By setting the BitmapScalingMode property of the RenderOptions object to LowQuality you can create a smoother animation when scaling a bitmap. LowQuality mode tells the WPF rendering engine to switch from a quality-optimized algorithm to a speed-optimized algorithm when processing images.

The following example shows how to set the BitmapScalingMode for an image object.

// Set the bitmap scaling mode for the image to render faster.
RenderOptions.SetBitmapScalingMode(MyImage, BitmapScalingMode.LowQuality);

CachingHint

By default, WPF does not cache the rendered contents of TileBrush objects, such as DrawingBrush and VisualBrush. In static scenarios where neither the contents nor use of the TileBrush in the scene is changing, this makes sense, since it conserves video memory. It does not make as much sense when a TileBrush with static content is used in a non-static way—for example, when a static DrawingBrush or VisualBrush is mapped to the surface of a rotating 3D object. The default behavior of WPF is to re-render the entire content of the DrawingBrush or VisualBrush for every frame, even though the content is unchanging.

By setting the CachingHint property of the RenderOptions object to Cache you can increase performance by using cached versions of the tiled brush objects.

The CacheInvalidationThresholdMinimum and CacheInvalidationThresholdMaximum property values are relative size values that determine when the TileBrush object should be regenerated due to changes in scale. For example, by setting the CacheInvalidationThresholdMaximum property to 2.0, the cache for the TileBrush only needs to be regenerated when its size exceeds twice the size of the current cache.

The following example shows how to use the caching hint option for a DrawingBrush.

// Set the minimum and maximum relative sizes for regenerating the tiled brush.
RenderOptions.SetCacheInvalidationThresholdMinimum(drawingBrush, 0.5);
RenderOptions.SetCacheInvalidationThresholdMaximum(drawingBrush, 2.0);

// The tiled brush will be regenerated when the size is
//   0.5x, 0.25x (and so forth)
// and
//   2x, 4x, 8x (and so forth)
// of the original size.

// Set the caching hint option for the brush.
RenderOptions.SetCachingHint(drawingBrush, CachingHint.Cache);

Object Behavior

Understanding the intrinsic behavior of WPF objects will help you make the right tradeoffs between functionality and performance.

Not Removing Event Handlers on Objects may Keep Objects Alive

The delegate that an object passes to its event is effectively a reference to that object. Therefore, event handlers can keep objects alive longer than expected. When performing clean up of an object that has registered to listen to an object's event, it is essential to remove that delegate before releasing the object. Keeping unneeded objects alive increases the application's memory usage. This is especially true when the object is the root of a logical tree or a visual tree.

WPF introduces a weak event listener pattern for events that can be useful in situations where the object lifetime relationships between source and listener are difficult to keep track of. Some existing WPF events use this pattern. If you are implementing objects with custom events, this pattern may be of use to you. For details, see WeakEvent Patterns.

There are several tools, such as the CLR Profiler and the Working Set Viewer, that can provides information on the memory usage of a specified process. The CLR Profiler includes a number of very useful views of the allocation profile, including a histogram of allocated types, allocation and call graphs, a time line showing garbage collections of various generations and the resulting state of the managed heap after those collections, and a call tree showing per-method allocations and assembly loads. For more information, see Microsoft .NET Framework Developer Center.

The Working Set Viewer is a WPF performance analysis tool that provides information on the memory usage of a specified process. This tool allows you to generate a snapshot of application memory usage information at a particular application state. For more information on WPF performance tools, see Performance Profiling Tools for WPF.

Dependency Properties and Objects

In general, accessing a dependency property of a DependencyObject is not slower than accessing a CLR property. While there is a small performance overhead for setting a property value, getting a value is as fast as getting the value from a CLR property. Offsetting the small performance overhead is the fact that dependency properties support robust features, such as data binding, animation, inheritance, and styling. For more information, see Dependency Properties Overview.

DependencyProperty Optimizations

You should define dependency properties in your application very carefully. If your DependencyProperty affects only render type metadata options, rather than other metadata options such as AffectsMeasure, you should mark it as such by overriding its metadata. For more information about overriding or obtaining property metadata, see Dependency Property Metadata.

It may be more efficient to have a property change handler invalidate the measure, arrange, and render passes manually if not all property changes actually affect measure, arrange, and render. For instance, you might decide to re-render a background only when a value is greater than a set limit. In this case, your property change handler would only invalidate render when the value exceeds the set limit.

Making a DependencyProperty Inheritable is Not Free

By default, registered dependency properties are non-inheritable. However, you can explicitly make any property inheritable. While this is a useful feature, converting a property to be inheritable impacts performance by increasing the length of time for property invalidation.

Use RegisterClassHandler Carefully

While calling RegisterClassHandler allows you to save your instance state, it is important to be aware that the handler is called on every instance, which can cause performance problems. Only use RegisterClassHandler when your application requires that you save your instance state.

Set the Default Value for a DependencyProperty during Registration

When creating a DependencyProperty that requires a default value, set the value using the default metadata passed as a parameter to the Register method of the DependencyProperty. Use this technique rather than setting the property value in a constructor or on each instance of an element.

Set the PropertyMetadata Value using Register

When creating a DependencyProperty, you have the option of setting the PropertyMetadata using either the Register or OverrideMetadata methods. Although your object could have a static constructor to call OverrideMetadata, this is not the optimal solution and will impact performance. For best performance, set the PropertyMetadata during the call to Register.

Freezable Objects

A Freezable is a special type of object that has two states: unfrozen and frozen. Freezing objects whenever possible improves the performance of your application and reduces its working set. For more information, see Freezable Objects Overview.

Each Freezable has a Changed event that is raised whenever it changes. However, change notifications are costly in terms of application performance.

Consider the following example in which each Rectangle uses the same Brush object:

rectangle_1.Fill = myBrush;
rectangle_2.Fill = myBrush;
rectangle_3.Fill = myBrush;
// ...
rectangle_10.Fill = myBrush;

By default, WPF provides an event handler for the SolidColorBrush object's Changed event in order to invalidate the Rectangle object's Fill property. In this case, each time the SolidColorBrush has to fire its Changed event it is required to invoke the callback function for each Rectangle—the accumulation of these callback function invocations impose a significant performance penalty. In addition, it is very performance intensive to add and remove handlers at this point since the application would have to traverse the entire list to do so. If your application scenario never changes the SolidColorBrush, you will be paying the cost of maintaining Changed event handlers unnecessarily.

Freezing a Freezable can improve its performance, because it no longer needs to expend resources on maintaining change notifications. The table below shows the size of a simple SolidColorBrush when its IsFrozen property is set to true, compared to when it is not. This assumes applying one brush to the Fill property of ten Rectangle objects.

State Size

Frozen SolidColorBrush

212 Bytes

Non-frozen SolidColorBrush

972 Bytes

The following code sample demonstrates this concept:

Brush frozenBrush = new SolidColorBrush(Colors.Blue);
frozenBrush.Freeze();
Brush nonFrozenBrush = new SolidColorBrush(Colors.Blue);

for (int i = 0; i < 10; i++)
{
    // Create a Rectangle using a non-frozed Brush.
    Rectangle rectangleNonFrozen = new Rectangle();
    rectangleNonFrozen.Fill = nonFrozenBrush;

    // Create a Rectangle using a frozed Brush.
    Rectangle rectangleFrozen = new Rectangle();
    rectangleFrozen.Fill = frozenBrush;
}

Changed Handlers on Unfrozen Freezables may Keep Objects Alive

The delegate that an object passes to a Freezable object's Changed event is effectively a reference to that object. Therefore, Changed event handlers can keep objects alive longer than expected. When performing clean up of an object that has registered to listen to a Freezable object's Changed event, it is essential to remove that delegate before releasing the object.

WPF also hooks up Changed events internally. For example, all dependency properties which take Freezable as a value will listen to Changed events automatically. The Fill property, which takes a Brush, illustrates this concept.

Brush myBrush = new SolidColorBrush(Colors.Red);
Rectangle myRectangle = new Rectangle();
myRectangle.Fill = myBrush;

On the assignment of myBrush to myRectangle.Fill, a delegate pointing back to the Rectangle object will be added to the SolidColorBrush object's Changed event. This means the following code does not actually make myRect eligible for garbage collection:

myRectangle = null;

In this case myBrush is still keeping myRectangle alive and will call back to it when it fires its Changed event. Note that assigning myBrush to the Fill property of a new Rectangle will simply add another event handler to myBrush.

The recommended way to clean up these types of objects is to remove the Brush from the Fill property, which will in turn remove the Changed event handler.

myRectangle.Fill = null;
myRectangle = null;

User Interface Virtualization

WPF also provides a variation of the StackPanel element that automatically "virtualizes" data-bound child content. In this context, the word virtualize refers to a technique by which a subset of UIElements are generated from a larger number of data items based upon which items are visible on-screen. It is intensive, both in terms of memory and processor, to generate a large number of UI elements when only a few may be on the screen at a given time. VirtualizingStackPanel (through functionality provided by VirtualizingPanel) calculates visible items and works with the ItemContainerGenerator from an ItemsControl (such as ListBox or ListView) to only create UIElements for visible items.

As a performance optimization, visual objects for these items are only generated or kept alive if they are visible on the screen. When they are no longer in the viewable area of the control, the visual objects may be removed. This is not to be confused with data virtualization, where data objects are not all present in the local collection- rather streamed in as needed.

The table below shows the elapsed time adding and rendering 5000 TextBlock elements to a StackPanel and a VirtualizingStackPanel. In this scenario, the measurements represent the time between attaching a text string to the ItemsSource property of an ItemsControl object to the time when the panel elements display the text string.

Host panel Render time (ms)

StackPanel

3210

VirtualizingStackPanel

46

Application Resources

WPF allows you to share application resources so that you can support a consistent look or behavior across similar-typed elements. For more information on resources, see Resources Overview.

Sharing resources

If your application uses custom controls and defines resources in a ResourceDictionary (or XAML Resources node), it is recommended that you either define the resources at the Application or Window object level, or define them in the default theme for the custom controls. Defining resources in a custom control's ResourceDictionary imposes a performance impact for every instance of that control. For example, if you have performance-intensive brush operations defined as part of the resource definition of a custom control and many instances of the custom control, the application's working set will increase significantly.

To illustrate this point, consider the following. Let's say you are developing a card game using WPF. For most card games, you need 52 cards with 52 different faces. You decide to implement a card custom control and you define 52 brushes (each representing a card face) in the resources of your card custom control. In your main application, you initially create 52 instances of this card custom control. Each instance of the card custom control generates 52 instances of Brush objects, which gives you a total of 52 * 52 Brush objects in your application. By moving the brushes out of the card custom control resources to the Application or Window object level, or defining them in the default theme for the custom control, you reduce the working set of the application, since you are now sharing the 52 brushes among 52 instances of the card control.

Sharing a Brush without Copying

If you have multiple elements using the same Brush object, define the brush as a resource and reference it, rather than defining the brush inline in XAML. This method will create one instance and reuse it, whereas defining brushes inline in XAML creates a new instance for each element.

The following markup sample illustrates this point:

<StackPanel.Resources>
  <LinearGradientBrush x:Key="myBrush" StartPoint="0,0.5" EndPoint="1,0.5" Opacity="0.5">
    <LinearGradientBrush.GradientStops>
      <GradientStopCollection>
        <GradientStop Color="GoldenRod" Offset="0" />
        <GradientStop Color="White" Offset="1" />
      </GradientStopCollection>
    </LinearGradientBrush.GradientStops>
  </LinearGradientBrush>
</StackPanel.Resources>

<!-- Non-shared Brush object. -->
<Label>
  Label 1
  <Label.Background>
    <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5" Opacity="0.5">
      <LinearGradientBrush.GradientStops>
        <GradientStopCollection>
          <GradientStop Color="GoldenRod" Offset="0" />
          <GradientStop Color="White" Offset="1" />
        </GradientStopCollection>
      </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>
  </Label.Background>
</Label>

<!-- Shared Brush object. -->
<Label Background="{StaticResource myBrush}">Label 2</Label>
<Label Background="{StaticResource myBrush}">Label 3</Label>

Use Static Resources when Possible

A static resource provides a value for any XAML property attribute by looking up a reference to an already defined resource. Lookup behavior for that resource is analogous to compile-time lookup.

A dynamic resource, on the other hand, will create a temporary expression during the initial compilation and thus defer lookup for resources until the requested resource value is actually required in order to construct an object. Lookup behavior for that resource is analogous to run-time lookup, which imposes a performance impact. Use static resources whenever possible in your application, using dynamic resources only when necessary.

The following markup sample shows the use of both types of resources:

<StackPanel.Resources>
  <SolidColorBrush x:Key="myBrush" Color="Teal"/>
</StackPanel.Resources>

<!-- StaticResource reference -->
<Label Foreground="{StaticResource myBrush}">Label 1</Label>

<!-- DynamicResource reference -->
<Label Foreground="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">Label 2</Label>

Text

WPF includes support for the presentation of text content through the use of feature-rich user interface (UI) controls. In general you can divide text rendering in three layers:

  1. Using the Glyphs and GlyphRun objects directly.

  2. Using the FormattedText object.

  3. Using high-level controls, such as the TextBlock and FlowDocument objects.

Rendering Text at the Glyph Level

Windows Presentation Foundation (WPF) provides advanced text support including glyph-level markup with direct access to Glyphs for customers who want to intercept and persist text after formatting. These features provide critical support for the different text rendering requirements in each of the following scenarios.

  • Screen display of fixed-format documents.

  • Print scenarios.

    • Extensible Application Markup Language (XAML) as a device printer language.

    • Microsoft XPS Document Writer.

    • Previous printer drivers, output from Win32 applications to the fixed format.

    • Print spool format.

  • Fixed-format document representation, including clients for previous versions of Windows and other computing devices.

NoteNote:

Glyphs and GlyphRun are designed for fixed-format document presentation and print scenarios. Windows Presentation Foundation (WPF) provides several elements for general layout and user interface (UI) scenarios such as Label and TextBlock. For more information on layout and UI scenarios, see the Typography in Windows Presentation Foundation.

The following examples show how to define properties for a Glyphs object in Extensible Application Markup Language (XAML). The Glyphs object represents the output of a GlyphRun in XAML. The examples assume that the Arial, Courier New, and Times New Roman fonts are installed in the C:\WINDOWS\Fonts folder on the local computer.

<!-- The example shows how to use a Glyphs object. -->
<Page
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  >

   <StackPanel Background="PowderBlue">

      <Glyphs
         FontUri             = "C:\WINDOWS\Fonts\TIMES.TTF"
         FontRenderingEmSize = "100"
         StyleSimulations    = "BoldSimulation"
         UnicodeString       = "Hello World!"
         Fill                = "Black"
         OriginX             = "100"
         OriginY             = "200"
      />

   </StackPanel>
</Page>

Using DrawGlyphRun

If you have custom control and you want to render glyphs, use the DrawGlyphRun method.

WPF also provides lower-level services for custom text formatting through the use of the FormattedText object. The most efficient way of rendering text in Windows Presentation Foundation (WPF) is by generating text content at the glyph level using Glyphs and GlyphRun. However, the cost of this efficiency is the loss of easy to use rich text formatting, which are built-in features of Windows Presentation Foundation (WPF) controls, such as TextBlock and FlowDocument.

FormattedText Object

The FormattedText object allows you to draw multi-line text, in which each character in the text can be individually formatted. For more information, see Drawing Formatted Text.

To create formatted text, call the FormattedText constructor to create a FormattedText object. Once you have created the initial formatted text string, you can apply a range of formatting styles. If your application wants to implement its own layout, then the FormattedText object is better choice than using a control, such as TextBlock. For more information on the FormattedText object, see Drawing Formatted Text .

The FormattedText object provides low-level text formatting capability. You can apply multiple formatting styles to one or more characters. For example, you could call both the SetFontSize and SetForegroundBrush methods to change the formatting of the first five characters in the text.

The following code example creates a FormattedText object and renders it.

protected override void OnRender(DrawingContext drawingContext)
{
    string testString = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor";

    // Create the initial formatted text string.
    FormattedText formattedText = new FormattedText(
        testString,
        CultureInfo.GetCultureInfo("en-us"),
        FlowDirection.LeftToRight,
        new Typeface("Verdana"),
        32,
        Brushes.Black);

    // Set a maximum width and height. If the text overflows these values, an ellipsis "..." appears.
    formattedText.MaxTextWidth = 300;
    formattedText.MaxTextHeight = 240;

    // Use a larger font size beginning at the first (zero-based) character and continuing for 5 characters.
    // The font size is calculated in terms of points -- not as device-independent pixels.
    formattedText.SetFontSize(36 * (96.0 / 72.0), 0, 5);

    // Use a Bold font weight beginning at the 6th character and continuing for 11 characters.
    formattedText.SetFontWeight(FontWeights.Bold, 6, 11);

    // Use a linear gradient brush beginning at the 6th character and continuing for 11 characters.
    formattedText.SetForegroundBrush(
                            new LinearGradientBrush(
                            Colors.Orange,
                            Colors.Teal,
                            90.0),
                            6, 11);

    // Use an Italic font style beginning at the 28th character and continuing for 28 characters.
    formattedText.SetFontStyle(FontStyles.Italic, 28, 28);

    // Draw the formatted text string to the DrawingContext of the control.
    drawingContext.DrawText(formattedText, new Point(10, 0));
}

FlowDocument, TextBlock, and Label Controls

WPF includes multiple controls for drawing text to the screen. Each control is targeted to a different scenario and has its own list of features and limitations.

FlowDocument Impacts Performance More than TextBlock or Label

In general, the TextBlock element should be used when limited text support is required, such as a brief sentence in a user interface (UI). Label can be used when minimal text support is required. The FlowDocument element is a container for re-flowable documents that support rich presentation of content, and therefore, has a greater performance impact than using the TextBlock or Label controls.

For more information on FlowDocument, see Flow Document Overview.

Avoid Using TextBlock in FlowDocument

The TextBlock element is derived from UIElement. The Run element is derived from TextElement, which is less costly to use than a UIElement-derived object. When possible, use Run rather than TextBlock for displaying text content in a FlowDocument.

The following markup sample illustrates two ways of setting text content within a FlowDocument:

<FlowDocument>

  <!-- Text content within a Run (more efficient). -->
  <Paragraph>
    <Run>Line one</Run>
  </Paragraph>

  <!-- Text content within a TextBlock (less efficient). -->
  <Paragraph>
    <TextBlock>Line two</TextBlock>
  </Paragraph>

</FlowDocument>

Avoid Using TextBlock to Set Text Properties

In general, using a Run within a TextBlock is more performance intensive than not using an explicit Run object at all. If you are using a Run in order to set text properties, set those properties directly on the TextBlock instead.

The following markup sample illustrates these two ways of setting a text property, in this case, the FontWeight property:

<!-- Run is used to set text properties. -->
<TextBlock>
  <Run FontWeight="Bold">Hello, world</Run>
</TextBlock>

<!-- TextBlock is used to set text properties, which is more efficient. -->
<TextBlock FontWeight="Bold">
  Hello, world
</TextBlock>

The following table shows the cost of displaying 1000 TextBlock objects with and without an explicit Run.

TextBlock type Creation time (ms) Render time (ms)

Run setting text properties

146

540

TextBlock setting text properties

43

453

Avoid Databinding to the Label.Content Property

Imagine a scenario where you have a Label object that is updated frequently from a String source. When data binding the Label element's Content property to the String source object, you may experience poor performance. Each time the source String is updated, the old String object is discarded and a new String is recreated—because a String object is immutable, it cannot be modified. This, in turn, causes the ContentPresenter of the Label object to discard its old content and regenerate the new content to display the new String.

The solution to this problem is simple. If the Label is not set to a custom ContentTemplate value, replace the Label with a TextBlock and data bind its Text property to the source string.

Data bound property Update time (ms)

Label.Content

835

TextBlock.Text

242

The Hyperlink object is an inline-level flow content element that allows you to host hyperlinks within the flow content.

You can optimize the use of multiple Hyperlink elements by grouping them together within the same TextBlock. This helps to minimize the number of objects you create in your application. For example, you may want to display multiple hyperlinks, such as the following:

MSN Home | My MSN

The following markup example shows multiple TextBlock elements used to display the hyperlinks:

<!-- Hyperlinks in separate TextBlocks. -->
<TextBlock>
  <Hyperlink TextDecorations="None" NavigateUri="https://www.msn.com">MSN Home</Hyperlink>
</TextBlock>

<TextBlock Text=" | "/>

<TextBlock>
  <Hyperlink TextDecorations="None" NavigateUri="http://my.msn.com">My MSN</Hyperlink>
</TextBlock>

The following markup example shows a more efficient way of displaying the hyperlinks, this time, using a single TextBlock:

<!-- Hyperlinks combined in the same TextBlock. -->
<TextBlock>
  <Hyperlink TextDecorations="None" NavigateUri="https://www.msn.com">MSN Home</Hyperlink>
  
  <Run Text=" | " />
  
  <Hyperlink TextDecorations="None" NavigateUri="http://my.msn.com">My MSN</Hyperlink>
</TextBlock>

A TextDecoration object is a visual ornamentation that you can add to text; however, it can be performance intensive to instantiate. If you make extensive use of Hyperlink elements, consider showing an underline only when triggering an event, such as the MouseEnter event. For more information, see How to: Use a Text Decoration with a Hyperlink.


Hyperlink appearing on MouseEnter

Hyperlinks displaying TextDecorations

The following markup sample shows a Hyperlink defined with and without an underline:

<!-- Hyperlink with default underline. -->
<Hyperlink NavigateUri="https://www.msn.com">
  MSN Home
</Hyperlink>

<Run Text=" | " />

<!-- Hyperlink with no underline. -->
<Hyperlink Name="myHyperlink" TextDecorations="None"
           MouseEnter="OnMouseEnter"
           MouseLeave="OnMouseLeave"
           NavigateUri="https://www.msn.com">
  My MSN
</Hyperlink>

The following table shows the performance cost of displaying 1000 Hyperlink elements with and without an underline.

Hyperlink Creation time (ms) Render time (ms)

With underline

289

1130

Without underline

299

776

Text Formatting Features

WPF provides rich text formatting services, such as automatic hyphenations. These services may impact application performance and should only be used when needed.

Avoid Unnecessary Use of Hyphenation

Automatic hyphenation finds hyphen breakpoints for lines of text, and allows additional break positions for lines in TextBlock and FlowDocument objects. By default, the automatic hyphenation feature is disabled in these objects. You can enable this feature by setting the object's IsHyphenationEnabled property to true. However, enabling this feature causes WPF to initiate Component Object Model (COM) interoperability, which can impact application performance. It is recommended that you do not use automatic hyphenation unless you need it.

Use Figures Carefully

A Figure element represents a portion of flow content that can be absolutely-positioned within a page of content. In some cases, a Figure may cause an entire page to automatically reformat if its position collides with content that has already been laid-out. You can minimize the possibility of unnecessary reformatting by either grouping Figure elements next to each other, or declaring them near the top of content in a fixed page size scenario.

Optimal Paragraph

The optimal paragraph feature of the FlowDocument object lays out paragraphs so that white space is distributed as evenly as possible. By default, the optimal paragraph feature is disabled. You can enable this feature by setting the object's IsOptimalParagraphEnabled property to true. However, enabling this feature impacts application performance. It is recommended that you do not use the optimal paragraph feature unless you need it.

Data Binding

Windows Presentation Foundation (WPF) data binding provides a simple and consistent way for applications to present and interact with data. Elements can be bound to data from a variety of data sources in the form of CLR objects and XML. For more information on data binding, see Data Binding Overview.

How Data Binding References are Resolved

Before discussing data binding performance issues, it is worthwhile to explore how the Windows Presentation Foundation (WPF) data binding engine resolves object references for binding.

The source of a Windows Presentation Foundation (WPF) data binding can be any CLR object. You can bind to properties, sub-properties, or indexers of a CLR object. The binding references are resolved by using either Microsoft .NET Framework version 3.0 reflection or an ICustomTypeDescriptor. Here are three methods for resolving object references for binding.

The first method involves using reflection. In this case, the PropertyInfo object is used to discover the attributes of the property and provides access to property metadata. When using the ICustomTypeDescriptor interface, the data binding engine uses this interface to access the property values. The ICustomTypeDescriptor interface is especially useful in cases where the object does not have a static set of properties.

Property change notifications can be provided either by implementing the INotifyPropertyChanged interface or by using the change notifications associated with the TypeDescriptor. However, the preferred strategy for implementing property change notifications is to use INotifyPropertyChanged.

If the source object is a CLR object and the source property is a CLR property, the Windows Presentation Foundation (WPF) data binding engine has to first use reflection on the source object to get the TypeDescriptor, and then query for a PropertyDescriptor. This sequence of reflection operations is potentially very time-consuming from a performance perspective.

The second method for resolving object references involves a CLR source object that implements the INotifyPropertyChanged interface, and a source property that is a CLR property. In this case, the data binding engine uses reflection directly on the source type and gets the required property. This is still not the optimal method, but it will cost less in working set requirements than the first method.

The third method for resolving object references involves a source object that is a DependencyObject and a source property that is a DependencyProperty. In this case, the data binding engine does not need to use reflection. Instead, the property engine and the data binding engine together resolve the property reference independently. This is the optimal method for resolving object references used for data binding.

The table below compares the speed of data binding the Text property of one thousand TextBlock elements using these three methods.

Binding the Text property of a TextBlock Binding time (ms) Render time -- includes binding (ms)

To a property of a CLR object

115

314

To a property of a CLR object which implements INotifyPropertyChanged

115

305

To a DependencyProperty of a DependencyObject.

90

263

Binding to Large CLR Objects

There is a significant performance impact when you data bind to a single CLR object with thousands of properties. You can minimize this impact by dividing the single object into multiple CLR objects with fewer properties. The table shows the binding and rendering times for data binding to a single large CLR object versus multiple smaller objects.

Data binding 1000 TextBlock objects Binding time (ms) Render time -- includes binding (ms)

To a CLR object with 1000 properties

950

1200

To 1000 CLR objects with one property

115

314

Binding to an ItemsSource

Consider a scenario in which you have a CLR List object that holds a list of employees that you want to display in a ListBox. To create a correspondence between these two objects, you would bind your employee list to the ItemsSource property of the ListBox. However, suppose you have a new employee joining your group. You might think that in order to insert this new person into your bound ListBox values, you would simply add this person to your employee list and expect this change to be recognized by the data binding engine automatically. That assumption would prove false; in actuality, the change will not be reflected in the ListBox automatically. This is because the CLR List object does not automatically raise a collection changed event. In order to get the ListBox to pick up the changes, you would have to recreate your list of employees and re-attach it to the ItemsSource property of the ListBox. While this solution works, it introduces a huge performance impact. Each time you reassign the ItemsSource of ListBox to a new object, the ListBox first throws away its previous items and regenerates its entire list. The performance impact is magnified if your ListBox maps to a complex DataTemplate.

A very efficient solution to this problem is to make your employee list an ObservableCollection. An ObservableCollection object raises a change notification which the data binding engine can receive. The event adds or removes an item from an ItemsControl without the need to regenerate the entire list.

The table below shows the time it takes to update the ListBox (with UI virtualization turned off) when one item is added. The number in the first row represents the elapsed time when the CLR List object is bound to ListBox element's ItemsSource. The number in the second row represents the elapsed time when an ObservableCollection is bound to the ListBox element's ItemsSource. Note the significant time savings using the ObservableCollection data binding strategy.

Data binding the ItemsSource Update time for 1 item (ms)

To a CLR List object

1656

To an ObservableCollection

20

Bind IList to ItemsControl not IEnumerable

If you have a choice between binding an IList or an IEnumerable to an ItemsControl object, choose the IList object. Binding IEnumerable to an ItemsControl forces WPF to create a wrapper IList object, which means your performance is impacted by the unnecessary overhead of a second object.

Do not Convert CLR objects to XML Just for Data Binding.

WPF allows you to data bind to XML content; however, data binding to XML content is slower than data binding to CLR objects. Do not convert CLR object data to XML if the only purpose is for data binding.

Other Performance Recommendations

Opacity on Brushes versus Opacity on Elements

When you use a Brush to set the Fill or Stroke of an element, it is better to set the Brush.Opacity value rather than the setting the element's Opacity property. Modifying an element's Opacity property can cause WPF to create a temporary surface.

The NavigationWindow object derives from Window and extends it with content navigation support, primarily by aggregating NavigationService and the journal. You can update the client area of NavigationWindow by specifying either a uniform resource identifier (URI) or an object. The following sample shows both methods:

private void buttonGoToUri(object sender, RoutedEventArgs args)
{
    navWindow.Source = new Uri("NewPage.xaml", UriKind.RelativeOrAbsolute);
}

private void buttonGoNewObject(object sender, RoutedEventArgs args)
{
    NewPage nextPage = new NewPage();
    nextPage.InitializeComponent();
    navWindow.Content = nextPage;
}

Each NavigationWindow object has a journal that records the user's navigation history in that window. One of the purposes of the journal is to allow users to retrace their steps.

When you navigate using a uniform resource identifier (URI), the journal stores only the uniform resource identifier (URI) reference. This means that each time you revisit the page, it is dynamically reconstructed, which may be time consuming depending on the complexity of the page. In this case, the journal storage cost is low, but the time to reconstitute the page is potentially high.

When you navigate using an object, the journal stores the entire visual tree of the object. This means that each time you revisit the page, it renders immediately without having to be reconstructed. In this case, the journal storage cost is high, but the time to reconstitute the page is low.

When you use the NavigationWindow object, you will need to keep in mind how the journaling support impacts your application's performance. For more information, see Navigation Overview.

Hit Testing on Large 3D Surfaces

Hit testing on large 3D surfaces is a very performance intensive operation in terms of CPU consumption. This is especially true when the 3D surface is animating. If you do not require hit testing on these surfaces, then disable hit testing. Objects that are derived from UIElement can disable hit testing by setting the IsHitTestVisible property to false.

Avoid Using ScrollBarVisibility=Auto

Whenever possible, avoid using the ScrollBarVisibility value Auto for the HorizontalScrollBarVisibility and VerticalScrollBarVisibility properties. These properties are defined for RichTextBox, ScrollViewer, TextBox objects, and as an attached property for the ListBox object. Instead, set ScrollBarVisibility to Disabled, Hidden, or Visible.

The Auto value is intended for cases when space is limited and scrollbars should only be displayed when necessary. For example, it may be useful to use this ScrollBarVisibility value with a ListBox of 30 items as opposed to a TextBox with hundreds of lines of text.

WPF Performance Tools and Resources

WPF provides a suite of performance profiling tools that allow you to analyze the run-time behavior of your application and determine the types of performance optimizations you can apply. The following table lists the five performance profiling tools that are included in the Windows SDK tool, WPFPerf:

Tool Description

Event Trace

Use for analyzing events and generating event log files.

Perforator

Use for analyzing rendering behavior.

Trace Viewer

Record, display, and browse Event Tracing for Windows (ETW) log files in a WPF user-interface format.

Visual Profiler

Use for profiling the use of WPF services, such as layout and event handling, by elements in the visual tree.

Working Set Viewer

Use for analyzing the working set characteristics of your application.

The Visual Profiler tool suite provides a rich, graphical view of performance data. In this screenshot, the CPU Usage section of the Visual Profiler gives you a precise breakdown of an object's use of WPF services, such as rendering and layout.


Visual Profiler display output

Visual Profiler display output

For more information on WPF performance tools, see Performance Profiling Tools for WPF.

Viewing the Visual Tree with XamlPad

Analyzing the visual tree hierarchy using XAMLPad may give you insight into how control template expansion works. This knowledge may help you understand the performance costs and tradeoffs of the user interface design you are creating.

XamlPad provides an option for viewing and exploring the visual tree that corresponds to the currently defined XAML content. Click the Show Visual Tree button on the menu bar to display the visual tree. The following illustrates the expansion of XAML content into visual tree nodes in the Visual Tree Explorer panel of XamlPad:


Visual Tree Explorer panel in XamlPad

Visual Tree Explorer panel in XamlPad

Notice how the Label, TextBox, and Button controls each display a separate visual object hierarchy in the Visual Tree Explorer panel of XamlPad. This is because WPF controls have a ControlTemplate that contains the visual tree of that control. When you explicitly reference a control, you implicitly reference its visual hierarchy. For more information on visual objects and the visual tree, see Windows Presentation Foundation Graphics Rendering Overview.

You can view the property settings of an item in the Visual Tree Explorer by selecting the item. The Property Tree Explorer panel, below the Visual Tree Explorer panel, displays the current property settings for the selected visual object.


Property Tree Explorer panel in XamlPad

Property Tree Explorer

For more information, see XAMLPad.

Debug Tracing Support for WPF

The PresentationTraceSources class provides debug tracing support that specifically targets Windows Presentation Foundation (WPF) applications. Tracing is a diagnostics system by which an application's progression can be tracked. The tracing statements report information, much the way the WriteLine method is often used. However, tracing statements can be switched on or off by using a configuration file. In addition, the output of tracing statements can be customized.

For other related .NET Framework 3.0 diagnostic classes, see System.Diagnostics.

See Also

Reference

RenderOptions
RenderCapability

Concepts

Graphics Rendering Tiers
Windows Presentation Foundation Graphics Rendering Overview
The Layout System
Element Tree
Drawing Objects Overview
Using DrawingVisual Objects
Dependency Properties Overview
Freezable Objects Overview
Resources Overview
Documents in Windows Presentation Foundation
Drawing Formatted Text
Typography in Windows Presentation Foundation
Data Binding Overview
Navigation Overview

Other Resources

Performance Profiling Tools for WPF