应用 TextWrapping 后如何获得具有内联元素支持的 TextBlock 行?

How to get TextBlock lines with Inline element support after applying TextWrapping?

我试图在多个包含 TextBlock 的页面上拆分一个很长的字符串(文档),但是,我需要使每个页面都有特定的行数,这意味着我需要将 TextBlock 拆分为多行。

我尝试创建多个逻辑,但没有得到准确的东西,但在这里找到了一个解决方案 (),它在我的原型项目上对我有用,然后停止工作并将整个文本合而为一行。

这是上述主题的代码:

public static class TextUtils
    {
        public static IEnumerable<string> GetLines(this TextBlock source)
        {
            var text = source.Text;
            int offset = 0;
            TextPointer lineStart = source.ContentStart.GetPositionAtOffset(1, LogicalDirection.Forward);
            do
            {
                TextPointer lineEnd = lineStart != null ? lineStart.GetLineStartPosition(1) : null;
                int length = lineEnd != null ? lineStart.GetOffsetToPosition(lineEnd) : text.Length - offset;
                yield return text.Substring(offset, length);
                offset += length;
                lineStart = lineEnd;
            }
            while (lineStart != null);
        }
    }

这是我的代码:

<TextBlock x:Name="testTB" TextAlignment="Justify" FontFamily="Arial" FontSize="12" TextWrapping="Wrap" Width="100"/>
testTB.Text = Functions.GenString(200);

foreach (string xc in testTB.GetLines())
{
    MessageBox.Show(xc);
}

我猜问题是 lineStart.GetLineStartPosition(1) 正在返回 null

感谢任何帮助,提前致谢。

对我来说,您发布的代码看起来容易出错。它仅在 TextBlock 包含纯文本时才有效。但是,当您使用 Inline 元素(如 RunBoldUnderline 时,您将不再有纯文本作为内容,还有上下文标记(如内联元素的标签)。我想这就是您基于偏移量的 string.Substring 失败的地方。

解决方案是从检索到的 TextPointer 结果中创建一个 TextRange 并通过 TextRange.Text 属性.

提取纯文本

以下实现同时支持:通过 TextBlock.Text 属性 设置的纯文本和使用 Inline 元素设置的文本:

public static IEnumerable<string> GetLines(this TextBlock source)
{
  TextPointer lineStart = source.ContentStart.GetPositionAtOffset(1, LogicalDirection.Forward);
  do
  {
    TextPointer lineEnd = lineStart.GetLineStartPosition(1) ?? source.ContentEnd; 
    var textRange = new TextRange(lineStart, lineEnd);
    lineStart = lineEnd;
    yield return textRange.Text;
  }
  while (lineStart.IsAtLineStartPosition);
}

备注

等待 TextBlock.Loaded 事件发生是很重要的。这是因为 TextBlockUIElement.Measure 过程中将单个文本字符串拆分为多行,因为这是控件知道其所需大小以及行的最大可用宽度的时刻。 UIElement.Measure 在布局加载开始时由渲染引擎调用。

例子

MainWindow.xaml

<Window>
  <TextBlock x:Name="TextBlock" 
             TextWrapping="Wrap"
             Width="100">
    <TextBlock.Inlines>
      <Run
        Text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat," />
      <Bold>
        <Bold.Inlines>
          <Run Text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, conse" />
        </Bold.Inlines>
      </Bold>
      <LineBreak />
      <LineBreak />
      <LineBreak />
      <Underline>
        <Run Text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut " />
        <Run Text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut " />
      </Underline>
      <Run Text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut " />
    </TextBlock.Inlines>
  </TextBlock>
</Window>

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public MainWindow()
  {
    this.Loaded += OnLoaded;
  }

  private void OnLoaded(object sender, EventArgs e)
  {
    var lines = this.TextBlock.GetLines().ToList(); // Returns 54 lines
  }
}