问题
The main goal is to let an User identify a selection as the current, among all other selected words or phrases.
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
if (results.Count > 0)
{
richTextBox1.SelectionStart = results[(int)numericUpDown1.Value - 1];
richTextBox1.SelectionLength = textBox1.Text.Length;
richTextBox1.SelectionColor = Color.Red;
richTextBox1.ScrollToCaret();
}
}
In this case, in the screenshot, there are 3 results of the searched word: System
. All the results colored in yellow.
If I change the NumericUpDown value to 2, it will color in red the second System
word in the results and then when changing the NumericUpDown value to 3, it will color the last System result in red too.
My problem is that both 2 and 3 will be in colored in red: I want only the current selected word to be colored in red, all the other matches should use the default color.
回答1:
A few suggestions that can help in building a class object that can handle selections of text.
This class should contain most (or all) the logic needed to search for keywords inside a given text, maintain a list of the matches found, their position and length and allows basic navigation of this list of matches, plus other configurations that can extend the functionality without much effort.
Here, the TextSearcher
1 class constructor expects a RichTextBox control as one of the arguments, plus a Color used for the normal selection and a Color used to highlight the current match when the list of matches is navigated.
► The search functionality is handled by the Regex.Matches() method, which return a MatchCollection of Match objects, quite handy since each Match
contains the position (Index
property) of the match inside the text and its length (Length
property).
► The navigation functionality is instead provided by a BindingSource object. Its DataSource
property is set to the MatchCollection
returned by Regex.Matches()
and it's reset each time a new set of keywords is searched.
This class, when initialized, provides the MoveNext()
, MovePrevious()
, MoveLast()
and MoveFirst()
methods, plus a Position
property, which identifies the Index of current object in the collection - a Match
object, here.
► The CaseSensite
property is used to perform case sensitive or insensitive searches. Added to show that it can be simple to expand the basic functionality with more features that build on the existing (for example, a culture sensitive/insensitive search).
To initialize the TextSearcher class, pass the instance of a RichTextBox control and two Color values to its contructor:
TextSearcher matchFinder = null;
public SomeForm()
{
InitializeComponent();
matchFinder = new TextSearcher(richTextBox1, Color.Yellow, Color.Red);
}
In the example, you can see four Controls:
- The Previous (
btnPrevious
) and Next (btnNext
) Buttons, used to navigate the list of matches. - A NumericUpDown (
nudGotoMatch
), used to jump to a specific matched keywork. - A TextBox (
txtSearch
), used to enter one or more keywords, separated by a pipe (the pipe char is used specify alternate matches in the Regex, it could be replaced with something else in the UI).
Since the TextSearcher
class contains all the search and navigation logic, the code needed to activate the functionality of these controls in the front-end is minimal, just the standard UI requirements (such as setting e.SuppressKeyPress = true
when the Enter key is pressed in a TextBox control).
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();
}
This is how it works:
The TextSearcher
class:
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)
{
this.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);
this.m_bsMatches.Position = Math.Max(Math.Min(this.m_bsMatches.Count, position), 0);
SelectText(true);
}
public void NextMatch()
{
SelectText(false);
if (this.m_bsMatches != null && m_bsMatches.Position == this.m_bsMatches.Count - 1) {
this.m_bsMatches.MoveFirst();
}
else { this.m_bsMatches.MoveNext(); }
SelectText(true);
}
public void PreviousMatch()
{
SelectText(false);
if (this.m_bsMatches != null && this.m_bsMatches.Position > 0) {
this.m_bsMatches.MovePrevious();
}
else { this.m_bsMatches.MoveLast(); }
SelectText(true);
}
public int Search(string keywords)
{
if (CurrentKeywords.Equals(keywords)) return Matches.Count;
this.m_bsMatches?.Dispose();
CurrentKeywords = keywords;
var options = RegexOptions.Multiline |
(CaseSensitive ? RegexOptions.IgnoreCase : RegexOptions.None);
Matches = Regex.Matches(this.m_Rtb.Text, keywords, options);
if (Matches != null) {
this.m_Rtb.SelectAll();
this.m_Rtb.SelectionColor = this.m_Rtb.ForeColor;
this.m_Rtb.SelectionStart = 0;
this.m_bsMatches = new BindingSource(Matches, null);
SelectKeywords();
return Matches.Count;
}
return 0;
}
private void SelectKeywords()
{
foreach (Match m in Matches) {
SelectText(false);
NextMatch();
}
this.m_bsMatches.MoveFirst();
}
private void SelectText(bool current)
{
this.m_Rtb.Select(CurrentMatch.Index, CurrentMatch.Length);
this.m_Rtb.SelectionColor = current ? CurrentColor : SelectionColor;
}
}
1 - I know, great name! It took me a while to come up with it so, please, don't changed it :)
来源:https://stackoverflow.com/questions/62009599/how-to-highlight-a-word-or-phrase-with-a-color-different-from-all-the-other-sele