Advanced Text Formatting in WPF






4.61/5 (15 votes)
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);
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.