TextEffects is a poorly-documented property that allows you to apply an effect to a portion of text. It’s not the best of name for the property (at least in this first version of WPF), because the only “effects” supported in the current version are Clip, Foreground, and Transform.
There are four important things to know about TextEffects:
- They’re really only interesting when you’re working with a substring of text. In other words, if you want to affect the entire content of a
TextBlock, you’re better off using properties exposed directly onTextBlock:Clip,Foreground, andRenderTransform(or its cousin,LayoutTransform). - The
ClipandTransformeffects are always relative to the beginning of the line. In other words, the defaultCenterXandCenterYof aRotateTransformare at the beginning of the line — not at the beginning of the text being affected (see the screenshots below). - Just like
RenderTransform, any size and position changes caused will not affect the layout of text. This is good for animation, since you don’t want to performance penalty caused by re-layout. It does, however, mean that you can easily end up with overlapping text. - They’re a little quirky and frustrating to use (at least in this version of the platform). With the exception of a few narrow scenarios, you’re better off using
Clip,Foreground, orRenderTransform.
Let’s start with a basic example and the screenshot:
<Border xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
VerticalAlignment="Center" HorizontalAlignment="Center" BorderThickness="1" BorderBrush="#ccc" Padding="20" SnapsToDevicePixels="True">
<TextBlock FontFamily="Calibri" FontSize="30" TextWrapping="Wrap">
<TextBlock.TextEffects>
<TextEffect PositionStart="5" PositionCount="10">
<TextEffect.Transform>
<TranslateTransform Y="-5" />
</TextEffect.Transform>
</TextEffect>
</TextBlock.TextEffects>
Clap Your Hands Say Yeah!
</TextBlock>
</Border>

The markup is pretty straightforward: TextEffect.PositionStart and TextEffect.PositionCount properties indicate the substring for the effect, while TextEffect.Transform is a standard Transform.
When we resize, notice how the effect applies even across a line break:

That was simple enough, now let’s switch to a RotateTransform and see how that works:
<Border xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
VerticalAlignment="Center" HorizontalAlignment="Center" BorderThickness="1" BorderBrush="#ccc" Padding="20" SnapsToDevicePixels="True">
<TextBlock FontFamily="Calibri" FontSize="30" TextWrapping="Wrap">
<TextBlock.TextEffects>
<TextEffect PositionStart="5" PositionCount="4">
<TextEffect.Transform>
<RotateTransform Angle="-5" />
</TextEffect.Transform>
</TextEffect>
<TextEffect PositionStart="16" PositionCount="3">
<TextEffect.Transform>
<RotateTransform Angle="-5" />
</TextEffect.Transform>
</TextEffect>
</TextBlock.TextEffects>
Clap Your Hands Say Yeah!
</TextBlock>
</Border>
Now we have two identical RotateTransform effects being applied to two different portions of the text. Here’s the screenshot:

Is that what you expected? Let’s add some lines to make it clearer:

As I mentioned earlier, effects are always relative to the beginning of the line. Look what happens when the text wraps:

Notice how “Say” is now rotated with respect to the beginning of the line it occurs on.
Unfortunately, there’s no foolproof way to make the rotation relative to the position of the text. You can manually set the rotation center through the RotateTransform.CenterX and RotateTransform.CenterY properties — but to get the desired effect you’d have to calculate the distance from the word to the beginning of the line (which will end up being incorrect if the text breaks across lines).
<Run>ning into Trouble
Honestly, I had never done much with TextEffects until I started writing this post. I discovered a few tricky issues while constructing the following markup:
<Border xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
VerticalAlignment="Center" HorizontalAlignment="Center" BorderThickness="1" BorderBrush="#ccc" Padding="20" SnapsToDevicePixels="True">
<TextBlock FontFamily="Calibri" FontSize="30" TextWrapping="Wrap">
<TextBlock.TextEffects>
<TextEffect PositionStart="5" PositionCount="4">
<TextEffect.Transform>
<RotateTransform Angle="-5" />
</TextEffect.Transform>
</TextEffect>
</TextBlock.TextEffects>
Clap <Bold>Your</Bold> Hands Say Yeah!
</TextBlock>
</Border>
This is the markup used in our rotation example, with the second effect removed and a Bold tag added around the word “Your.” If you load this XAML, you’ll see the following:

It was a little tricky to realize what’s going on here. First, it turns out that TextEffects only works with a simple string of content. Let’s try fix this by applying the TextEffect to the Bold instead of the TextBlock (note that we also change the PositionStart to 0):
<Border xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
VerticalAlignment="Center" HorizontalAlignment="Center" BorderThickness="1" BorderBrush="#ccc" Padding="20" SnapsToDevicePixels="True">
<TextBlock FontFamily="Calibri" FontSize="30" TextWrapping="Wrap">
Clap
<Bold>
<Bold.TextEffects>
<TextEffect PositionStart="0" PositionCount="4">
<TextEffect.Transform>
<RotateTransform Angle="-5" />
</TextEffect.Transform>
</TextEffect>
</Bold.TextEffects>
Your
</Bold>
Hands Say Yeah!
</TextBlock>
</Border>
Load this up and we get:

Well, we’re missing yet another thing! As you may already know, Run elements are used behind the scenes when we’re constructing element trees with text. It seems that the effect isn’t applying to the Run inside the Bold. Let’s switch the Bold to a Run and set the FontWeight property:
<Border xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
VerticalAlignment="Center" HorizontalAlignment="Center" BorderThickness="1" BorderBrush="#ccc" Padding="20" SnapsToDevicePixels="True">
<TextBlock FontFamily="Calibri" FontSize="30" TextWrapping="Wrap">
Clap
<Run FontWeight="Bold">
<Run.TextEffects>
<TextEffect PositionStart="0" PositionCount="4">
<TextEffect.Transform>
<RotateTransform Angle="-5" />
</TextEffect.Transform>
</TextEffect>
</Run.TextEffects>
Your
</Run>
Hands Say Yeah!
</TextBlock>
</Border>
Drumroll please …

Argh! This last one took a while, but I finally figured it out by playing with PositionStart and PositionEnd. Counter-intuitively, the PositionStart property isn’t relative to the beginning of the Run, it seems to be relative to the beginning of the containing TextBlock — but not quite. Originally, we used a PositionStart of 5, but in order to work as expected, I had to use a value of 8! Removing the whitespace between “Clap” and the Run lowered this to 7, but I never figured out how to get it to 5 (which, although counter-intuitive, I could at least understand).
Bleh. My recommendation is to set PositionStart to 0 and PositionCount to something large, like 100. Doing so, we get our final markup here:
<Border xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
VerticalAlignment="Center" HorizontalAlignment="Center" BorderThickness="1" BorderBrush="#ccc" Padding="20" SnapsToDevicePixels="True">
<TextBlock FontFamily="Calibri" FontSize="30" TextWrapping="Wrap">
Clap
<Run FontWeight="Bold">
<Run.TextEffects>
<TextEffect PositionStart="0" PositionCount="100">
<TextEffect.Transform>
<RotateTransform Angle="-5" />
</TextEffect.Transform>
</TextEffect>
</Run.TextEffects>
Your
</Run>
Hands Say Yeah!
</TextBlock>
</Border>
Finally, we get the expected result!

In the end …
In most cases, you’re better off using RenderTransform if you can get away with it. Hopefully, future version of WPF will add some more interesting effects and fix these weird issues. Meanwhile, I hope this post helps people who are struggling with TextEffects.