web-dev-qa-db-ja.com

既知のフォントサイズと文字のWPF TextBlock幅を計算する方法は?

テキスト"Some Text"およびfont size 10.を含むTextBlockがあるとします。

適切なTextBlockwidthの計算方法

68
Developer

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レイアウトで使用できるデバイスに依存しないピクセルを返します。

140
RandomEngy

記録のために...ビジュアルツリーに追加された後に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
34
user1604008

提供されたソリューションは.Net Framework 4.5に適していましたが、Windows 10 DPIスケーリングとFramework 4.6.xがさまざまな程度のサポートを追加するため、テキストの測定に使用されるコンストラクターは[Obsolete]、およびpixelsPerDipパラメーターを含まないそのメソッドのコンストラクター。

残念ながら、もう少し複雑ですが、新しいスケーリング機能により精度が向上します。

PixelsPerDip

MSDNによると、これは次のことを表しています。

密度ごとのピクセル数に依存しないピクセル値。スケール係数に相当します。たとえば、画面のDPIが120(または120/96 = 1.25であるため1.25)の場合、密度に依存しないピクセルごとに1.25ピクセルが描画されます。 DIPは、デバイスの解像度とDPIに依存しないようにWPFが使用する測定単位です。

Microsoft/WPF-Samples GitHubリポジトリ(DPIスケーリング対応)のガイダンスに基づいて選択した回答を実装します。

完全にWindows 10 Anniversary(コードの下)の時点でDPIスケーリングをサポートするために必要な追加の構成がいくつかありますが、これは動作しませんでしたが、これなしでは、これはスケーリングが設定された(およびスケーリングの変更を考慮した)単一のモニターで機能します。上記のリポジトリのWord文書は、これらの値を追加するとアプリケーションが起動しないため、その情報のソースです。 このサンプルコード 同じレポからも良い基準点として機能しました。

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)
    {
        DpiInfo 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のドキュメントによると、複数のモニター構成でディスプレイごとに異なるDPI設定を持つWindows 10 Anniversaryの機能をカバーするには、アプリケーションのマニフェストにいくつかの設定を追加する必要があります。これらの設定がなければ、ウィンドウが異なる設定であるディスプレイから別のディスプレイに移動されたときに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>

ドキュメントによると:

[これらの] 2つのタグの組み合わせには、次の効果があります。1)Windows 10 Anniversary Update以上のモニターごと2)System <Windows 10 Anniversary Update

9
mdip

バックエンドコードの要素にバインディングパスを追加することでこれを解決しました。

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

これは、FormattedTextのような上記の参照のすべてのオーバーヘッドをコードに追加するよりもはるかにクリーンなソリューションであることがわかりました。

その後、私はこれを行うことができました:

double d_width = MyText.Width;
4
Chef Pharaoh

私はうまくいくいくつかの方法を見つけました...

/// <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);
}
4
Developer

私はこれを使用します:

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)
0
isxaker