WPF为啥改变TextBlock字体Button,Label等控件的字体都变了

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF为啥改变TextBlock字体Button,Label等控件的字体都变了相关的知识,希望对你有一定的参考价值。

如题,代码如下
<Style TargetType="x:Type TextBlock">
<Setter Property="FontFamily" Value="华文彩云"/>
</Style>
模板里我确定是没有TextBlock的!!!!

因为wpf里每个控件都有其控件模板(ControlTemplate)。如果你有blend可以任意打开个控件看看其Template里的内容。Button,Label等控件显示文字的地方在它们的模板内都用了 TextBlock。 所以当你设置TextBlock的样式的时候,这些控件的字体也都变了。追问

我确实用Blend看过,模板里没有TextBlock

追答

恩,我刚建了个demo测试了下,在Resource里加了TextBlock的style。但是我Button和Label控件的字体样式没有发生变化。Button和Label模板里用来显示内容的是ContentPresenter。也就是说明它们的Content是可以被定制的。任何控件都可以放在这里面。你看下是否这些Button和label的Content里面有放textblock.

参考技术A 因为button label等控件默认模版里显示文字的地方用的就是textblock. 所以textblock写样式时必须得加Key,然后需要用样式的textblock 显示引用样式。追问

模板里没有TextBlock,这个TextBlock从何而来

如何计算已知字体大小和字符的 WPF TextBlock 宽度?

【中文标题】如何计算已知字体大小和字符的 WPF TextBlock 宽度?【英文标题】:How to calculate WPF TextBlock width for its known font size and characters? 【发布时间】:2012-03-05 02:00:38 【问题描述】:

假设我有 TextBlock 的文本 "Some Text"字体大小 10.0

如何计算合适的TextBlock宽度

【问题讨论】:

这个问题之前已经问过了,也和字体有关。 你也可以从ActualWidth获取实际宽度。 使用 TextRenderer 也应该适用于 WPF:***.com/questions/721168/… 【参考方案1】:

为你找到了这个:

Graphics g = control.CreateGraphics();
int width =(int)g.MeasureString(aString, control.Font).Width; 
g.dispose();

【讨论】:

这种具体做法只适用于WinForms。【参考方案2】:

使用FormattedText 类。

我在我的代码中创建了一个辅助函数:

private Size MeasureString(string candidate)

    var formattedText = new FormattedText(
        candidate,
        CultureInfo.CurrentCulture,
        FlowDirection.LeftToRight,
        new Typeface(this.textBlock.FontFamily, this.textBlock.FontStyle, this.textBlock.FontWeight, this.textBlock.FontStretch),
        this.textBlock.FontSize,
        Brushes.Black,
        new NumberSubstitution(),
        1);

    return new Size(formattedText.Width, formattedText.Height);

它返回可用于 WPF 布局的与设备无关的像素。

【讨论】:

这真的很有帮助 如何在 UWP 中做同样的事情 @ArunPrasad 在单独的问题上提出这将是一件很棒的事情。这个问题显然适用于 WPF。 如果传入的候选是空格,则返回宽度为零。我认为这可能与自动换行有关? 在我的情况下,您还需要配置这些:c# formattedText.MaxTextHeight = textBlock.MaxHeight; formattedText.MaxTextWidth = textBlock.MaxWidth; formattedText.Trimming = TextTrimming.None; // Change this value in case you do have trimming 【参考方案3】:

我找到了一些效果很好的方法...

