21

I'd like to modify the spacing between characters in a WPF TextBox.
Something like the letter-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:

enter image description here

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?

Cheeso
  • 189,189
  • 101
  • 473
  • 713

4 Answers4

31

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:

Kerning

BrianR
  • 630
  • 6
  • 6
  • 1
    This 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 regular `TextBlock` in those scenarios. So just be aware/careful! – Matt Jacobi Dec 11 '14 at 20:23
  • 1
    This also will break the ability to trim or wrap text. – vlaku Jun 12 '17 at 16:47
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 (?)

1

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!

Markus Hütter
  • 7,796
  • 1
  • 36
  • 63
-4

For what its worth . . .

If you have the option to switch your implementation to RichTextBox, this might be easier than the work-around you found Sept 2013. I just tried it for my own needs and it works for what I need. I do not see a way with RichTextBox to control kerning of individual spaces like typesetters do. But TextBox was eating additional spaces (consolidating multiple adjacent spaces to a single space) like HTML. I needed for the spaces to display the same amount of spacing as is in my text String, and RichTextBox does this.

<RichTextBox x:Name="MyRichTextBox"></RichTextBox>

I'm no expert, but it seems you can't specify the text content in XAML. I had to specify it in a code-behind event:

this.MyRichTextBox.AppendText("V A R I E D      S P A C E S");
philologon
  • 2,093
  • 4
  • 19
  • 35