Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Advanced Text Formatting in WPF

0.00/5 (No votes)
13 Apr 2013 1  
This article describes advanced text formatting in WPF.

Introduction

WPF provides several controls which allow working with text. Some of these controls are Label, TextBlock, TextBox, RichTextBox etc. But all these controls provide limited text formatting capabilities. There is yet another control provided by WPF, called FormattedText. This control provides extensive text formatting capabilities. In particular, the FormattedText control can be used in situations where you want to display large sized text as banners. Also it can be used to play animation and videos on text. 

Background

A FormattedText object is created by passing the following parameters to its constructor:

  • string
  • CultureInfo
  • FlowDirection
  • TypeFace
  • Brush
FormattedText text = new FormattedText("HELLO", 
   System.Globalization.CultureInfo.GetCultureInfo("en-US"), 
   FlowDirection.LeftToRight, new Typeface("LilyUPC"), 256, Brushes.Black);
Colourised in 4ms

The MaxTextWidth and MaxTextHeight methods are used to specify the maximum width and height respectively.

The SetForegroundBrush method is used to format the text using a brush.

Typically we override the OnRender method of the Window to perform the formatting and display the formatted text. The OnRender method of the Window class receives a DrawingContext object as parameter. The DrawText method of the DrawingContext object is used to draw the text on the Window.

Note: The Background attribute of the Window element must be "Transparent" for the OnRender method to work.

Using the code

The following code can be used to display text formatted using a LinearGradientBrush.

protected override void OnRender(DrawingContext drawingContext)
{
    FormattedText text = new FormattedText("AZIM", 
      System.Globalization.CultureInfo.GetCultureInfo("en-US"), 
      FlowDirection.LeftToRight, new Typeface("LilyUPC"), 256, Brushes.Black);
    text.MaxTextWidth = 700;
    text.MaxTextHeight = 400;
    LinearGradientBrush brush = new LinearGradientBrush();	// Create a LinearGradientBrush
    // Set GradientStops
    brush.GradientStops.Add(new GradientStop(Colors.Red, 0.2));
    brush.GradientStops.Add(new GradientStop(Colors.Green, 0.3));
    brush.GradientStops.Add(new GradientStop(Colors.Blue, 0.5));
    brush.GradientStops.Add(new GradientStop(Colors.Magenta, 0.7));
    brush.GradientStops.Add(new GradientStop(Colors.Yellow, 0.8));
    brush.GradientStops.Add(new GradientStop(Colors.Cyan, 0.9));
    text.SetForegroundBrush(brush, 0, 4);			// Apply formatting to 4 chars starting from first char
    drawingContext.DrawText(text, new Point(10, 0));		// Draw text on the Window
    base.OnRender(drawingContext);
}
Colourised in 18ms

The above code creates a FormattedText object. Then it creates a LinearGradientBrush and uses the SetForegroundBrush method of the FormattedText object to format the text. The SetForegroundBrush method takes three parameters. The first parameter is the brush, the second parameter is the index of the starting character, and the third parameter is the number of characters to be formatted. Finally it uses the DrawText method of the DrawingContext object to draw the text on the Window.

The output of the above code is as follows:

Animation can be applied to a LinearGradientBrush and used as a background for the text. The XAML code for this is as follows:

<Window x:Class="TextEffects.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Background="Transparent"
        Title="Animated Text" Height="350" Width="525">
    <Canvas>
        <Rectangle Name="myrect" Width="350" Height="250">
            <Rectangle.Fill>
                <LinearGradientBrush x:Name="brush" StartPoint="0,0" EndPoint="1,1">
                    <GradientStop x:Name="stop1" Offset="0" Color="Red"/>
                    <GradientStop x:Name="stop2" Offset="0.5" Color="Green"/>
                    <GradientStop x:Name="stop3" Offset="1" Color="Blue"/>
                </LinearGradientBrush>
            </Rectangle.Fill>
            <Rectangle.Triggers>
                <EventTrigger RoutedEvent="Window.Loaded">
                    <BeginStoryboard>
                        <Storyboard RepeatBehavior="Forever">
                            <ColorAnimation Storyboard.TargetName="stop1" 
                               Storyboard.TargetProperty="Color" From="Red" 
                               To="Green" Duration="0:0:1" BeginTime="0:0:0"/>
                            <ColorAnimation Storyboard.TargetName="stop1" 
                               Storyboard.TargetProperty="Color" From="Green" 
                               To="Blue" Duration="0:0:1" BeginTime="0:0:0.5"/>
                            <ColorAnimation Storyboard.TargetName="stop1" 
                               Storyboard.TargetProperty="Color" From="Blue" 
                               To="Red" Duration="0:0:1" BeginTime="0:0:1"/>
                            <ColorAnimation Storyboard.TargetName="stop2" 
                               Storyboard.TargetProperty="Color" From="Green" 
                               To="Blue" Duration="0:0:1" BeginTime="0:0:0"/>
                            <ColorAnimation Storyboard.TargetName="stop2" 
                               Storyboard.TargetProperty="Color" From="Blue" 
                               To="Red" Duration="0:0:1" BeginTime="0:0:0.5"/>
                            <ColorAnimation Storyboard.TargetName="stop2" 
                               Storyboard.TargetProperty="Color" From="Red" 
                               To="Green" Duration="0:0:1" BeginTime="0:0:1"/>
                            <ColorAnimation Storyboard.TargetName="stop3" 
                               Storyboard.TargetProperty="Color" From="Blue" 
                               To="Red" Duration="0:0:1" BeginTime="0:0:0"/>
                            <ColorAnimation Storyboard.TargetName="stop3" 
                               Storyboard.TargetProperty="Color" From="Red" 
                               To="Green" Duration="0:0:1" BeginTime="0:0:0.5"/>
                            <ColorAnimation Storyboard.TargetName="stop3" 
                               Storyboard.TargetProperty="Color" From="Green" 
                               To="Blue" Duration="0:0:1" BeginTime="0:0:1"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Rectangle.Triggers>
        </Rectangle>
    </Canvas>
