一些有助于构建可以处理文本选择的类对象的建议。
此类应包含在给定文本中搜索关键字所需的大部分(或全部)逻辑,维护找到的匹配项列表、它们的位置和长度,并允许此列表的基本导航匹配项,以及其他可以轻松扩展功能的配置。
这里,TextSearcher1 类构造函数需要一个 RichTextBox 控件作为参数之一,加上一个用于正常选择的颜色和一个用于在列表中突出显示当前匹配的颜色匹配是导航。
▶ 搜索功能由Regex.Matches() 方法处理,该方法返回Match 对象的MatchCollection,非常方便,因为每个Match 都包含文本内匹配的位置(Index 属性)及其长度(Length 属性)。
▶ 导航功能由BindingSource 对象提供。它的DataSource 属性设置为Regex.Matches() 返回的MatchCollection,并且每次搜索一组新关键字时都会重置。
该类在初始化时提供 MoveNext()、MovePrevious()、MoveLast() 和 MoveFirst() > 方法,加上一个 Position 属性,该属性标识集合中当前对象的索引 - 此处为 Match 对象。
▶ CaseSensite 属性用于执行区分大小写或不区分大小写的搜索。添加以表明使用更多基于现有功能的功能(例如,文化敏感/不敏感搜索)可以轻松扩展基本功能。
要初始化 TextSearcher 类,请将 RichTextBox 控件的实例和两个 Color 值传递给它的构造函数:
TextSearcher matchFinder = null;
public SomeForm()
{
InitializeComponent();
matchFinder = new TextSearcher(richTextBox1, Color.Yellow, Color.Red);
}
在示例中,您可以看到四个控件:
- 上一个 (
btnPrevious) 和下一个 (btnNext) 按钮,用于导航匹配列表。
- NumericUpDown (
nudGotoMatch),用于跳转到特定的匹配键。
- 一个文本框 (
txtSearch),用于输入一个或多个关键字,用管道分隔(管道字符用于指定 Regex 中的备用匹配项,它可以在 UI 中替换为其他内容)。李>
由于TextSearcher类包含所有的搜索和导航逻辑,在前端激活这些控件的功能所需的代码是最少的,只是标准的UI要求(例如在回车时设置e.SuppressKeyPress = true在 TextBox 控件中按下键)。
private void txtSearch_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter) {
string keywords = (sender as Control).Text;
e.SuppressKeyPress = true;
if (!matchFinder.CurrentKeywords.Equals(keyword)) {
nudGotoMatch.Maximum = matchFinder.Search(keywords);
}
}
}
private void nudGotoMatch_ValueChanged(object sender, EventArgs e)
{
if (matchFinder.Matches.Count > 0) {
matchFinder.GotoMatch((int)nudGotoMatch.Value - 1);
}
}
private void btnPrevious_Click(object sender, EventArgs e)
{
matchFinder.PreviousMatch();
}
private void btnNext_Click(object sender, EventArgs e)
{
matchFinder.NextMatch();
}
它是这样工作的:
TextSearcher 类:
using System.Collections.Generic;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;
private class TextSearcher
{
private BindingSource m_bsMatches = null;
private RichTextBox m_Rtb = null;
public TextSearcher(RichTextBox rtb) : this(rtb, Color.Yellow, Color.Red) { }
public TextSearcher(RichTextBox rtb, Color selectionColor, Color currentColor)
{
m_Rtb = rtb;
SelectionColor = selectionColor;
CurrentColor = currentColor;
}
public string CurrentKeywords { get; private set; } = string.Empty;
public bool CaseSensitive { get; set; } = true;
public int CurrentIndex => m_bsMatches.Position;
public Match CurrentMatch => (Match)m_bsMatches.Current;
public MatchCollection Matches { get; private set; }
public Color SelectionColor { get; set; }
public Color CurrentColor { get; set; }
public void GotoMatch(int position)
{
SelectText(false);
m_bsMatches.Position = Math.Max(Math.Min(m_bsMatches.Count, position), 0);
SelectText(true);
}
public void NextMatch()
{
SelectText(false);
if (m_bsMatches != null && m_bsMatches.Position == m_bsMatches.Count - 1) {
m_bsMatches.MoveFirst();
}
else { m_bsMatches.MoveNext(); }
SelectText(true);
}
public void PreviousMatch()
{
SelectText(false);
if (m_bsMatches != null && m_bsMatches.Position > 0) {
m_bsMatches.MovePrevious();
}
else { m_bsMatches.MoveLast(); }
SelectText(true);
}
public int Search(string keywords)
{
if (CurrentKeywords.Equals(keywords)) return Matches.Count;
m_bsMatches?.Dispose();
CurrentKeywords = keywords;
var options = RegexOptions.Multiline |
(CaseSensitive ? RegexOptions.IgnoreCase : RegexOptions.None);
Matches = Regex.Matches(m_Rtb.Text, keywords, options);
if (Matches != null) {
m_Rtb.SelectAll();
m_Rtb.SelectionColor = m_Rtb.ForeColor;
m_Rtb.SelectionStart = 0;
m_bsMatches = new BindingSource(Matches, null);
SelectKeywords();
return Matches.Count;
}
return 0;
}
private void SelectKeywords()
{
foreach (Match m in Matches) {
SelectText(false);
NextMatch();
}
m_bsMatches.MoveFirst();
}
private void SelectText(bool current)
{
m_Rtb.Select(CurrentMatch.Index, CurrentMatch.Length);
m_Rtb.SelectionColor = current ? CurrentColor : SelectionColor;
}
}
1 - 我知道,好名字!我花了一段时间才想出它,所以请不要改变它:)