Reputation: 301
I'm trying to split a very long string (document) on several pages containing a TextBlock, however, I need to make each page of specific number of lines which means that I need to split the TextBlock into lines.
I tried to create several logics but no luck of getting an accurate thing, but found a solution here (Get the lines of the TextBlock according to the TextWrapping property?) which worked for me on my prototype project then stopped working and gets the whole text in one line.
Here is the code from the above topic:
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);
}
}
And this is my code:
<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);
}
Where I guess that the issue is that lineStart.GetLineStartPosition(1)
is returning null.
Any help is appreciated, thanks in advance.
Upvotes: 2
Views: 1163
Reputation: 28968
To me the code you have posted looks error prone. It will work only if the TextBlock
contains plain text. But when you are using Inline
elements like Run
, Bold
or Underline
, you no more have plain text as content, but also context markers like tags for the inline elements. I guess this is where your offset based string.Substring
fails.
The solution is to create a TextRange
from the retrieved TextPointer
results and extract the plain text via the TextRange.Text
property.
The following implementation supports both: plain text set via the TextBlock.Text
property and text set using Inline
elements:
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);
}
It is important to wait until the TextBlock.Loaded
event was raised. This is because the TextBlock
splits the single text string into lines during the UIElement.Measure
process, as this is the moment where the control knows its desired size and therefore the max available width of a line. UIElement.Measure
is invoked by the rendering engine, when the layout loading has started.
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
}
}
Upvotes: 2