web-dev-qa-db-fra.com

Comment calculer la largeur de WPF TextBlock pour sa taille de police et ses caractères connus?

Disons que j'ai TextBlock avec le texte "Some Text" et taille de la police 10.0 .

Comment puis-je calculer TextBlock width ?

64
Academy of Programmer

Utilisez la classe FormattedText .

J'ai créé une fonction d'assistance dans mon code:

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

Il retourne des pixels indépendants du périphérique qui peuvent être utilisés dans une présentation WPF.

130
RandomEngy

Pour mémoire, je suppose que l’opérateur essaie de déterminer par programmation la largeur que le bloc de texte occupera après son ajout à l’arborescence des éléments visuels. IMO une meilleure solution que formatedText (comment gérez-vous quelque chose comme textWrapping?) Consisterait à utiliser Measure et Arrange sur un exemple de TextBlock par exemple.

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

La solution fournie était appropriée pour .Net Framework 4.5. Toutefois, avec l’adaptation de l’opérateur Windows 10 et le Framework 4.6.x avec divers degrés de prise en charge, le constructeur utilisé pour mesurer le texte est maintenant marqué [Obsolete], ainsi que tous les constructeurs de cette méthode qui n'incluez pas le paramètre pixelsPerDip.

Malheureusement, c'est un peu plus compliqué, mais les nouvelles capacités de mise à l'échelle permettront une plus grande précision.

PixelsPerDip

Selon MSDN, cela représente:

La valeur Pixels par densité indépendante du pixel, qui équivaut au facteur d'échelle. Par exemple, si le DPI d'un écran est de 120 (ou 1,25 car 120/96 = 1,25), 1,25 pixel par pixel indépendant par densité est dessiné. Le DIP est l'unité de mesure utilisée par WPF pour être indépendante de la résolution du périphérique et des DPI.

Voici mon implémentation de la réponse sélectionnée sur la base des instructions du référentiel Microsoft/WPF-Samples GitHub avec prise en compte de la mise à l'échelle DPI.

Une configuration supplémentaire est nécessaire pour complètement prendre en charge la mise à l'échelle DPI à partir de Windows 10 Anniversary (sous le code), ce que je ne pourrais pas utiliser, mais sans elle, cela fonctionne sur un seul moniteur avec la mise à l'échelle configurée changements d'échelle). Le document Word dans le référentiel ci-dessus est la source de cette information car mon application ne se lancerait pas une fois que j'aurais ajouté ces valeurs. Cet exemple de code du même référentiel a également servi de bon point de référence.

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

Autres exigences

Selon la documentation fournie dans Microsoft/WPF-Samples, vous devez ajouter certains paramètres au manifeste de l'application pour que Windows 10 Anniversary puisse utiliser différents paramètres DPI par écran dans des configurations à plusieurs moniteurs. Il va sans dire que sans ces paramètres, l'événement OnDpiChanged pourrait ne pas être déclenché lorsqu'une fenêtre est déplacée d'un affichage à un autre avec des paramètres différents, ce qui ferait que vos mesures continueraient à s'appuyer sur la précédente DpiScale. L’application que j’écrivais était pour moi, seule, et je n’ai pas ce genre de configuration, je n’avais donc rien à tester et lorsque j’ai suivi les instructions, j’ai fini avec une application qui ne pouvait pas démarrer en raison d’un manifeste. des erreurs, alors j’ai abandonné, mais ce serait une bonne idée d’examiner cela et d’ajuster le manifeste de votre application pour contenir:

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

Selon la documentation:

La combinaison de [ces] deux balises a l’effet suivant: 1) Par moniteur pour> = Windows 10 Anniversary Update 2) Système <Windows 10 Anniversary Update

6
mdip

J'ai résolu ce problème en ajoutant un chemin de liaison à l'élément dans le code backend:

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

J'ai trouvé cette solution beaucoup plus propre que d'ajouter tout le temps système des références ci-dessus, telles que FormattedText, à mon code.

Après, j'ai pu faire ceci:

double d_width = MyText.Width;
4
Chef Pharaoh

J'ai trouvé des méthodes qui fonctionnent bien ...

/// <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
Academy of Programmer

J'utilise celui-ci:

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