Viewbox内的TextBlock - 渲染奇怪

9
这是关于一种非常简单的构造的问题-我有以下 XAML 代码:
    <Viewbox Height="100" Stretch="Uniform">
        <TextBlock FontFamily="Georgia">My Cool Text</TextBlock>
    </Viewbox>

这很容易理解。但是,当我启动程序时,我会得到奇怪的模糊文本(我的项目中没有任何位图效果)。 (左侧- VS2010中的设计器视图,右侧- 运行的应用程序)
有人对为什么会发生这种情况有任何建议吗?

在我的机器上看起来不错,你使用的是哪个版本的WPF?你能否在一个新窗口中测试它,以此作为唯一的内容? - decyclone
是的,如果我创建一个仅包含此代码的新应用程序,看起来很不错。我知道这点 :) 问题是我不知道我是如何得到这个模糊拉伸的效果的。某处有些问题,但我不知道从哪里开始。发布整个代码/资源字典也不是最好的主意 - 太多了。 - Jefim
此外,Viewbox类型没有默认样式,TextBlock的默认样式只是设置颜色。而且上面的代码完全是从我的XAML中提取出来的。 - Jefim
2个回答

29

Jefim已经正确回答了自己的问题,但我想解释一下为什么在使用这组特性时会出现这种行为。Jefim认为这是WPF中的一个错误,但事实并非如此。当你要求WPF执行不可能的任务时,问题就会出现。在你要求这个不可能的事情时,它必须做出妥协,而结果就是你看到的东西。

这个解释有点长,不适合放在评论中,所以我把它放在一个单独的回答中。

这个示例使用了WPF的两个相互矛盾的特性。这些特性包括:

  1. 能够以任何比例一致地呈现可视化元素
  2. 能够以与GDI32相同的方式呈现文本

你无法同时使用这两个特性。GDI32以一种无法始终按比例缩放的方式呈现文本:如果某个特定字体大小的文本恰好是200个像素宽,则如果你将字体大小乘以3,并使用相同字体家族在新的字体大小下呈现相同的文本,在GDI32中它可能不是600个像素-它会接近,但通常不完全正确。

GDI32在形状和字符宽度方面进行处理,以增强文本的清晰度和锐度。具体来说,它会将字母弯曲,以便其特征与屏幕上的像素更好地对齐。在必要时,它会调整单个字符的宽度为精确的像素宽度。由于这个字母的弯曲都是基于实际像素的,所以它会以不同的方式弯曲不同大小的文本。

即使这种方式可以让文本看起来非常清晰,但如果你尝试逐渐改变比例,情况会变得非常糟糕。如果你尝试为此类文本的字体大小添加动画效果,结果可能会导致文本看起来闪烁和抖动,因为为了清晰度而进行的调整在每个字体大小上略微不同。即使不进行动画,它也可能产生较差的结果-- 如果您有一个单一的字体显示在多个大小上,它可能在每个大小上看起来非常不同;如果你的应用程序具有缩放功能,随着你的缩放,文本的特性可能会发生显著变化(布局也可能发生变化)。 (如果您使用Microsoft Word,您可能已经注意到某些单词之间出现了奇怪的额外宽间距。这是Word与GDI32对抗的结果-- Word尝试尽可能地保持屏幕布局与打印时的实际情况尽可能接近,这意味着它有时会与GDI32的网格拟合相冲突。)
因此,WPF提供了一种不同的文本呈现方法:它可以以尽可能忠实于字体原始设计的方式呈现文本。这样扭曲的文本较少,这意味着你不会在缩放时出现间断。
缺点是与由GDI32呈现的文本相比,文本看起来模糊。(GDI32所做的变形都旨在提高清晰度。)
因此,在WPF 4.0中,微软添加了以GDI32方式呈现文本的功能。这就是TextOptions.TextFormattingMode="Display"的作用。
通过打开该选项,您表示“我不需要一致的缩放,并且更喜欢清晰度,因此生成与GDI32相同的像素。”如果您然后继续应用缩放,在告诉WPF您不需要可伸缩性后,您将得到低质量的结果。WPF精心生成了文本的位图表示,完全按照您的规格要求,然后您告诉它以不同的比例呈现该文本。因此,它看起来像是为不同分辨率生成的某些文本的缩放位图。

你可以认为WPF在这里做了一些不同的事情:如果你在GDI32中应用缩放转换,你会看到不同的行为 - 你会看到先前描述的不一致性在不同的比例下。如果你真的想要在WPF中获得这种效果,你可以直接修改字体大小来实现。但是WPF并不优先考虑获得相同的效果 - 它的目标是使得在真正需要的时候能够获得GDI32风格的清晰文本,并提供默认的一致缩放。

而你在这里遇到的是“一致性缩放”。打开GDI32样式的文本呈现不会破坏一致性缩放:应用缩放因子(直接、通过ScaleTransform或间接地通过Viewbox)将通过准确的指定缩放因子改变视觉元素的尺寸。如果它重新生成调整到新缩放大小的文本视觉元素,那么文本就会以不同的宽度呈现出来。这实际上会导致Viewbox的问题:它应用基于内容的自然尺寸的缩放因子,旨在使其适合可用空间。但是如果在缩放后重新进行网格拟合,这实际上会改变宽度。由于GDI32文本呈现方式固有的不一致性,ViewBox可能甚至无法找到合适的缩放因子 - 可能会出现一个文本块,其在特定字体中呈现时永远不会以200像素宽度呈现。对于某些字体大小,网格拟合中的四舍五入可能会将尺寸降低到198,并且随着字体大小的微小增量而保持在那里,直到你超过某个阈值,此时它可能跳到202像素。

对于试图将文本强制适应精确的200像素的 Viewbox,这将是一个问题。但是 Viewbox 不是以这种方式工作的 - 它使用 WPF 的一致缩放,在你选择字体大小并执行 GDI32 风格的文本渲染之后,才会进行缩放。因此,Viewbox 总是能够完成其设计的任务,但这项任务与 GDI32 风格的文本渲染根本不兼容。
简而言之,WPF 为您请求的字体大小呈现文本,然后对结果进行缩放。
因此,您必须仅选择其中一个特性 - 您不能同时具备这两个特性,因为这是不可能的。要么不在可能应用任意比例因子的上下文中尝试呈现文本(例如 Viewbox),要么不打开 GDI32 风格的文本渲染。否则,您就会遇到那种奇怪的像素化文本。

谢谢您的“评论”-很有趣。我将其标记为答案,因为它确实值得。至于我的“错误”表述-您的解释已经为我澄清了一些事情。现在我只想说-这应该写在MSDN文档中。真的,因为这并不显而易见(至少对我来说),我不得不花费几个小时才能找到问题的原因。总之,再次感谢您的回答。 - Jefim
@lan Griffiths 谢谢,我想知道是否可以获取 viewBox 中已更改其实际大小的元素? 实际大小是屏幕上显示的元素而不是 ActualSize。 我在 http://stackoverflow.com/questions/42411455/how-to-know-the-element-actual-size-have-change-by-other-control?noredirect 发布了这个问题。 - lindexi

10

好的,找到了bug。我的窗口样式有以下setter:

    <Setter Property="TextOptions.TextFormattingMode" Value="Display"/>

如果我将其设置为“理想”(即默认值),则可以正确地渲染视图框内的文本。我认为这是WPF中的一个错误。基本上,如果您尝试以下操作:

<Viewbox Height="100" Stretch="Uniform" TextOptions.TextFormattingMode="Display">
    <TextBlock FontFamily="Georgia">My Cool Text</TextBlock>
</Viewbox>

你将会得到和我最开始展示的图片相同的结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接