Как получить строки TextBlock с поддержкой встроенных элементов после применения TextWrapping? - PullRequest
1 голос
/ 16 марта 2020

Я пытаюсь разбить очень длинную строку (документ) на несколько страниц, содержащих TextBlock, однако мне нужно сделать каждую страницу с определенным c числом строк, что означает, что мне нужно разбить TextBlock на строки .

Я пытался создать несколько логик, но безуспешно, чтобы получить точную вещь, но нашел здесь решение ( Получить строки TextBlock в соответствии со свойством TextWrapping? ), которое работало для я на моем прототипе проекта затем перестал работать и получает весь текст в одну строку.

Вот код из приведенного выше topi c:

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 .

Любая помощь приветствуется, спасибо заранее.

1 Ответ

1 голос
/ 17 марта 2020

Для меня код, который вы разместили, выглядит подверженным ошибкам. Это будет работать, только если TextBlock содержит простой текст. Но когда вы используете Inline элементы, такие как Run, Bold или Underline, у вас больше не будет простого текста в качестве содержимого, но также есть контекстные маркеры, такие как теги для встроенных элементов. Я предполагаю, что это где ваше смещение на основе string.Substring терпит неудачу.

Решение состоит в том, чтобы создать TextRange из полученных TextPointer результатов и извлечь простой текст с помощью свойства 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. Это связано с тем, что TextBlock разбивает одну текстовую строку на строки во время процесса UIElement.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
  }
}
...