UWP:计算RichTextBlock中的文本高度会产生奇怪的结果 [英] UWP: Compute text height in a RichTextBlock gives weird results

查看:90
本文介绍了UWP:计算RichTextBlock中的文本高度会产生奇怪的结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要一种可靠的方法来获取RichTextBlock中包含的文本的高度,甚至在将其实际绘制到场景上之前.

I need a reliable method to get the height of the text contained in a RichTextBlock, even before it is actually drawn on the scene.

使用常规Measure()方法会产生怪异的结果,如在MVCE中可以看到的那样:

Using the normal Measure() method produces a weird result, as it can be seen in the MVCE: https://github.com/cghersi/UWPExamples/tree/master/MeasureText (I want to keep fiexed the width, and measure the final height, but the result of DesiredSize is far different from the actual height!!).

基于这个原因,我找到了一种粗略的方法(在此处 https://stackoverflow.com/a/45937298/919700),我将其扩展以实现自己的目的,即使用一些Win2D API计算内容高度.

For this reason, I found a rough method (mentioned here https://stackoverflow.com/a/45937298/919700), that I extended to serve my purpose, where we use some Win2D API to compute the content height.

问题在于,在某些情况下,此方法提供的高度小于预期的高度.

The problem is that in some cases, this method provides an height that is smaller than the expected one.

  1. 有没有一种通用的方法来检索(正确的)高度 TextBlock,甚至还没有绘制在场景上之前?
  2. 如果不是这种情况,我在做什么错了?
  1. Is there a general way to retrieve the (correct) height of a TextBlock, even before it is drawn on the scene?
  2. If this is not the case, what am I doing wrong?

这是我的代码(您也可以在此处找到MVCE: https://github.com/cghersi/UWPExamples/tree/master/RichText ):

Here's my code (which you can find also as MVCE here: https://github.com/cghersi/UWPExamples/tree/master/RichText):

    public sealed partial class MainPage
    {
        public static readonly FontFamily FONT_FAMILY = new FontFamily("Assets/paltn.ttf#Palatino-Roman");
        public const int FONT_SIZE = 10;
        private readonly Dictionary<string, object> FONT = new Dictionary<string, object>
        {
            { AttrString.FONT_FAMILY_KEY, FONT_FAMILY },
            { AttrString.FONT_SIZE_KEY, FONT_SIZE },
            { AttrString.LINE_HEAD_INDENT_KEY, 10 },
            { AttrString.LINE_SPACING_KEY, 1.08 },
            { AttrString.FOREGROUND_COLOR_KEY, new SolidColorBrush(Colors.Black) }
        };

        // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
        private readonly RichTextBlock m_displayedText;

        public MainPage()
        {
            InitializeComponent();

            // create the text block:
            m_displayedText = new RichTextBlock
            {
                MaxLines = 0, //Let it use as many lines as it wants
                TextWrapping = TextWrapping.Wrap,
                AllowFocusOnInteraction = false,
                IsHitTestVisible = false,
                Width = 80,
                Height = 30,
                Margin = new Thickness(100)
            };

            // set the content with the right properties:
            AttrString content = new AttrString("Excerpt1 InkLink", FONT);
            SetRichText(m_displayedText, content);

            // add to the main panel:
            MainPanel.Children.Add(m_displayedText);

            // compute the text height: (this gives the wrong answer!!):
            double textH = GetRichTextHeight(content, (float)m_displayedText.Width);
            Console.WriteLine("text height: {0}", textH);
        }

        public static double GetRichTextHeight(AttrString text, float maxWidth)
        {
            if (text == null)
                return 0;

            CanvasDevice device = CanvasDevice.GetSharedDevice();
            double finalH = 0;
            foreach (AttributedToken textToken in text.Tokens)
            {
                CanvasTextFormat frmt = new CanvasTextFormat()
                {
                    Direction = CanvasTextDirection.LeftToRightThenTopToBottom,
                    FontFamily = textToken.Get(AttrString.FONT_FAMILY_KEY, FONT_FAMILY).Source,
                    FontSize = textToken.Get(AttrString.FONT_SIZE_KEY, FONT_SIZE),
                    WordWrapping = CanvasWordWrapping.Wrap
                };
                CanvasTextLayout layout = new CanvasTextLayout(device, textToken.Text, frmt, maxWidth, 0f);
                finalH += layout.LayoutBounds.Height;
            }

            return finalH;

            //return textBlock.Blocks.Sum(block => block.LineHeight);
        }

        private static void SetRichText(RichTextBlock label, AttrString str)
        {
            if ((str == null) || (label == null))
                return;
            label.Blocks.Clear();
            foreach (AttributedToken token in str.Tokens)
            {
                Paragraph paragraph = new Paragraph()
                {
                    TextAlignment = token.Get(AttrString.TEXT_ALIGN_KEY, TextAlignment.Left),
                    TextIndent = token.Get(AttrString.LINE_HEAD_INDENT_KEY, 0),
                };
                double fontSize = token.Get(AttrString.FONT_SIZE_KEY, FONT_SIZE);
                double lineSpacing = token.Get(AttrString.LINE_SPACING_KEY, 1.0);
                paragraph.LineHeight = fontSize * lineSpacing;
                paragraph.LineStackingStrategy = LineStackingStrategy.BlockLineHeight;
                Run run = new Run
                {
                    Text = token.Text,
                    FontFamily = token.Get(AttrString.FONT_FAMILY_KEY, FONT_FAMILY),
                    FontSize = fontSize,
                    Foreground = token.Get(AttrString.FOREGROUND_COLOR_KEY, new SolidColorBrush(Colors.Black)),
                    FontStyle = token.Get(AttrString.ITALIC_KEY, false) ? 
                        Windows.UI.Text.FontStyle.Italic : Windows.UI.Text.FontStyle.Normal
                };
                paragraph.Inlines.Add(run);
                label.Blocks.Add(paragraph);
            }
        }
    }

    public class AttrString
    {
        public const string FONT_FAMILY_KEY = "Fam";
        public const string FONT_SIZE_KEY = "Size";
        public const string LINE_HEAD_INDENT_KEY = "LhI";
        public const string LINE_SPACING_KEY = "LSpace";
        public const string FOREGROUND_COLOR_KEY = "Color";
        public const string ITALIC_KEY = "Ita";
        public const string TEXT_ALIGN_KEY = "Align";
        public const string LINE_BREAK_MODE_KEY = "LineBreak";

        public static Dictionary<string, object> DefaultCitationFont { get; set; }
        public static Dictionary<string, object> DefaultFont { get; set; }

        public List<AttributedToken> Tokens { get; set; }

        public AttrString(string text, Dictionary<string, object> attributes)
        {
            Tokens = new List<AttributedToken>();
            Append(text, attributes);
        }

        public AttrString(AttrString copy)
        {
            if (copy?.Tokens == null)
                return;
            Tokens = new List<AttributedToken>(copy.Tokens);
        }

        public AttrString Append(string text, Dictionary<string, object> attributes)
        {
            Tokens.Add(new AttributedToken(text, attributes));
            return this;
        }

        public bool IsEmpty()
        {
            foreach (AttributedToken t in Tokens)
            {
                if (!string.IsNullOrEmpty(t.Text))
                    return false;
            }

            return true;
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (AttributedToken t in Tokens)
            {
                sb.Append(t.Text);
            }
            return sb.ToString();
        }
    }

    public class AttributedToken
    {
        public string Text { get; set; }

        public Dictionary<string, object> Attributes { get; set; }

        public AttributedToken(string text, Dictionary<string, object> attributes)
        {
            Text = text;
            Attributes = attributes;
        }

        public T Get<T>(string key, T defaultValue)
        {
            if (string.IsNullOrEmpty(key) || (Attributes == null))
                return defaultValue;
            if (Attributes.ContainsKey(key))
                return (T)Attributes[key];
            else
                return defaultValue;
        }

        public override string ToString()
        {
            return Text;
        }
    }

**更新**:

在进一步研究问题之后,该问题似乎与CanvasTextFormat对象的缺乏可配置性有关,尤其是对于第一行的缩进(在RichTextBlock中使用属性Paragraph.TextIndent表示).有什么方法可以在CanvasTextFormat对象中指定这种设置吗?

After further digging into the issue, the problem seems related to the lack of configurability for the CanvasTextFormat object, especially for the indentation of the first line (expressed in the RichTextBlock using the property Paragraph.TextIndent). Is there any way to specify such setting in a CanvasTextFormat object?

推荐答案

查看您的MeasureText MVCE代码,在RichTextBlock上调用Measure()的问题归结为这一行:

Looking at your MeasureText MVCE code, the problem with calling Measure() on the RichTextBlock comes down to this line:

    m_textBlock.Margin = new Thickness(200);

这将所有边上的通用边距设置为200,这意味着元素至少需要左侧200宽度和右侧200宽度或400宽度.由于您的Measure(300,infinite)指定的可用宽度小于最小所需的400宽度,因此RichTextBlock决定,最好的办法是在每个字符处换行,产生5740像素的高度(加上200 + 200距边缘的高度).

This sets a universal margin of 200 on all sides, which means the element needs at least 200 width on the left plus 200 width on the right, or 400 width. Since your Measure(300,infinite) specifies an available width of less than the minimum required 400 width, the RichTextBlock decides that the best it can do is wrap the text at every character, producing the massive 5740 pixel height (plus the 200+200 height from the margin).

如果删除该行,RichTextBlock将使用指定的约束300,并正确测量其所需的高度为90像素,这是它在屏幕上呈现的高度(如果您设置Width = 300或以其他方式导致实际元素布局具有相同的约束).

If you remove that line, the RichTextBlock will use the specified constraint of 300 and correctly measure its desired height as 90 pixels, which is what it renders as on screen (if you set Width=300 or otherwise result in the actual element layout to have the same constraint).

或者,由于您知道元素所需的宽度,因此可以在其上设置Width = 300,然后它将以该宽度进行测量.不过,由于设置了边距",因此高度会增加.

Alternatively, since you know the width you want for the element, you could set Width=300 on it and it will then measure with that width. The Height will be expanded as a result of the set Margin, though.

我假设您的真实应用程序中实际上没有设置Margin = 200,而是使用了诸如Margin = 5之类的较小值来说明RichTextBlock在树和图形中时实际需要的边距.如果是这种情况,那么您可以:

I'm assuming you don't actually have Margin=200 set in your real app, and instead have something smaller like Margin=5 to account for margin you actually want when the RichTextBlock is in the tree and drawing. If this is the case, then you can either:

  1. 使用Width = 300方法进行测量,并从DesireSize.Height中减去顶部和底部的边距.
  2. 使用(300 + margin.Left + margin.Right)作为宽度进行度量,以便一旦从总availableSize中减去边距后,文本可以使用的剩余宽度就是您想要的300.您仍然需要减去从DesireSize.Height的顶部+底部边缘移出.

这篇关于UWP:计算RichTextBlock中的文本高度会产生奇怪的结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