WPF Controls – Enhancing the TextBlock

What is a moderate interpretation of the text? Halfway between what it really means and what you’d like it to mean?
Antonin Scalia

I’m going to continue presenting some of the controls I’ve developed over recent years that complement the standard WPF elements. This post introduces perFormattedTextBlock – an extension of the standard WPF TextBlock.

I’m sure you’re all aware of how the standard WPF TextBlock control can display a piece of text using the specified display characteristics – font size / style / family / foreground & background colours etc. You can even combine multiple text elements (each with different formatting) into a single block of text by placing each part into a Run element within the TextBlock, which will wrap and flow within the bounds of the control.

<TextBlock FontSize="20" FontFamily="Courier New" Background="LightBlue">
    <Run Text="How do we make" />
    <Run Foreground="Red" FontWeight="Bold" Text="this part" />
    <Run Text="of the text stand out?" />
</TextBlock>

In an MVVM based application, the text of each run element could be bound to a different string property (remembering that Run.Text binds two-way by default, so we have to explicitly set one-way binding). However, with anything more than a couple of changes of text formatting, the number of runs and hence individual string properties required would soon get out of hand. Also this technique requires that formatting layout be done at design-time, with just the text content varying. What if the desired text and formatting isn’t known until the application is running?

The ideal would be to allow a single string to be used to to define the display, with control codes embedded within the text to define the changes of display characteristics, which could be built up at run-time. Of course, this is exactly what HTML is … but it feels like a massive overkill to embed a browser control into the View just to display some formatted text. I’ve taken the HTML concept of tags within the text as the basis for this control though.

perFormattedTextBlock supports a basic subset of the HTML tags – just the elements relating directly to text formatting. There are no tables, spans, images, lists etc. There is also one difference with this control compared to HTML, which is how it deals with mismatched tags. Unlike HTML, When a closing tag is reached, the text will revert to the formatting before the corresponding opening tag, including implicitly closing any other tags that may have been opened.

To generate the display, the bound Text string is parsed, and the text split into a number of different run elements, each with the required formatting. To do this parsing, I’ve used regular expression matching. Many of you will be aware of Jamie Zawinski’s famous quote on this subject, which can be paraphrased as “if you try to solve a programming problem using regex, often all you end up with is two problems”. However, in this case regex really is the ideal solution, as the matching patterns are well defined, even if they are rather complicated.

For example, the regex pattern for the opening font size tag is

<fs[ =]?(?:[0-9]+|\*[0-9]+(?:\.[0-9]+)?)>

This allows any of the following to be matched (plus any other numeric value you might want)

absolute values

<fs9>
<fs 9>
<fs=9>
<fs99>
<fs 99>
<fs=99>

relative values

<fs*9>
<fs *9>
<fs=*9>
<fs*99>
<fs *99>
<fs=*99>
<fs*9.9>
<fs *9.9>
<fs=*9.9>
<fs*99.9>
<fs *99.9>
<fs=*99.9>
<fs*9.99>
<fs *9.99>
<fs=*9.99>

The breakdown of the individual elements within the regex pattern is

<fs[ =]?(?:[0-9]+|\*[0-9]+(?:\.[0-9]+)?)>
 
<fs                                     >
literal characters, the start and end of the tag
   [ =]?
optionally, either exactly one space or one literal '=' character
        (?:                            )
a mandatory non-capturing group, consisting of either
           [0-9]+
at least one numeric digit
                 |
or
                  \*[0-9]+
a literal '*' character followed by at least 1 numeric digit
                          (?:        )?
optionally followed 1 time by a non-capturing group consisting of
                             \.[0-9]+
a literal '.' character followed by at least 1 numeric digit

The regex patterns for the other tag types are similarly documented in the source code.

The code for locating the matching closing tag is an iterative process. Initially we just take the first match, then check whether the enclosed text contains any additional opening tags of the same type. If any are found, the closing tag search is advanced, skipping over as many closing tags as are required. This process will be repeated until no new opening tags of the same type found, and may result in there actually not being a matching closing tag at all.

The demo project shows perFormattedTextBlock in action. Select an pre-defined item from the ComboBox to see the features of this control, or you can type your own free-format text into the TextBox and the TextBlock will render it using any embedded tags.

As usual the code samples for this blog are available on Github, along with my own personal C# / WPF library.

If you find this article useful, or you have any other feedback, please leave a comment below.

Leave a Reply

Your e-mail address will not be published. Required fields are marked *