알려진 글꼴 크기 및 문자에 대한 WPF TextBlock 너비를 계산하는 방법은 무엇입니까?


81

TextBlock텍스트 "Some Text"글꼴 크기 10.0 이 있다고 가정 해 보겠습니다 .

적절한 TextBlock 너비를 어떻게 계산할 수 있습니까?


이것은 이전에 요청되었으며 글꼴에 따라 다릅니다.
HB

또한에서 실제 너비를 얻을 수 있습니다 ActualWidth.
HB

2
TextRenderer를 사용하면 WPF에서도 작동합니다. stackoverflow.com/questions/721168/…
HB

답변:


156

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 레이아웃에서 사용할 수있는 장치 독립적 픽셀을 반환합니다.


이건 정말 도움이되었다
케쉬

1
UWP에서 동일한 작업을 수행하는 방법
Arun Prasad

6
@ArunPrasad 별도의 질문에 대해 물어 보는 것이 좋습니다. 이 질문은 WPF로 명확하게 범위가 지정됩니다.
RandomEngy

전달 된 후보가 공백이면 너비 0을 반환합니다. 아마도 이것이 단어 줄 바꿈과 관련이 있다고 생각합니까?
Mark Miller

43

기록을 위해 ... 나는 운영자가 시각적 트리에 추가 된 후 textBlock이 차지할 너비를 프로그래밍 방식으로 결정하려고한다고 가정합니다. IMO는 formattedText (textWrapping과 같은 것을 어떻게 처리합니까?)보다 나은 솔루션은 샘플 TextBlock에서 Measure and 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

1
약간의 수정만으로 Windows 8.1 Store 앱 (Windows 런타임)을 작성하는 데 도움이되었습니다. 이 TextBlock의 글꼴 정보를 설정하여 너비를 정확하게 계산하는 것을 잊지 마십시오. 깔끔한 솔루션과 찬성 할 가치가 있습니다.
데이비드 총장

1
여기서 핵심은이 임시 텍스트 블록을 제자리에 만드는 것입니다. 페이지의 기존 텍스트 블록을 사용한 이러한 모든 조작은 작동하지 않습니다. ActualWidth는 다시 그린 후에 만 ​​업데이트됩니다.
Tertium

13

제공된 솔루션은 .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는 WPF에서 장치 해상도 및 DPI와 독립적으로 사용되는 측정 단위입니다.

다음 은 DPI 확장 인식 기능이 있는 Microsoft / WPF-Samples GitHub 리포지토리의 지침에 따라 선택한 답변을 구현 한 것입니다 .

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)
    {
        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의 설명서에 따르면, 다중 모니터 구성에서 디스플레이 당 다른 DPI 설정을 갖는 Windows 10 Anniversary의 기능을 포함하도록 애플리케이션의 매니페스트에 일부 설정을 추가해야합니다. 이러한 설정이 없으면 OnDpiChanged 이벤트가 다른 설정을 사용하여 한 디스플레이에서 다른 디스플레이로 이동할 때 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 1 주년 업데이트 2) 시스템 <Windows 10 1 주년 업데이트


5

잘 작동하는 몇 가지 방법을 찾았습니다 ...

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

3
글리프 너비 합계는 커닝을 고려하지 않기 때문에 대부분의 글꼴에서 작동하지 않습니다 .
Nicolas Repiquet 2012

4

백엔드 코드의 요소에 바인딩 경로를 추가하여이 문제를 해결했습니다.

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

이것은 FormattedText와 같은 위 참조의 모든 오버 헤드를 내 코드에 추가하는 것보다 훨씬 더 깨끗한 솔루션이라는 것을 알았습니다.

그 후 나는 이것을 할 수 있었다.

double d_width = MyText.Width;

2
d_width = MyText.ActualWidth;바인딩없이 간단하게 할 수 있습니다 . 문제 TextBlock는가 아직 시각적 트리에 없을 때 입니다.
xmedeko

0

나는 이것을 사용한다 :

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)

-3

다음을 찾았습니다.

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

24
이 특정 접근 방식은 WinForms에만 적용됩니다.
HB
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.