/// <summary>
/// Get the required height and width of the specified text. Uses Glyph's
/// </summary>
public static Size MeasureText(string text, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double fontSize)

    Typeface typeface = new Typeface(fontFamily, fontStyle, fontWeight, fontStretch);
    GlyphTypeface glyphTypeface;

    if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
    
        return MeasureTextSize(text, fontFamily, fontStyle, fontWeight, fontStretch, fontSize);
    

    double totalWidth = 0;
    double height = 0;

    for (int n = 0; n < text.Length; n++)
    
        ushort glyphIndex = glyphTypeface.CharacterToGlyphMap[text[n]];

        double width = glyphTypeface.AdvanceWidths[glyphIndex] * fontSize;

        double glyphHeight = glyphTypeface.AdvanceHeights[glyphIndex] * fontSize;

        if (glyphHeight > height)
        
            height = glyphHeight;
        

        totalWidth += width;
    

    return new Size(totalWidth, height);


/// <summary>
/// Get the required height and width of the specified text. Uses FortammedText
/// </summary>
public static Size MeasureTextSize(string text, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double fontSize)

    FormattedText ft = new FormattedText(text,
                                            CultureInfo.CurrentCulture,
                                            FlowDirection.LeftToRight,
                                            new Typeface(fontFamily, fontStyle, fontWeight, fontStretch),
                                            fontSize,
                                            Brushes.Black);
    return new Size(ft.Width, ft.Height);

【讨论】:

对大多数字体求和字形宽度不起作用,因为它不考虑kerning。【参考方案4】:

我通过在后端代码中为元素添加绑定路径解决了这个问题:

<TextBlock x:Name="MyText" Width="Binding Path=ActualWidth, ElementName=MyText" />

我发现这是一个比将 FormattedText 等上述引用的所有开销添加到我的代码中更简洁的解决方案。

之后,我能够做到这一点:

double d_width = MyText.Width;

【讨论】:

你可以简单地做d_width = MyText.ActualWidth;而不用绑定。问题是TextBlock 还没有在可视化树中。【参考方案5】:

为了记录... 我假设操作者正在尝试以编程方式确定 textBlock 在添加到可视化树后将占用的宽度。 IMO 比 formattedText 更好的解决方案(你如何处理 textWrapping 之类的东西?)将是在示例 TextBlock 上使用 Measure 和 Arrange。例如

var textBlock = new TextBlock  Text = "abc abd adfdfd", TextWrapping = TextWrapping.Wrap ;
// auto sized
textBlock.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
textBlock.Arrange(new Rect(textBlock.DesiredSize));

Debug.WriteLine(textBlock.ActualWidth); // prints 80.323333333333
Debug.WriteLine(textBlock.ActualHeight);// prints 15.96

// constrain the width to 16
textBlock.Measure(new Size(16, Double.PositiveInfinity));
textBlock.Arrange(new Rect(textBlock.DesiredSize));

Debug.WriteLine(textBlock.ActualWidth); // prints 14.58
Debug.WriteLine(textBlock.ActualHeight);// prints 111.72

【讨论】:

经过一些小的修改,这对我编写 Windows 8.1 应用商店应用程序(Windows 运行时)很有用。不要忘记设置此 TextBlock 的字体信息,以便准确计算宽度。简洁的解决方案,值得一票。 这里的关键点是就地创建这个临时文本块。所有这些对页面上现有文本块的操作都不起作用:它的 ActualWidth 仅在重绘后更新。【参考方案6】:

提供的解决方案适用于 .Net Framework 4.5,但是,随着 Windows 10 DPI 缩放和 Framework 4.6.x 为其添加不同程度的支持,用于测量文本的构造函数现在标记为 [Obsolete],以及任何该方法上不包含 pixelsPerDip 参数的构造函数。

不幸的是,它涉及更多一点,但它会通过新的缩放功能提高准确性。

###PixelsPerDip

根据 MSDN,这表示:

Pixels Per Density Independent Pixel 值,相当于比例因子。例如,如果屏幕的 DPI 为 120(或 1.25,因为 120/96 = 1.25),则每个密度独立像素绘制 1.25 个像素。 DIP 是 WPF 使用的独立于设备分辨率和 DPI 的测量单位。

这是我根据Microsoft/WPF-Samples GitHub 存储库的指导对所选答案的实现,并具有 DPI 缩放意识。

