Click here to Skip to main content
Click here to Skip to main content

Advanced Text Formatting in WPF

By , 13 Apr 2013
 

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);

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);
}

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>

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);
    }
}

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);
}

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));
}

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>

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);
}

Following is the output of the above code:

Points of Interest

I hope readers find the above discussion interesting and useful.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Azim Zahir
Instructor / Trainer NIIT, India
India India
Member
I am a trainer by profession. Currently I am working with NIIT (Mumbai, India) as a Senior Faculty. I enjoy programming as a hobby. My favorite technologies are Flash, Flex and Silverlight.
 
Of late I have developed keen interest in WPF and Windows Mobile programming.
 
Apart from computers, my favorite pastime is bicycling.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberDrABELL13 Apr '13 - 18:46 
GeneralRe: My vote of 5memberAzim Zahir14 Apr '13 - 21:14 
GeneralRe: My vote of 5memberDrABELL15 Apr '13 - 1:11 
QuestionGreat job Azimmemberliftup14 Jan '13 - 2:49 
AnswerRe: Great job AzimmemberAzim Zahir15 Jan '13 - 22:33 
GeneralRe: Great job Azimmemberliftup16 Jan '13 - 6:02 
GeneralRe: Great job AzimmemberAzim Zahir18 Jan '13 - 19:15 
QuestionNice!memberxzz01951 Aug '12 - 23:55 
AnswerRe: Nice!memberAzim Zahir2 Aug '12 - 7:03 
GeneralLocal copies of imagesstaffSmitha Vijayan1 Aug '12 - 11:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 13 Apr 2013
Article Copyright 2012 by Azim Zahir
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid