如何在 WPF 文本框中指定字母间距或字距调整?

发布于 2024-11-04 10:07:39 字数 1103 浏览 1 评论 0 原文

我想修改 WPF TextBox 中字符之间的间距。
类似于 CSS 中可用的 letter-spacing: 5px 内容。
我认为在 XAML 中这是可能的;最简单的方法是什么?

我找到了“GlyphRun 对象和 Glyphs 元素简介”文档,并发现它毫无帮助。

这是该页面的代码示例:

<!-- "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"
/>

同一文档页面对 Indices 属性的作用进行了“解释”:

我不知道这意味着什么。我也不确定索引是否正确 - 代码中的注释提到了我不关心的“字符宽度”。我想调整字符之间的宽度。

此外,也没有关于如何将 Glyphs 元素应用到 TextBox 的示例。当我尝试时,我的 WPF 测试应用程序崩溃了。


我想要做的是稍微增加 WPF 文本框中绘制的字符之间出现的空白空间。文本的长度和内容会有所不同。每次出现新字符时是否都必须修改 Indicies 属性?有没有办法说“为每个角色设置比平常多 20% 的空间”。

有人可以帮助我吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

七秒鱼° 2024-11-11 10:07:39

我尝试了 Glyphs 和 FontStretch,但无法轻松获得我想要的结果。我能够想出一种适合我的目的的方法。也许它也对其他人有用。

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

我可以绑定到任何字符串,并且不需要进行任何字符宽度检测来正确设置间距。右边距是字母之间的空格

示例:

Kerning

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

梅倚清风 2024-11-11 10:07:39

我找到了一种使用 TextBlock 类,因为它支持 翻译转换。通过将 TextBlock.TextProperty 上的默认 PropertyChangedCallback 替换为自定义的,我们可以将 TranslateTransform 应用于 TextBlock< 中的每个字母/代码>。

这是我所做的完整的逐步编码:

首先,我们创建一个自定义类并继承 TextBlock,如下所示:

using System.Windows.Controls;

namespace MyApp
{
    class SpacedLetterTextBlock : TextBlock
    {
        public SpacedLetterTextBlock() : base()
        {
        }
    }
}

然后,在 XAML 中,我们将 TextBlock 更改为我们的自定义类(更多信息可以找到 此处):

<Window x:Class="MyApp.MainWindow"
        ...
        xmlns:app="clr-namespace:MyApp">
   <Grid>
       <app:SpacedLetterTextBlock>
           Some Text
       </app:SpacedLetterTextBlock>
   </Grid>
</Window>

最后,在 .cs 代码隐藏文件中的 InitializeComponent() 方法之前,添加 OverrideMetadata 方法,如下所示:

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

...并在每次 TextProperty 更改时将 TranslateTransform 应用于每个字母:

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

首先,我是 WPF 和 C# 的新手,所以我的答案可能不是最干净的解决方案。如果您对如何改进这个答案有任何意见,我们将不胜感激!

其次,我还没有使用大量 TextBlock 元素测试此解决方案,并且(可能)存在巨大的性能损失,因为 TranslateTransform 应用于中的每个单独字母一个TextBlock.Text

最后,TextBlock 的文本超出 TranslateTransform 的任何正 X 值的范围。我认为您可以重新计算 TextBlock 的宽度,然后以编程方式放置它(?)

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 (?)

各自安好 2024-11-11 10:07:39

FontStretch 适合您吗?

否则,您可能需要在那里查看 this是一个图像,显示了前进宽度的含义。虽然我以前没有这样做过,也不知道这是否有效,但增加左右侧轴承可能就是您想要的!

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!

谜兔 2024-11-11 10:07:39

虽然我喜欢 ItemsControl hack,但这里有一个简单的 Unicode "Space分隔符” 技巧可能对某些人有用。

<TextBlock Text="{Binding Text,Converter={x:Static c:LetterSpacingConverter.Default}}" />
public class LetterSpacingConverter : IValueConverter
{
    private const char ThinSpace = '\x2009';
    private const char HairSpace = '\x200A';

    public static readonly LetterSpacingConverter Default = new();

    public object Convert(object value, Type targetType,
                          object parameter, CultureInfo culture)
    {
        if (value is string s)
        {
            // Use ThinSpace for slightly wider spacing :)
            return string.Join(HairSpace, s.ToArray());
        }

        return DependencyProperty.UnsetValue;
    }

    public object ConvertBack(object value, Type targetType,
                              object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

为了提高性能,您可能希望将值缓存在视图模型中,而不是使用转换器。

Though I like the ItemsControl hack, here's a simple Unicode "Space Separator" trick that might work for some.

<TextBlock Text="{Binding Text,Converter={x:Static c:LetterSpacingConverter.Default}}" />
public class LetterSpacingConverter : IValueConverter
{
    private const char ThinSpace = '\x2009';
    private const char HairSpace = '\x200A';

    public static readonly LetterSpacingConverter Default = new();

    public object Convert(object value, Type targetType,
                          object parameter, CultureInfo culture)
    {
        if (value is string s)
        {
            // Use ThinSpace for slightly wider spacing :)
            return string.Join(HairSpace, s.ToArray());
        }

        return DependencyProperty.UnsetValue;
    }

    public object ConvertBack(object value, Type targetType,
                              object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

For performance, you might want to cache the value in the view model instead of using a converter.

临风闻羌笛 2024-11-11 10:07:39

它的价值是什么。 。 。

如果您可以选择将实现切换到 RichTextBox,这可能比您在 2013 年 9 月找到的解决方法更容易。我只是根据自己的需要尝试了它,它可以满足我的需要。我没有看到 RichTextBox 可以像排字机那样控制各个空间的字距调整的方法。但 TextBox 会像 HTML 一样占用额外的空间(将多个相邻空间合并为一个空间)。我需要让空格显示与文本字符串中相同的间距,而 RichTextBox 可以做到这一点。

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

我不是专家,但似乎您无法在 XAML 中指定文本内容。我必须在代码隐藏事件中指定它:

this.MyRichTextBox.AppendText("V A R I E D      S P A C E S");

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");
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文