WPF Performance - TextBlock

March 12, 2017

WPF typically doesn’t have many performance issues and, where it does, this can usually be fixed by virtualisation. Having said that, even if you never need to use this, it’s useful to know that you can eek that last ounce of performance out of the system.

Here’s a basic program that contains a TextBlock:

<Window x:Class="TextBlockTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TextBlockTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        x:Name="MainWindowView">
    <Grid>
        <ScrollViewer>
            <ItemsControl ItemsSource="{Binding BigList, ElementName=MainWindowView}" Margin="0,-1,0,1">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding}"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </Grid>
</Window>

Code behind:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace TextBlockTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public ObservableCollection<string> BigList { get; set; }

        public MainWindow()
        {
            BigList = new ObservableCollection<string>();
            for (int i = 0; i <= 10000; i++)
            {
                BigList.Add($"Item {i}");
            }

            InitializeComponent();
        }
    }
}

Let’s, for a minute, imagine this is slow, and profile it:

textblockpref1

The layout is taking most of the time. Remember that each control needs to be created, and remember that the TextBlock does slightly more than just display text:

textblockperf2

The whole panel took 3.46s. Not terrible, performance, but can it be improved? The answer is: yes, it can! Very, very slightly.

Let’s create a Custom Control:



using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace FastTextBlock
{
   
    public class MyTextBlockTest : Control
    {
        private FormattedText \_formattedText;

        static MyTextBlockTest()
        {
            //DefaultStyleKeyProperty.OverrideMetadata(typeof(MyTextBlockTest), new FrameworkPropertyMetadata(typeof(MyTextBlockTest)));
        }

        public static readonly DependencyProperty TextProperty =
             DependencyProperty.Register(
                 "Text", 
                 typeof(string),
                 typeof(MyTextBlockTest), 
                 new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure,
                    (o, e) => ((MyTextBlockTest)o).TextPropertyChanged((string)e.NewValue)));

        private void TextPropertyChanged(string text)
        {
            var typeface = new Typeface(
                new FontFamily("Times New Roman"),
                FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);

            \_formattedText = new FormattedText(
                text, CultureInfo.CurrentCulture,
                FlowDirection.LeftToRight, typeface, 15, Brushes.Black);
        }


        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            if (\_formattedText != null)
            {
                drawingContext.DrawText(\_formattedText, new Point());
            }
        }

        protected override Size MeasureOverride(Size constraint)
        {
            //return base.MeasureOverride(constraint);

            return \_formattedText != null
            ? new Size(\_formattedText.Width, \_formattedText.Height)
            : new Size();
        }
    }
}

Here’s the new XAML:

    <Grid>
        <ScrollViewer>
            <ItemsControl ItemsSource="{Binding BigList, ElementName=MainWindowView}" Margin="0,-1,0,1">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <!--<TextBlock Text="{Binding}"/>-->
                        <controls:MyTextBlockTest Text="{Binding}" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </Grid>

Shaves around 10ms off the time:

textblockperf3

Even more time can be shaved by moving up an element (that is, inheriting from a more base class than `Control`. In fact, `Control` inherits from `FrameworkElement`:

public class MyTextBlockTest : FrameworkElement

Shaves another 10ms off:

textblockperf4

Conclusion

Clearly, this isn’t a huge performance boost, and in 99% of use cases, this would be massively premature optimisation. However, the time that this really comes into its own is where you have a compound control (a control that consists of other controls), and you have lots of them (hundreds). See my next post for details.

References:

https://social.msdn.microsoft.com/Forums/en-US/94ddd25e-7093-4986-b8c8-b647924251f6/manual-rendering-of-a-wpf-user-control?forum=wpf

http://www.codemag.com/article/100023

http://stackoverflow.com/questions/20338044/how-do-i-make-a-custom-uielement-derived-class-that-contains-and-displays-othe

http://stackoverflow.com/questions/42494455/wpf-custom-control-inside-itemscontrol



Profile picture

A blog about one man's journey through code… and some pictures of the Peak District
Twitter

© Paul Michaels 2024