How can I specify letter spacing or kerning, in a WPF TextBox?
Solution 1
I tried Glyphs and FontStretch and couldn't easily get the result I was looking for. I was able to come up with an approach that works for my purposes. Maybe it will work for others, as well.
<ItemsControl ItemsSource="{Binding SomeString}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
Margin="0,0,5,0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I can bind to any string and don't need to do any character width detection to set the spacing properly. The right margin is the space between the letters.
Example:
Solution 2
I found a way to have letter spacing with TextBlock class as it supports TranslateTransforms. By replacing a default PropertyChangedCallback
on the TextBlock.TextProperty
with a custom one, we can apply TranslateTransform
to each letter in the TextBlock
.
Here is a complete step-by-step coding I did:
First, we create a custom class and inherit from TextBlock
like so:
using System.Windows.Controls;
namespace MyApp
{
class SpacedLetterTextBlock : TextBlock
{
public SpacedLetterTextBlock() : base()
{
}
}
}
Then, in XAML, we change the TextBlock to our custom class (more information can be found here):
<Window x:Class="MyApp.MainWindow"
...
xmlns:app="clr-namespace:MyApp">
<Grid>
<app:SpacedLetterTextBlock>
Some Text
</app:SpacedLetterTextBlock>
</Grid>
</Window>
Finally, before the InitializeComponent()
method in the .cs
code-behind file, add the OverrideMetadata
method like so:
// This line of code adds our own callback method to handle any changes in the Text
// property of the TextBlock
SpacedLetterTextBlock.TextProperty.OverrideMetadata(
typeof(SpacedLetterTextBlock),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnTextChanged)
)
);
... and apply TranslateTransform
to each letter each time TextProperty
changes:
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SpaceLettersOut(d);
}
// This method takes our custom text block and 'moves' each letter left or right by
// applying a TranslateTransform
private static void SpaceLettersOut(DependencyObject d)
{
SpacedLetterTextBlock thisBlock = (SpacedLetterTextBlock)d;
for (int i = 1; i <= thisBlock.Text.Length; i++)
{
// TranslateTransform supports doubles and negative numbers, so you can have
// whatever spacing you need - do see 'Notes' section in the answer for
// some limitations.
TranslateTransform transform = new TranslateTransform(2, 0);
TextEffect effect = new TextEffect();
effect.Transform = transform;
effect.PositionStart = i;
effect.PositionCount = thisBlock.Text.Length;
thisBlock.TextEffects.Add(effect);
if (effect.CanFreeze)
{
effect.Freeze();
}
}
}
NOTES:
First, I am a complete novice in WPF and C#, so my answer might not be the cleanest solution available. If you have any comments on how to improve this answer, it will be greatly appreciated!
Second, I haven't tested this solution with a large number of TextBlock
elements, and there (probably) is a huge performance penalty as TranslateTransform
is applied to each individual letter in a TextBlock.Text
.
Finally, the text of the TextBlock
goes out of bounds with any positive X
value for TranslateTransform
. I think that you can re-calculate the width of the TextBlock
and only then place it programmatically (?)
Solution 3
is FontStretch an option for you?
Otherwise you might want to look into this there is an image, showing what advance width means. Though I have not done this before and don't know if this works increasing right and left side bearings might be what you want!
Cheeso
I'm a longtime hacker and software practitioner. Creator of IIRF, DotNetZip, ReloadIt, CleanModQueue and a bunch of other Apigee-related tools and scripts. Maintainer of csharp-mode and a few other emacs-y things . I'm a Polyglot: Java, JavaScript, golang, a little PHP, C#, some VB.NET, C, XSLT, Powershell, elisp of course. Currently most of the work I do is JavaScript and NodeJS. I work for Google. We're hiring API geeks around the world. see: https://careers.google.com/jobs#t=sq&q=j&li=20&l=false&jlo=en-US&j=apigee See my blog ...or my Github profile ...or my LinkedIn profile
Updated on July 05, 2021Comments
-
Cheeso almost 3 years
I'd like to modify the spacing between characters in a WPF TextBox.
Something like theletter-spacing: 5px
thing that is available in CSS.
I think it is possible in XAML; what's the simplest way?I found the "Introduction to the GlyphRun Object and Glyphs Element" document, and found it to be exceedingly unhelpful.
This is a code example from that page:
<!-- "Hello World!" with explicit character widths for proportional font --> <Glyphs FontUri = "C:\WINDOWS\Fonts\ARIAL.TTF" FontRenderingEmSize = "36" UnicodeString = "Hello World!" Indices = ",80;,80;,80;,80;,80;,80;,80;,80;,80;,80;,80" Fill = "Maroon" OriginX = "50" OriginY = "225" />
The same documentation page gives this "explanation" for what the
Indices
property does:I have no idea what any of that means. I'm also not sure that Indices is the right thing - the comment in the code speaks of "character widths" which I don't care about. I want to adjust the width between characters.
Also, there is no example for how to apply a
Glyphs
element to a TextBox. When I tried it, my WPF test app just crashed.
What I want to do is slightly increase the empty space that appears between drawn characters within a WPF TextBox. The text will vary in length and content. Do I have to modify the
Indicies
property every time there is a new character? Is there a way to say "make it 20% more space than usual, for every character".Can anybody help me?
-
Matt Jacobi over 9 yearsThis is a nice solution (I up voted it), but this can definitely cause performance problems. We use this solution in some places, but we wanted to use it in an
ItemsControl
with a few hundred items and it was adding multiple seconds to the rendering time. We went back to a regularTextBlock
in those scenarios. So just be aware/careful! -
vlaku about 7 yearsThis also will break the ability to trim or wrap text.