How to highlight a word or phrase with a color different from all the other selections in a RichTextBox text?

◇◆丶佛笑我妖孽 提交于 2021-02-05 07:56:27

问题


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 TextSearcher1 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!