Other aspects of text in WPF handle text in the context of controls that are dedicated to text (TextBlock, TextBox, RichTextBox), implement the FlowDocument model. As far as I can tell, there is no direct way to transfer text from a FlowDocument to a FormattedText object. This post outlines how it can be done with a bit of jiggery poker.
The tricky part of this process is traversing through the runs and paragraphs in the FlowDocument:
private IEnumerable<TextElement> GetRunsAndParagraphs(FlowDocument doc)
{
// use the GetNextContextPosition method to iterate through the
// FlowDocument
for (TextPointer position = doc.ContentStart;
position != null && position.CompareTo(doc.ContentEnd) <= 0;
position = position.GetNextContextPosition(LogicalDirection.Forward))
{
if (position.GetPointerContext(LogicalDirection.Forward) ==
TextPointerContext.ElementEnd)
{
// return solely the Runs and Paragraphs. all other elements are
// ignored since they aren't supported by FormattedText.
Run run = position.Parent as Run;
if (run != null)
{
yield return run;
}
else
{
Paragraph para = position.Parent as Paragraph;
if (para != null)
{
yield return para;
}
}
}
}
}
The rest of process is fairly straightforward. Use the GetRunsAndParagraphs method and build up the formatting on a FormattedText object:
public FormattedText GetFormattedText(FlowDocument doc)
{
if (doc == null)
{
throw new ArgumentNullException("doc");
}
FormattedText output = new FormattedText(
GetText(doc),
CultureInfo.CurrentCulture,
doc.FlowDirection,
new Typeface(doc.FontFamily, doc.FontStyle, doc.FontWeight, doc.FontStretch),
doc.FontSize,
doc.Foreground);
int offset = 0;
foreach (TextElement el in GetRunsAndParagraphs(doc))
{
Run run = el as Run;
if (run != null)
{
int count = run.Text.Length;
output.SetFontFamily(run.FontFamily, offset, count);
output.SetFontStyle(run.FontStyle, offset, count);
output.SetFontWeight(run.FontWeight, offset, count);
output.SetFontSize(run.FontSize, offset, count);
output.SetForegroundBrush(run.Foreground, offset, count);
output.SetFontStretch(run.FontStretch, offset, count);
output.SetTextDecorations(run.TextDecorations, offset, count);
offset += count;
}
else
{
offset += Environment.NewLine.Length;
}
}
return output;
}
private string GetText(FlowDocument doc)
{
StringBuilder sb = new StringBuilder();
foreach (TextElement el in GetRunsAndParagraphs(doc))
{
Run run = el as Run;
sb.Append(run == null ? Environment.NewLine : run.Text);
}
return sb.ToString();
}
You can download the source code and demo here.
2 comments:
A handy bit of code.
My small suggestion is....
As an alternative to :
private string GetText(FlowDocument doc) {
....
}
you could use :
new TextRange(doc.ContentStart, doc.ContentEnd).Text
regards David
I found that TextRange performed very poorly when used in this way for large documents.
Sorry, I should have mentioned this in the article.
Thanks
Dan
Post a Comment