</Window>
Colourised in 69ms

The code-behind code is as follows:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
    protected override void OnRender(DrawingContext drawingContext)
    {
        FormattedText text = new FormattedText
           ("AZIM", System.Globalization.CultureInfo.GetCultureInfo("en-US"), 
            FlowDirection.LeftToRight, new Typeface("LilyUPC"), 256, Brushes.Black);
        text.MaxTextWidth = 700;
        text.MaxTextHeight = 400;
        text.SetForegroundBrush(brush, 0, 4);
        drawingContext.DrawText(text, new Point(10, 0));
        myrect.Visibility = Visibility.Hidden;			// Hide the Rectangle
        base.OnRender(drawingContext);
    }
}
Colourised in 8ms

This produces the following output:

An image can be used as a background for the text. The following code can be used for the same:

protected override void OnRender(DrawingContext drawingContext)
{
    FormattedText text = new FormattedText("AZIM", 
      System.Globalization.CultureInfo.GetCultureInfo("en-US"), 
      FlowDirection.LeftToRight, new Typeface("LilyUPC"), 256, Brushes.Black);
    text.MaxTextWidth = 700;
    text.MaxTextHeight = 400;
    ImageBrush brush = new ImageBrush();			// Create an ImageBrush
    brush.ImageSource = new BitmapImage(new Uri("flower.jpg", UriKind.Relative));	// Set the image source
    text.SetForegroundBrush(brush, 0, 4);
    drawingContext.DrawText(text, new Point(10, 200));
    base.OnRender(drawingContext);
}
Colourised in 6ms

Its output is as follows:

If you want to play video on the surface of the text, you can use a VideoDrawing object in combination with a DrawingBrush object as follows:

protected override void OnRender(DrawingContext drawingContext)
{
    FormattedText text =
       new FormattedText("AZIM", System.Globalization.CultureInfo.GetCultureInfo("en-US"), 
       FlowDirection.LeftToRight, new Typeface("LilyUPC"), 256, Brushes.Black);
    text.MaxTextWidth = 700;
    text.MaxTextHeight = 400;
    MediaTimeline timeline = new MediaTimeline(new Uri("airplane.mpg", UriKind.Relative));	// Create MediaTimeLine
    timeline.RepeatBehavior = RepeatBehavior.Forever;
    MediaClock clock = timeline.CreateClock();
    MediaPlayer player = new MediaPlayer();
    player.Clock = clock;
    VideoDrawing drawing = new VideoDrawing();	// Create VideoDrawing
    drawing.Rect = new Rect(0, 0, 300, 200);
    drawing.Player = player;			// Set player
    DrawingBrush brush = new DrawingBrush(drawing);	// Create DrawingBrush based on the VideoDrawing
    text.SetForegroundBrush(brush, 0, 4);
    drawingContext.DrawText(text, new Point(100, 0));
}
Colourised in 17ms

The output of this code is as follows:

A FormattedText object can be converted to a PathGeometry object. In the following example an ellipse is created which follows the path of the text.

Following is the XAML code:

<Window x:Class="TextEffects.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Background="Transparent"
        Title="PathGeometry Animation" Height="350" Width="525">
    <Canvas>
        <Ellipse Canvas.Top="0" Canvas.Left="0" Width="50" Height="50">
            <Ellipse.Fill>
                <RadialGradientBrush GradientOrigin="0.5,0.5" 
                          Center="0.5,0.5" RadiusX="0.5" RadiusY="0.5">
                    <RadialGradientBrush.GradientStops>
                        <GradientStop Color="Transparent" Offset="0.25" />
                        <GradientStop Color="Red" Offset="1" />
                    </RadialGradientBrush.GradientStops>
                </RadialGradientBrush>
            </Ellipse.Fill>
            <Ellipse.RenderTransform>
                <MatrixTransform />
            </Ellipse.RenderTransform>
            <Ellipse.Triggers>
                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard x:Name="storyboard">
                                <MatrixAnimationUsingPath x:Name="matrixAnimation" 
                                   Duration="0:00:40" RepeatBehavior="Forever" 
                                   Storyboard.TargetProperty="RenderTransform.Matrix" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Ellipse.Triggers>
        </Ellipse>
    </Canvas>
</Window>
Colourised in 28ms

Following is the code-behind code:

protected override void OnRender(DrawingContext drawingContext)
{
    FormattedText text =
      new FormattedText("AZIM", 
        System.Globalization.CultureInfo.GetCultureInfo("en-US"), 
        FlowDirection.LeftToRight, new Typeface("LilyUPC"), 256, Brushes.Black);
    text.MaxTextWidth = 700;
    text.MaxTextHeight = 400;
    ImageBrush brush = new ImageBrush();
    brush.ImageSource = new BitmapImage(new Uri("flower.jpg", UriKind.Relative));
    text.SetForegroundBrush(brush, 0, 4);
    drawingContext.DrawText(text, new Point(10, 0));
    Geometry geometry = text.BuildGeometry(new System.Windows.Point(-20, -20));	// Convert text to Geometry object
    PathGeometry pathGeometry = geometry.GetFlattenedPathGeometry();		// Create a PathGeometry based on the Geometry object
    matrixAnimation.PathGeometry = pathGeometry;				// Set PathGeometry for the animation
    base.OnRender(drawingContext);
}
Colourised in 16ms

Following is the output of the above code:

Points of Interest

I hope readers find the above discussion interesting and useful.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here