Как выделить слово или фразу цветом, отличным от всех других выделений в тексте RichTextBox? - PullRequest
1 голос
/ 25 мая 2020

Основная цель - позволить пользователю идентифицировать выделение как текущее среди всех других выбранных слов или фраз.

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();
    }
}

В этом случае на скриншоте есть 3 результата поиска слова: System. Все результаты окрашены в желтый цвет.

Если я изменю значение NumericUpDown на 2, оно будет окрашивать в красный цвет второе System слово в результатах, а затем при изменении значения NumericUpDown на 3 он также покрасит в красный цвет последний системный результат.

Моя проблема в том, что и 2, и 3 будут окрашены в красный цвет: я хочу, чтобы только текущее выбранное слово было окрашено в красный цвет, все остальные совпадения должны использовать цвет по умолчанию.

In yellow

2 is in red

2 and 3 in red but only 3 should be in red 2 should be back to be in yellow

1 Ответ

2 голосов
/ 26 мая 2020

Несколько предложений, которые могут помочь в создании объекта класса, который может обрабатывать выделение текста.

Этот класс должен содержать большую часть (или все) logi c, необходимых для поиска ключевых слов внутри заданного текста, поддерживать список найденных совпадений, их положение и длину, а также позволяет c навигация по этому списку совпадений, а также другие конфигурации, которые могут расширить функциональность без особых усилий.

Здесь конструктор класса TextSearcher 1 ожидает в качестве одного из аргументов элемент управления RichTextBox, плюс Color, используемый для обычного выбора, и Color, используемый для выделения текущего совпадения, когда список совпадений: перемещаемый .

► Функциональность поиска обрабатывается методом Regex.Matches () , который возвращает MatchCollection из Сопоставление объектов, что весьма удобно, поскольку каждый Match содержит позицию (свойство Index) совпадения внутри текста и его длину (свойство Length).

► Функциональность навигации вместо этого обеспечивается объектом BindingSource . Его свойство DataSource имеет значение MatchCollection, возвращаемое Regex.Matches(), и оно сбрасывается каждый раз при поиске нового набора ключевых слов.
Этот класс при инициализации предоставляет MoveNext() , MovePrevious(), MoveLast() и MoveFirst(), а также свойство Position, которое определяет индекс текущий объект в коллекции - здесь объект Match.

► Свойство CaseSensite используется для поиска с учетом регистра или без учета регистра. Добавлено, чтобы показать, что можно легко расширить базовую c функциональность дополнительными функциями, которые основаны на существующих (например, поиск с учетом языка и региональных параметров).

Чтобы инициализировать класс TextSearcher, передайте экземпляр элемента управления RichTextBox и два значения Color его конструктору:

TextSearcher matchFinder = null;

public SomeForm()
{
    InitializeComponent();
    matchFinder = new TextSearcher(richTextBox1, Color.Yellow, Color.Red);
}

В этом примере вы можете увидеть четыре элемента управления:

  • Кнопки «Назад» (btnPrevious) и «Далее» (btnNext), используемые для навигации по списку совпадений.
  • NumericUpDown (nudGotoMatch), используется для перехода к заданной c согласованной клавише.
  • Текстовое поле (txtSearch), используемое для ввода одного или нескольких ключевых слов, разделенных вертикальной чертой (символ вертикальной черты используется для указания альтернативных совпадений в регулярном выражении, его можно заменить чем-то другим в пользовательском интерфейсе) .

Поскольку класс TextSearcher содержит все логики поиска и навигации c, код, необходимый для активации функциональности этих элементов управления во внешнем интерфейсе, минимален, только стандартные требования пользовательского интерфейса ( например, установка e.SuppressKeyPress = true при нажатии клавиши Enter в элементе управления 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();
}

Вот как это работает:

RichTextBox Search Utility

Класс 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)
    {
        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 - Я знаю, великое имя! Мне потребовалось время, чтобы это придумать, поэтому, пожалуйста, не меняйте его :)

...