使用 RichTextBox 中的索引突出显示阿拉伯语单词?
Highlighting an arabic word using its index in a RichTextBox?
我写了一个小的 WPF 程序,它应该突出显示我在 RichTextBox
.
中输入的任何阿拉伯语句子的第 X 个单词
例如,我输入这个文本并指定单词 2(索引:1):
这是我应该看到的:
这是我看到的:
我的代码
XAML:
<RichTextBox x:Name="ArabicRTB" FlowDirection="RightToLeft">
<FlowDocument>
<Paragraph>
<Run x:Name="ArabicRTB_Run" Text="كتبه البحرين هنا"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
C#:
private (int, int) WordIndexToCharIndex(int index)
{
int charIndex = 0;
int wordIndex = 0;
foreach (string word in ArabicRTB_Run.Text.Split(' '))
{
if (wordIndex == index)
{
return (charIndex, word.Length);
}
wordIndex++;
charIndex += word.Length + 2;
}
throw new IndexOutOfRangeException();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Here I specify the index of the word I need to highlight
var wordPos = WordIndexToCharIndex(1);
TextPointer pointer = ArabicRTB.Document.ContentStart;
TextPointer start = pointer.GetPositionAtOffset(wordPos.Item1);
TextPointer end = start.GetPositionAtOffset(wordPos.Item2);
var selection = new TextRange(start, end);
selection.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Goldenrod);
}
下面的代码展示了如何根据上下文相关的方法计算单词位置。
MainWindow.xaml
:
<Window ...>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<RichTextBox Name="rtb" BorderBrush="{x:Null}" Padding="5" Margin="10" FlowDirection="RightToLeft" VerticalScrollBarVisibility="Auto" FontSize="18">
<FlowDocument>
<Paragraph>
<Run Text="كت" Background="Aqua"/><Run Text="به" Background="Beige"/>
<Run Text="البحرين" Background="ForestGreen" />
<Run Text="هنا"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
<Button Grid.Row="1" Click="Button_SearchAsync" Margin="2" Padding="3">Press to test</Button>
</Grid>
</Window>
MainWindow.xaml.cs
的一部分:
private async void Button_SearchAsync(object sender, RoutedEventArgs e)
{
var index = 1;
await FindWordAsync(rtb, index);
rtb.Focus();
}
public async Task FindWordAsync(RichTextBox rtb, int index)
{
await Task<object>.Factory.StartNew(() =>
{
this.Dispatcher.Invoke(() =>
{
var range = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd).CalculateTextRange(index);
if (range is TextRange)
{
// If it found color in red
this.Dispatcher.Invoke(() => { range.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Red); });
}
});
return Task.FromResult<object>(null);
});
}
下面CalculateTextRange()
的方法实际上是通过index
计算请求的单词。由于包括对表格和列表的支持,它看起来有点复杂。
public static class TextRangeExt
{
public static TextRange CalculateTextRange(this TextRange range, int index)
{
string pattern = @"\b\w+\b";
int correction = 0;
TextPointer start = range.Start;
foreach (Match match in Regex.Matches(range.Text, pattern))
{
System.Diagnostics.Debug.WriteLine("match.Index= " + match.Index + ", match.Length= " + match.Length + " |" + match.Value + "|");
if (CalculateTextRange(start, match.Index - correction, match.Length) is TextRange tr)
{
correction = match.Index + match.Length;
start = tr.End;
if (index-- <= 0) return tr;
}
}
return null;
}
// Return calculated a `TextRange` of the string started from `iStart` index and having `length` size or `null`.
private static TextRange CalculateTextRange(TextPointer startSearch, int iStart, int length)
{
return (startSearch.GetTextPositionAtOffset(iStart) is TextPointer start)
? new TextRange(start, start.GetTextPositionAtOffset(length))
: null;
}
private static TextPointer GetTextPositionAtOffset(this TextPointer position, int offset)
{
for (TextPointer current = position; current != null; current = position.GetNextContextPosition(LogicalDirection.Forward))
{
position = current;
var adjacent = position.GetAdjacentElement(LogicalDirection.Forward);
var navigator = position.GetPointerContext(LogicalDirection.Forward);
switch (navigator)
{
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--;
}
else if (adjacent is ListItem lsItem)
{
var index = new TextRange(lsItem.ElementStart, lsItem.ElementEnd).Text.IndexOf('\t');
if (index >= 0) offset -= index + 1;
}
break;
case TextPointerContext.ElementEnd:
if (adjacent is Paragraph para)
{
var correction = 0;
if (para.Parent is TableCell tcell)
{
var bCount = tcell.Blocks.Count;
var cellText = new TextRange(tcell.Blocks.FirstBlock.ContentStart, tcell.Blocks.LastBlock.ContentEnd).Text;
if ((bCount == 1 && cellText.EndsWith(Environment.NewLine)) || bCount > 1)
{
correction = 2;
}
else if (tcell.Parent is TableRow trow)
{
var cells = trow.Cells.Count;
correction = (cells <= 0 || trow.Cells.IndexOf(tcell) != cells - 1) ? 1 : 2;
}
}
else
{
correction = 2;
}
offset -= correction;
}
break;
}
}
return position;
}
}
我写了一个小的 WPF 程序,它应该突出显示我在 RichTextBox
.
例如,我输入这个文本并指定单词 2(索引:1):
这是我应该看到的:
这是我看到的:
我的代码
XAML:
<RichTextBox x:Name="ArabicRTB" FlowDirection="RightToLeft">
<FlowDocument>
<Paragraph>
<Run x:Name="ArabicRTB_Run" Text="كتبه البحرين هنا"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
C#:
private (int, int) WordIndexToCharIndex(int index)
{
int charIndex = 0;
int wordIndex = 0;
foreach (string word in ArabicRTB_Run.Text.Split(' '))
{
if (wordIndex == index)
{
return (charIndex, word.Length);
}
wordIndex++;
charIndex += word.Length + 2;
}
throw new IndexOutOfRangeException();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Here I specify the index of the word I need to highlight
var wordPos = WordIndexToCharIndex(1);
TextPointer pointer = ArabicRTB.Document.ContentStart;
TextPointer start = pointer.GetPositionAtOffset(wordPos.Item1);
TextPointer end = start.GetPositionAtOffset(wordPos.Item2);
var selection = new TextRange(start, end);
selection.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Goldenrod);
}
下面的代码展示了如何根据上下文相关的方法计算单词位置。
MainWindow.xaml
:
<Window ...>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<RichTextBox Name="rtb" BorderBrush="{x:Null}" Padding="5" Margin="10" FlowDirection="RightToLeft" VerticalScrollBarVisibility="Auto" FontSize="18">
<FlowDocument>
<Paragraph>
<Run Text="كت" Background="Aqua"/><Run Text="به" Background="Beige"/>
<Run Text="البحرين" Background="ForestGreen" />
<Run Text="هنا"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
<Button Grid.Row="1" Click="Button_SearchAsync" Margin="2" Padding="3">Press to test</Button>
</Grid>
</Window>
MainWindow.xaml.cs
的一部分:
private async void Button_SearchAsync(object sender, RoutedEventArgs e)
{
var index = 1;
await FindWordAsync(rtb, index);
rtb.Focus();
}
public async Task FindWordAsync(RichTextBox rtb, int index)
{
await Task<object>.Factory.StartNew(() =>
{
this.Dispatcher.Invoke(() =>
{
var range = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd).CalculateTextRange(index);
if (range is TextRange)
{
// If it found color in red
this.Dispatcher.Invoke(() => { range.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Red); });
}
});
return Task.FromResult<object>(null);
});
}
下面CalculateTextRange()
的方法实际上是通过index
计算请求的单词。由于包括对表格和列表的支持,它看起来有点复杂。
public static class TextRangeExt
{
public static TextRange CalculateTextRange(this TextRange range, int index)
{
string pattern = @"\b\w+\b";
int correction = 0;
TextPointer start = range.Start;
foreach (Match match in Regex.Matches(range.Text, pattern))
{
System.Diagnostics.Debug.WriteLine("match.Index= " + match.Index + ", match.Length= " + match.Length + " |" + match.Value + "|");
if (CalculateTextRange(start, match.Index - correction, match.Length) is TextRange tr)
{
correction = match.Index + match.Length;
start = tr.End;
if (index-- <= 0) return tr;
}
}
return null;
}
// Return calculated a `TextRange` of the string started from `iStart` index and having `length` size or `null`.
private static TextRange CalculateTextRange(TextPointer startSearch, int iStart, int length)
{
return (startSearch.GetTextPositionAtOffset(iStart) is TextPointer start)
? new TextRange(start, start.GetTextPositionAtOffset(length))
: null;
}
private static TextPointer GetTextPositionAtOffset(this TextPointer position, int offset)
{
for (TextPointer current = position; current != null; current = position.GetNextContextPosition(LogicalDirection.Forward))
{
position = current;
var adjacent = position.GetAdjacentElement(LogicalDirection.Forward);
var navigator = position.GetPointerContext(LogicalDirection.Forward);
switch (navigator)
{
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--;
}
else if (adjacent is ListItem lsItem)
{
var index = new TextRange(lsItem.ElementStart, lsItem.ElementEnd).Text.IndexOf('\t');
if (index >= 0) offset -= index + 1;
}
break;
case TextPointerContext.ElementEnd:
if (adjacent is Paragraph para)
{
var correction = 0;
if (para.Parent is TableCell tcell)
{
var bCount = tcell.Blocks.Count;
var cellText = new TextRange(tcell.Blocks.FirstBlock.ContentStart, tcell.Blocks.LastBlock.ContentEnd).Text;
if ((bCount == 1 && cellText.EndsWith(Environment.NewLine)) || bCount > 1)
{
correction = 2;
}
else if (tcell.Parent is TableRow trow)
{
var cells = trow.Cells.Count;
correction = (cells <= 0 || trow.Cells.IndexOf(tcell) != cells - 1) ? 1 : 2;
}
}
else
{
correction = 2;
}
offset -= correction;
}
break;
}
}
return position;
}
}