Enhancing the performance of textual output

WPF provides similar options for creating text as it does for drawing shapes; the more versatile the output method, the easier it is to use, but the less efficient it is and vice versa. The vast majority of us opt for the simplest, but least efficient, method of using the high-level TextBlock or Label elements.

While this doesn't typically cause us any problems when used in typical forms, there is definitely room for improvement when displaying thousands of text blocks in a data grid, or other collection control. If we require formatted text, we can utilize the more efficient FormattedText object; otherwise, we can use the lowest-level method and the most efficient Glyphs elements.

Let's look at an example:

<UserControl x:Class="CompanyName.ApplicationName.Views.TextView" 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  xmlns:Controls=
    "clr-namespace:CompanyName.ApplicationName.Views.Controls" 
  Height="250" Width="325"> 
  <Grid ShowGridLines="True"> 
    <Grid.RowDefinitions> 
      <RowDefinition /> 
      <RowDefinition /> 
      <RowDefinition /> 
      <RowDefinition /> 
    </Grid.RowDefinitions> 
    <Label Content="Quite Efficient" FontFamily="Times New Roman"  
      FontSize="50" FontWeight="Bold" FontStyle="Italic"  
      Foreground="Red" Margin="10,0,0,0" Padding="0" /> 
    <TextBlock Grid.Row="1" Text="More Efficient" 
      FontFamily="Times New Roman" FontSize="50" FontWeight="Bold"
      FontStyle="Italic" Foreground="Black" Margin="10,0,0,0" /> 
    <Controls:FormattedTextOutput Grid.Row="2" Text="More Efficient" /> 
    <Glyphs Grid.Row="3" UnicodeString="Most Efficient"  
      FontUri="C:\WINDOWS\Fonts\timesbi.TTF" FontRenderingEmSize="50"  
      Fill="Black" OriginX="10" OriginY="45" /> 
  </Grid> 
</UserControl> 

Here, we have a View that has a Grid panel with four rows. The first row holds a Label control, which although fairly efficient, is the least efficient of the textual output methods shown here and, as we'll see soon, should only be used in very specific circumstances. On it, we specify the FontFamily, FontSize, FontWeight, FontStyle, and Foreground properties to define how its text should look.

The second row contains a TextBlock element, which is slightly more efficient, and, like the Label element, we specify the FontFamily, FontSize, FontWeightFontStyle, and Foreground properties on it directly. It's worth noting that to result in the same visual output, we don't need to set its Padding property to 0, which was required with the Label control.

In the third row, we have a custom FormattedTextOutput control that uses a FormattedText object internally and is slightly more efficient still. As we'll see shortly, we need to specify the relevant properties of this text object in code.

Finally, we see a Glyphs element in the fourth row and this represents the most efficient method of outputting text in a WPF application. Note that when using this method of textual output, we don't specify a font family by name, but instead set an exact font file path to its FontUri property.

As we want to match the bold italic version of the Times New Roman font, we specifically need to set the file path to that exact file. Therefore, we need to specify the timesbi.ttf file, rather than the normal times.ttf version. Other than setting the font size to the FontRenderingEmSize property and the margin to the OriginX and OriginY properties, this class is fairly self-explanatory.

Before continuing, let's first take a look at the visual output of this View:

Let's now take a look at the code inside the FormattedTextOutput class:

using System.Globalization; 
using System.Windows; 
using System.Windows.Media; 
 
namespace CompanyName.ApplicationName.Views.Controls 
{ 
  public class FormattedTextOutput : FrameworkElement 
  { 
    public static readonly DependencyProperty TextProperty =  
      DependencyProperty.Register(nameof(Text), typeof(string),
      typeof(FormattedTextOutput), new FrameworkPropertyMetadata( 
      string.Empty, FrameworkPropertyMetadataOptions.AffectsRender)); 
 
    public string Text 
    { 
      get { return (string)GetValue(TextProperty); } 
      set { SetValue(TextProperty, value); } 
    } 
 
    protected override void OnRender(DrawingContext drawingContext) 
    { 
      DpiScale dpiScale = VisualTreeHelper.GetDpi(this); 
FormattedText formattedText = new FormattedText(Text,
CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight,
new Typeface("Times New Roman"), 50, Brushes.Red,
dpiScale.PixelsPerDip);
formattedText.SetFontStyle(FontStyles.Italic); formattedText.SetFontWeight(FontWeights.Bold); drawingContext.DrawText(formattedText, new Point(10, 0)); } } }

