UWP:在RichTextBlock计算文本高度给出了怪异的结果(UWP: Compute text

2019-09-26 15:41发布

我需要一个可靠的方法来获取包含在该文本的高度RichTextBlock ,甚至在它实际上是在现场绘制。

使用正常测量()方法产生奇怪的结果,因为它可以在MVCE可以看出: https://github.com/cghersi/UWPExamples/tree/master/MeasureText (我想保持fiexed宽度,并测量最终高度,但DesiredSize的结果是从实际高度大不相同!)。

出于这个原因,我发现了一个粗略的方法(这里提到https://stackoverflow.com/a/45937298/919700 ),那我引申为我的目的,在这里我们使用一些Win2D API来计算内容的高度。

的问题是,在某些情况下,这种方法提供了一个高度比所预期的要小。

  1. 有没有检索TextBlock的(正确的)高度的通用方式,它被描绘在现场甚至过吗?
  2. 如果不是这种情况,我究竟做错了什么?

这里是我的代码(你也可以找到,因为这里MVCE: 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对象?

Answer 1:

看你MeasureText MVCE代码,并在RichTextBlock调用办法()的问题归结到这条线:

    m_textBlock.Margin = new Thickness(200);

这台200在所有侧面上的通用余量,这意味着该元件需要在左侧至少200宽度加上200宽度在右边,或400的宽度。 由于您的测量(300,无限)规定的低于最低要求的400宽度的可用宽度,将RichTextBlock决定它能做的最好的是自动换行的每一个字符,产生了大量的5740像素高度(加200 + 200从边缘高度)。

如果删除线,所述RichTextBlock将使用300指定的约束和正确地测量它的期望的高度为90个像素,这是它呈现为在屏幕上(如果设置宽度= 300或以其它方式导致的实际元件布局有同样的约束)。

或者,因为你知道你想要的元素的宽度,可以设置宽度=上300,然后它会与宽度测量。 高度将扩大为集保证金的结果,虽然。

我假设你实际上并没有保证金= 200集你的真正的应用程序,而是必须考虑到保证金类似保证金= 5时你真正想要当RichTextBlock在树和绘图。 如果是这种情况,那么您可以:

  1. 使用宽度= 300的方法用于测量和减去关闭从DesireSize.Height顶部+底部边缘。
  2. 与测量(300 + margin.Left + margin.Right)的宽度,这样一旦保证金是从总量减去availableSize关闭剩余宽度可以使用文本您打算300.你仍然需要减去折顶+从DesireSize.Height底部边缘。


文章来源: UWP: Compute text height in a RichTextBlock gives weird results