I am trying to create a syntax colorizer in WPF C#. To obtain that, I need to get location of every word and color it depending of matching pattern. RichTextBox
has quite complicated system of selecting text, so I guessed that it would be easier to check position of given word in pure string and connect it to position in text. Well, nope. Depending of how many I already changed the color of text, it behaves differently, sometimes even matching wrong number of letters.
Example:
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
namespace Example
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var message = "Hello World and everyone here!";
richTextBox.Document.Blocks.Clear();
richTextBox.Document.Blocks.Add(new Paragraph(new Run(message)));
var position = richTextBox.Document.ContentStart;
var text = position.GetTextInRun(LogicalDirection.Forward);
ShowPos(position,2,5); // shows message "Hello"
ShowPos(position,8,5); // shows message "Wor" if upper line is not
// commented - otherwise, shows "World"
}
private void ShowPos(TextPointer position, int from, int length)
{
var posA = position.GetPositionAtOffset(from);
var posB = posA.GetPositionAtOffset(length);
var range = new TextRange(posA, posB);
var textA = range.Text;
//if removed, everything works fine
range.ApplyPropertyValue(ForegroundProperty, Brushes.DarkRed);
MessageBox.Show(textA);
}
}
}
It's pretty frustrating. What should i do to match and colorize every single word?
This is a very old post, but maybe someone will find this solution useful.
Unfortunately, searching for text and determining its position in the RichTextBox
is a little complicated process. Since the text can contain different formatting elements like background/foreground color, different fonts, images and so on, the context must be used to determine position of required text.
One of the possible solution is shown below.
MainWindow.xaml
<Window ...
Title="MainWindow" Height="250" Width="400"
KeyDown="Window_KeyDown">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<RichTextBox Grid.Row="0" x:Name="rtb" AllowDrop="True"
VerticalScrollBarVisibility="Auto" Padding="2" FontSize="18"
HorizontalAlignment="Left" >
<FlowDocument>
<Paragraph>
<Run FontSize="10" Text="In this example, you will learn to print Hello World." />
<Run Text="In this example, you will learn to print Hello World." />
<Run FontSize="20" Text="In this example, you will learn to print Hello World." />
</Paragraph>
</FlowDocument>
</RichTextBox>
<Button Grid.Row="1" Click="Button_Click">Color Words</Button>
</Grid>
</Window>
MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
var pairs = new Dictionary<string, SolidColorBrush>(StringComparer.InvariantCultureIgnoreCase)
{
{ "hello", Brushes.Red },
{ "world", Brushes.Blue }
};
// Define the range to be used to analyze for the specified words
var textRange = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd);
// Build list of Word/TextRange pairs
var list = CalculateTextRange(textRange, pairs.Select(d => d.Key).ToList());
// Color words by using calculated `TextRange`.
for (int i = 0; i < list.Count; i++)
{
list[i].Value.ApplyPropertyValue(TextElement.ForegroundProperty, pairs[list[i].Key]);
}
rtb.Focus();
}
public static IList<KeyValuePair<string, TextRange>> CalculateTextRange(TextRange range, IList<string> words)
{
// Building the regular expression pattern
var pattern = new StringBuilder(@"\b");
for (int i = 0; i < words.Count; i++)
{
if (i > 0) pattern.Append('|');
pattern.Append(words[i]);
}
pattern.Append(@"\b");
Regex regExp = new Regex(pattern.ToString(), RegexOptions.IgnoreCase);
TextRange search = range;
int correction = 0;
var result = new List<KeyValuePair<string, TextRange>>();
// Enumerate all found mathes and creating list of Word/TextRange pairs
foreach (Match match in regExp.Matches(range.Text))
{
if (CalculateTextRange(search, match.Index - correction, match.Length) is TextRange tr)
{
result.Add(new KeyValuePair<string, TextRange>(match.Value, tr));
correction = match.Index + match.Length;
search = new TextRange(tr.End, search.End);
}
}
return result;
}
// Calculates a `TextRange` of the string started from `iStart` index
// and having `length` size or `null`.
public static TextRange CalculateTextRange(TextRange search, int iStart, int length)
{
return (GetTextPositionAtOffset(search.Start, iStart) is TextPointer start)
? new TextRange(start, GetTextPositionAtOffset(start, length))
: null;
}
// Calculate `TextPointer` from defined text position by specified offset.
public static TextPointer GetTextPositionAtOffset(TextPointer position, int offset)
{
for (TextPointer current = position; current != null; current = position.GetNextContextPosition(LogicalDirection.Forward))
{
position = current;
var adjacent = position.GetAdjacentElement(LogicalDirection.Forward);
var context = position.GetPointerContext(LogicalDirection.Forward);
switch (context)
{
case TextPointerContext.Text:
int count = position.GetTextRunLength(LogicalDirection.Forward);
if (offset <= count)
{
return position.GetPositionAtOffset(offset);
}
offset -= count;
break;
case TextPointerContext.ElementStart:
if (adjacent is InlineUIContainer)
offset--;
break;
case TextPointerContext.ElementEnd:
if (adjacent is Paragraph)
offset -= 2;
break;
}
}
return position;
}
The code above contains the Button_Click()
handler that performs the following steps when user is clicked on the Color Words button.
RichTextBox
and color for each word in the list (actually a brush in this case):var pairs = new Dictionary<string, SolidColorBrush>(StringComparer.InvariantCultureIgnoreCase)
{
{ "hello", Brushes.Red },
{ "world", Brushes.Blue }
};
RichBoxText
document is used:var textRange = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd);
TextRange
for all words specified in the list and found in the document:// Build list of Word/TextRange pairs
var list = CalculateTextRange(textRange, pairs.Select(d => d.Key).ToList());
The picture is added to check that the position is calculated correctly even in this case.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.