需要一些额外的配置才能完全支持 Windows 10 周年纪念版的 DPI 缩放(代码下方),我无法开始工作,但没有它,它可以在单个显示器上工作配置了缩放(并尊重缩放更改)。上述 repo 中的 Word 文档是该信息的来源,因为一旦我添加了这些值,我的应用程序就不会启动。来自同一个仓库的This sample code 也是一个很好的参考点。

public partial class MainWindow : Window

    private DpiScale m_dpiInfo;
    private readonly object m_sync = new object();

    public MainWindow()
    
        InitializeComponent();
        Loaded += OnLoaded;
    
    
    private Size MeasureString(string candidate)
    
        DpiScale dpiInfo;
        lock (m_dpiInfo)
            dpiInfo = m_dpiInfo;

        if (dpiInfo == null)
            throw new InvalidOperationException("Window must be loaded before calling MeasureString");

        var formattedText = new FormattedText(candidate, CultureInfo.CurrentUICulture,
                                              FlowDirection.LeftToRight,
                                              new Typeface(this.textBlock.FontFamily, 
                                                           this.textBlock.FontStyle, 
                                                           this.textBlock.FontWeight, 
                                                           this.textBlock.FontStretch),
                                              this.textBlock.FontSize,
                                              Brushes.Black, 
                                              dpiInfo.PixelsPerDip);
        
        return new Size(formattedText.Width, formattedText.Height);
    

// ... The Rest of Your Class ...

    /*
     * Event Handlers to get initial DPI information and to set new DPI information
     * when the window moves to a new display or DPI settings get changed
     */
    private void OnLoaded(object sender, RoutedEventArgs e)
                
        lock (m_sync)
            m_dpiInfo = VisualTreeHelper.GetDpi(this);
    

    protected override void OnDpiChanged(DpiScale oldDpiScaleInfo, DpiScale newDpiScaleInfo)
    
        lock (m_sync)
            m_dpiInfo = newDpiScaleInfo;

        // Probably also a good place to re-draw things that need to scale
    

其他要求

根据 Microsoft/WPF-Samples 上的文档,您需要向应用程序的清单中添加一些设置,以涵盖 Windows 10 Anniversary 在多显示器配置中为每个显示器设置不同 DPI 设置的能力。可以公平地猜测,如果没有这些设置,当窗口从一个显示器移动到另一个具有不同设置的显示器时,可能不会引发 OnDpiChanged 事件,这将使您的测量继续依赖于之前的DpiScale。我正在编写的应用程序是为我一个人编写的,我没有那种设置,所以我没有什么可测试的,当我按照指导进行时,我最终得到了一个由于清单而无法启动的应用程序错误,所以我放弃了,但最好检查一下并调整您的应用清单以包含:

<application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
        <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
        <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
    </windowsSettings>
</application>

根据文档:

[这些]两个标签的组合具有以下效果: 1) >= Windows 10 周年更新的每个监视器 2) 系统

【讨论】:

【参考方案7】:

我用这个:

var typeface = new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch);
var formattedText = new FormattedText(textBlock.Text, Thread.CurrentThread.CurrentCulture, textBlock.FlowDirection, typeface, textBlock.FontSize, textBlock.Foreground);

var size = new Size(formattedText.Width, formattedText.Height)

【讨论】:

以上是关于WPF为啥改变TextBlock字体Button,Label等控件的字体都变了的主要内容,如果未能解决你的问题,请参考以下文章

wpf 用button实现TextBlock里字体颜色的切换

WPF如何改变GridView字体大小

wpf中怎么是鼠标移动到textblock中的字体上使其改变颜色

WPF 动态生成一个button 和一个textblock 怎么给textblock添加数据

wpf中 定义一个button 在button的内部放置一个label,当点击label的时

如何计算已知字体大小和字符的 WPF TextBlock 宽度?