To understand what is algorithm can be used to analyze a content of the FlowDocument
the best way to read the Flexible Content Display With Flow Documents post or, for example, read the chapter “Documents” in book MacDonald M. - Pro WPF 4.5 in C#. Windows Presentation Foundation in .NET 4.5 (The Experts Voice in .NET), 2012.
So, the FlowDocument
contains BlockCollection Blocks
property. This is the top-level blocks of the whole the FlowDocument
content.
Code below uses this property to parse and analyze all elements in the FlowDocument
recursively, including to search for text fragments with a specified background color.
For the test purpose the application window contains the Color ComboBox
which allows to choose an some color and paint selected fragments of text using the Set Color button. The Search color text button starts scanning the document, finds the text colored with the specified color and uses the created TextRange
list to repainting the text in pink.
MainWindow.xaml
<Window ...
Title="MainWindow" Height="350" Width="500" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<RichTextBox Name="rtb" BorderBrush="LightGreen"
Padding="5" Margin="10" VerticalScrollBarVisibility="Auto">
<FlowDocument>
<Paragraph>
<Run>?</Run>
</Paragraph>
</FlowDocument>
</RichTextBox>
<Grid Grid.Row="1" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto" MinWidth="60"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Content="Color: " />
<ComboBox x:Name="ComboColor" Grid.Column="1" Width="150" Margin="3" SelectedValuePath="Name" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Fill="{Binding Name}" Width="16" Height="16" Margin="0,0,5,0" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Set color" Grid.Row="1" Grid.Column="1" Padding="3" Margin="3" Click="SetColor_Click"/>
<Button Content="Search colored text" Grid.Row="2" Grid.Column="1" Padding="3" Margin="3" Click="Search_Click"/>
</Grid>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespace WpfApp17
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ComboColor.ItemsSource = typeof(Colors).GetProperties();
ComboColor.SelectedValue = "Brown";
}
private void Search_Click(object sender, RoutedEventArgs e)
{
Parsing(rtb);
}
public void Parsing(RichTextBox rtb)
{
// Get selected color
var c = System.Drawing.Color.FromName(ComboColor.SelectedValue.ToString());
var parser = new RtfDocumentParser();
// Initialization with selected color
parser.Init(Color.FromArgb(c.A, c.R, c.G, c.B));
// Processing
parser.Analyze(rtb);
// Color found TextRanges to pink
foreach (var tr in parser.TextRanges)
{
tr.ApplyPropertyValue(TextElement.BackgroundProperty, new SolidColorBrush(Colors.Pink));
}
}
private void SetColor_Click(object sender, RoutedEventArgs e)
{
var selection = rtb.Selection;
if (!selection.IsEmpty)
{
var c = System.Drawing.Color.FromName(ComboColor.SelectedValue.ToString());
selection.ApplyPropertyValue(TextElement.BackgroundProperty, new SolidColorBrush(Color.FromArgb(c.A, c.R, c.G, c.B)));
}
}
}
}
RtfDocumentParser.cs
using System;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespace WpfApp17
{
public class RtfDocumentParser
{
#region Public properties
public Color SearchColor { get; private set; }
public IList<TextRange> TextRanges { get; private set; }
#endregion
#region Private properties
private TextPointer Start { get; set; }
private TextPointer End { get; set; }
#endregion
#region ctor
public RtfDocumentParser()
{
Init(Colors.Yellow);
}
#endregion
#region Public methods
public void Init(Color color)
{
SearchColor = color;
TextRanges = new List<TextRange>();
}
public void Analyze(RichTextBox rtb)
{
ParseBlockCollection(rtb.Document.Blocks);
CloseRange();
}
#endregion
#region Private methods
private void ParseBlockCollection(BlockCollection blocks)
{
foreach (var block in blocks)
{
CloseRange();
if (block is Paragraph para) { ParseInlineCollection(para.Inlines); }
else if (block is List list)
{
foreach (var litem in list.ListItems)
{
CloseRange();
TextRange range = new TextRange(litem.ElementStart, litem.ElementEnd);
ParseBlockCollection(litem.Blocks);
}
}
else if (block is Table table)
{
foreach (TableRowGroup rowGroup in table.RowGroups)
{
foreach (TableRow row in rowGroup.Rows)
{
foreach (var cell in row.Cells)
{
ParseBlockCollection(cell.Blocks);
}
}
}
}
else if (block is BlockUIContainer blockui) { /* blockui.Child */ }
else if (block is Section section) { ParseBlockCollection(section.Blocks); }
else { throw new NotImplementedException(); }
}
}
public void ParseInlineCollection(InlineCollection inlines)
{
foreach (var inline in inlines)
{
if (inline is Run r)
{
Analyze(r);
}
else if (inline is InlineUIContainer || inline is LineBreak lbreak)
{
CloseRange();
}
else if (inline is Span span)
{
ParseInlineCollection(span.Inlines);
}
}
}
private void Analyze(Run run)
{
if (run.Background is SolidColorBrush rBrush)
{
CheckPositions(rBrush.Color, run.ElementStart, run.ElementEnd);
}
else if (run.Parent is Span span && span.Background is SolidColorBrush sBrush)
{
CheckPositions(sBrush.Color, run.ElementStart, run.ElementEnd);
}
else if (End != null)
{
CloseRange();
}
}
private void CheckPositions(Color color, TextPointer start, TextPointer end)
{
if (color == SearchColor)
{
if (Start == null)
{
Start = start;
}
else if (!IsMatch(start, End))
{
TextRanges.Add(new TextRange(Start, End));
Start = start;
}
End = end;
}
else if (End != null)
{
CloseRange();
}
}
private bool IsMatch(TextPointer start, TextPointer position)
{
for (; position != null; position = position.GetNextContextPosition(LogicalDirection.Forward))
{
//var context = position.GetPointerContext(LogicalDirection.Forward);
if (start.CompareTo(position) == 0)
return true; // Match
}
return false;
}
private void CloseRange()
{
if (End is TextPointer)
{
TextRanges.Add(new TextRange(Start, End));
}
Start = End = null;
}
#endregion
}
}
TextRange
containing the "lly highlighted te" fragment? – Jackdaw