The FormattedTextOutput class is a fairly simple affair, with a single Dependency Property and its associated CLR wrapper and a single overridden base class method. One very important point to note is our use of the AffectsRender member of the FrameworkPropertyMetadataOptions enumeration to specify that changes to this property need to cause a new rendering pass.

Typically, the Text property will be updated from any data binding after the OnRender method is called by the UIElement base class. Without specifying this option, our class will never output any data bound values. By specifying this option, we are, in fact, telling the Framework to call the OnRender method again each time this property value changes.

In the overridden OnRender method, we first initialize a FormattedText object with basic properties, such as the text to render, the current culture, and the color, size, and type of the font to use. Additional style properties can be set using the various set methods that the class exposes. Finally, we call the DrawText method of the DrawingContext object specified by the drawingContext input parameter, passing in the FormattedText object and the position to render it.

Note that we can use data binding with all of these text rendering methods, so let's now update our previous example to demonstrate this:

...
<Label Content="{Binding Text}" FontFamily="Times New Roman"  
  FontSize="50" FontWeight="Bold" FontStyle="Italic" Foreground="Red"
  Margin="10,0,0,0" Padding="0" /> 
<TextBlock Grid.Row="1" Text="{Binding Text}" 
  FontFamily="Times New Roman" FontSize="50" FontWeight="Bold"
  FontStyle="Italic" Foreground="Red" Margin="10,0,0,0" /> 
<Controls:FormattedTextOutput Grid.Row="2" Text="{Binding Text}" /> 
<Glyphs Grid.Row="3" UnicodeString="{Binding Text}" FontUri=
  "C:\WINDOWS\Fonts\timesbi.TTF" FontRenderingEmSize="50"  
  Fill="Black" OriginX="10" OriginY="45" /> 
...

For this example, we can simply hardcode a value in our View Model:

namespace CompanyName.ApplicationName.ViewModels 
{ 
  public class TextViewModel : BaseViewModel 
  { 
    public string Text { get; set; } = "Efficient"; 
  } 
} 

Although we can data bind when using all of these textual output methods, there are some caveats to be aware of. We've just learned of one relating to the required metadata of the Text property in our custom FormattedTextOutput class and there is another relating to the Glyphs class.

It has a requirement that the UnicodeString property cannot be empty if the Indicies property, which represents an alternative method of providing the text to render, is also empty. Unfortunately, because of this requirement, attempting to data bind to the UnicodeString property, as we did in our extended example, will result in a compilation error:

Glyphs Indices and UnicodeString properties cannot both be empty.

To address this issue, we can simply provide a value for the FallbackValue property of the Binding class, so that the Glyphs class can be rest assured that even if there is no data bound value, its UnicodeString property will have a non-empty value.

Note that setting the FallbackValue property to an empty string will result in the same error being raised:

<Glyphs Grid.Row="3" UnicodeString="{Binding Text, FallbackValue='Data
  Binding Not Working'}" FontUri="C:\WINDOWS\Fonts\timesbi.TTF"
  FontRenderingEmSize="50" Fill="Black" OriginX="10" OriginY="45" /> 

There is one further issue regarding data binding; however, this time, it involves the Content property of the Label class. Because the string type is immutable, each time a data bound value updates the Content property, the previous string type will be discarded and replaced with the new one.

Furthermore, if the default ContentTemplate element is used, it will generate a new TextBlock element and discard the previous element each time the property string is replaced. As a result, updating a data bound TextBlock is approximately four times quicker than updating a Label control. Therefore, if we need to update our data bound text values, we should not use a Label control.

In fact, each method of rendering text has its own purpose. The Label control should specifically be used to label text fields in a form, and, in doing so, we can take advantage of its access key functionality and its ability to reference a target control. The TextBlock element is a general-purpose text output method that should be used the majority of the time.

The FormattedText object should really only be used when we specifically want to format some text in a particular way. It provides the ability to output text with a wide range of effects, such as being able to paint the stroke and fill of the text independently and to format particular ranges of characters within the rendered text string.

The Glyphs class extends the FrameworkElement class directly and is, therefore, extremely light-weight and should be utilized when we need to recreate our text output more efficiently than we can by using the alternative methods. Although the FormattedText class can make use of lower, core level classes to render its output, the most efficient way to render text is to use Glyphs objects.