Are you a WPF developer? Do your WPF apps have areas of poor performance or don’t run as quickly as you would like? If so, I have 15 tips to help you identify and improve the performance of your WPF applications.
While WPF is over a decade old and has been improved greatly over the years, there are still several areas that can suffer from poor performance. The reasons for this poor performance include things such as bad coding practices, broken bindings, complex layouts, the lack of UI virtualization, and much more. Luckily, with a little planning and a solid understanding of the WPF platform, you can have your WPF apps jumping into warp speed and leaping across the universe in milliseconds.
I have put together these 15 tips to help you improve the performance of your WPF apps.
1. Simplify your Visual Tree
A common source of performance issues is a deep and complex layout. Keep your XAML markup as simple and shallow as possible. When UI elements are drawn onscreen, a “layout pass” is called twice for each element (a measure pass and an arrange pass). The layout pass is a mathematically-intensive process—the larger the number of children in the element, the greater the number of calculations required.
2. Virtualize your ItemsControls
As mentioned earlier, a complex and deep visual tree results in a larger memory footprint and slower performance. ItemsControls usually increase performance problems with deep visual trees because they are not virtualized. This means they are constantly being created and destroyed for each item in the control. Instead, use the VirtualizingStackPanel as the items host and make use of the VirtualizingStackPanel.IsVirtualizing and set the VirtualizationMode to Recycling in order to reuse item containers instead of creating new ones each time.
3. Favor StaticResources over DynamicResources
StaticResources provide values for any XAML property attribute by looking up a reference to an already defined resource. Lookup behavior for that resource is the same as a compile-time lookup. DynamicResources will create a temporary expression and defer lookup for resources until the requested resource value is required. Lookup behavior for that resource is the same as a run-time lookup, which imposes a performance impact. Always use a StaticResource whenever possible.
4. Opacity on Brushes Instead of Elements
If you use a Brush to set the Fill or Stroke of an element, it is better to set the Opacity on the Brush rather than setting the element’s Opacity property. When you modify an element’s Opacity property, it can cause WPF to create temporary surfaces which results in a performance hit.
5. Avoid Using Run to Set Text Properties
Avoid using Runs within a TextBlock as this results in a much higher performance intensive operation. If you are using a Run to set text properties, set those directly on the TextBlock instead.
6. Favor StreamGeometries over PathGeometries
The StreamGeometry object is a very light-weight alternative to a PathGeometry. StreamGeometry is optimized for handling many PathGeometry objects. It consumes less memory and performs much better when compared to using many PathGeometry objects.
7. Use Reduced Image Sizes
If your app requires the display of smaller thumbnails, consider creating reduced-sized versions of your images. By default, WPF will load and decode your image to its full size. This can be the source of many performance problems if you are loading full images and scaling them down to thumbnail sizes in controls such as an ItemsControl. If possible, combine all images into a single image, such as a film strip composed of multiple images.
8. Lower the BitMapScalingMode
By default, WPF uses a high-quality image re-sampling algorithm that can sometimes consume system resources which results in frame rate degradation and causes animations to stutter. Instead, set the BitMapScalingMode to LowQuality to switch from a “quality-optimized” algorithm to a “speed-optimized” algorithm.
9. Use and Freeze Freezables
A Freezable is a special type of object that has two states: unfrozen and frozen. When you freeze an object such as a Brush or Geometry, it can no longer be modified. Freezing objects whenever possible improves the performance of your application and reduces its memory consumption.
10. Fix your Binding Errors
Binding errors are the most common type of performance problem in WPF apps. Every time a binding error occurs, your app takes a perf hit and as it tries to resolve the binding and writes the error out to the trace log. As you can imagine, the more binding errors you have the bigger the performance hit your app will take. Take the time to find and fix all your binding errors. Using a RelativeSource binding in DataTemplates is a major culprit in binding error as the binding is usually not resolved properly until the DataTempate has completed its initialization. Avoid using RelativeSource.FindAncestor at all costs. Instead, define an attached property and use property inheritance to push values down the visual tree instead of looking up the visual tree.
11. Avoid Databinding to the Label.Content Property
If you are using a Label to data bind to a String property, this will result in poor performance. This is because each time the String source is updated, the old string object is discarded, and a new String is created. If the Content of the Label is simple text, replace it with a TextBlock and bind to the Text property instead.
12. Bind ItemsControls to IList instead of IEnumerable
When data binding an ItemsControl to an IEnumerable, WPF will create a wrapper of type IList<T> which negatively impacts performance with the creation of a second object. Instead, bind the ItemsControl directly to an IList to avoid the overhead of the wrapper object.
13. Use the NeutralResourcesLanguage Attribute
Use the NeutralResourcesLanguageAttribute to tell the ResourceManager what the neutral culture is and avoid unsuccessful satellite assembly lookups.
14. Load Data on Separate Threads
A very common source of performance problems, UI freezes, and apps that stop responding is how you load your data. Make sure you are asynchronously loading your data on a separate thread as to not overload the UI thread. Loading data on the UI thread will result in very poor performance and an overall bad end-user experience. Multi-threading should be something every WPF developer is using in their applications.
15. Beware of Memory Leaks
Memory leaks are the number one cause of performance problems in most WPF applications. They are easy to have but can be difficult to find. For example, using the DependencyPropertyDescriptor.AddValueChanged can cause the WPF framework to take a strong reference to the source of the event that isn’t removed until you manually call DependencyPropertyDescriptor.RemoveValueChanged. If your views or behaviors rely on events being raised from an object or ViewModel (such as INotifyPropertyChanged), subscribe to them weakly or make sure you are manually unsubscribing. Also, if you are binding to properties in a ViewModel which does not implement INotifyPropertyChanged, chances are you have a memory leak.
Finally, a bonus tip. Sometimes when you have a performance problem it can be very difficult to identify what exactly is causing the issue. I suggest using an application performance profiler to help identify where these performance bottle necks are occurring in your code base. There are a lot of profiler options available to you. Some are paid, and some are free. The one I personally use the most is the Diagnosis Tools built directly into Visual Studio 2019.
Be sure to engage with me on Twitter, subscribe to my YouTube channel to be notified of new video content, and follow me on Twitch to watch me stream live.