C# - WebフォームのRichTextBox編集アプローチ?

okwaves2024-01-25  8

私は C# WinForm を使用しており、C# スクリプトのように見せたい RichTextBox があります。

特定の単語を使用するときに色を付けたいという意味です。彼らが単語を変更して編集したときは、黒色に戻してほしいです。

私のアプローチは機能しますが、非常に面倒で、スクロール オプションが作成され、以下のコードを表示するために使用する必要がある場合にバグが発生します。 (入力中、リッチテキストボックスは止まることなく上下に飛び跳ねます)

private void ScriptRichTextBox_TextChanged(object sender, EventArgs e)
    {
        ScriptTextChange = ScriptRichTextBox.Text;
        ScriptColorChange();
    }

    private void ScriptColorChange()
    {
        int index = ScriptRichTextBox.SelectionStart;
        ScriptRichTextBox.Text = ScriptTextChange; //Only way I found to make the all current text black again, SelectAll() didn't work well.
        ScriptRichTextBox.SelectionStart = index;
        String[] coloredNames = {"Main", "ClickMouseDown", "ClickMouseUp", "PressKey", "StopMoving", "Delay", "GoRight", "GoLeft", "GoUp", "GoDown", "MousePosition", "LastHorizontalDirection", "LastVerticalDirections", "CurrentDirection", "Directions" };
        String[] coloredNames2 = { "cm.", "if", "else", "while", "switch", "case", "break", "return", "new" };
        String[] coloredNames3 = { "MyPosition", "MyHp", "MyMp", "OtherPeopleInMap", ".RIGHT", ".LEFT", ".UP", ".DOWN", ".STOP_MOVING" };
        foreach (String s in coloredNames)
            this.CheckKeyword(s, Color.LightSkyBlue, 0);
        foreach (String s in coloredNames2)
            this.CheckKeyword(s, Color.Blue, 0);
        foreach (String s in coloredNames3)
            this.CheckKeyword(s, Color.DarkGreen, 0);
    }

    private void CheckKeyword(string word, Color color, int startIndex)
    {
        if (this.ScriptRichTextBox.Text.Contains(word))
        {
            int index = 0;
            int selectStart = this.ScriptRichTextBox.SelectionStart;

            while ((index = this.ScriptRichTextBox.Text.IndexOf(word, (index + 1))) != -1)
            {
                this.ScriptRichTextBox.Select((index + startIndex), word.Length);
                this.ScriptRichTextBox.SelectionColor = color;
                this.ScriptRichTextBox.Select(selectStart, 0);
                this.ScriptRichTextBox.SelectionColor = Color.Black;
            }
        }
    }

stackoverflow.com/a/3282911/17034

– ハンス・パッサント

2020 年 9 月 3 日 11:43

このコンテキスト (TexChanged イベントで基になる RTF を変更する間) でコントロールのテキストを設定することはおそらく得策ではありません。現在の単語 (SelectionStart 位置にある単語) にのみ注目し、それがカテゴリのいずれかに属しているかどうかを確認し (つまり、今行っていることの逆です)、それに応じて SelectionColor を設定する必要があります。 Control.ForeColor (属していない場合)。

– ジミ

2020 年 9 月 3 日 11:51

コントロール部分の設定テキスト(ScriptColorChange()の最初の3行)を削除しました。しかし、スクロール オプションがあるときにテキストが変更されると、依然としてバグが発生します。例を次に示します: i.imgur.com/duOMiUo.mp4 @Jimi

– ナベ

2020 年 9 月 3 日 12:11



------------------------

より良いアプリを示すために、コードを少しリファクタリングしました。テキストに色を付けるためのローチ。 TextChanged イベントを発生させるたびに文字列配列をインスタンス化することも最適ではありません。

更新: アイデアは、入力時に単語のセットと一致する単語バッファを構築することです。

バッファは各キーを記録し、.IsLetterOrDigit の場合は StringBuilder バッファに追加します。バッファには、キー押下値が記録され、バックスペースを押しても記録された文字が削除されないなど、追加のバグがいくつかあります。

単語バッファの代わりに、RegEx を使用して予約語リスト内の単語と一致させます。 \b(word|word2|word3....)\b のような結果になるように予約語 RegEx を構築します。これは、BuildRegExPattern(..) メソッドのコードで行われます。

文字または数字以外のキーを押すと、バッファがチェックされます。d はコンテンツの場合で、コンテンツが単語と一致する場合は、ScriptRichTextBox.Text 内のカーソルの直前のテキストのみがチェックされ、変更されます。

予約語から .(ドット) を削除すると、一致基準が複雑になるだけです。構築されたパターン内の正規表現は単語と正確に一致するため、FARRIGHT や cms などを入力しても単語の色が部分的に変わることはありません。

おまけとして、Ctrl+V を押して貼り付けるプロセスについても説明しました。これは、WinForms では少し面倒で、おそらく頻繁に発生するためです。

古い質問もあります。これはスクロール動作をカバーしており、[System.Runtime.InteropServices.DllImport("user32.dll")] 属性を追加することで相互運用する方法を示していますが、属性なしでも実行できます。

すべてを防ぐにはスクロール ジャンプには、フォームの .DefWndProc(msg) メソッドを使用できます。この質問により、WM_SETREDRAW プロパティがわかりました。

設定できる他のプロパティのリストもあります。

完全な実装は次のとおりです。

public partial class Form1 : Form
{
    private readonly string[] _skyBlueStrings;
    private readonly string[] _blueStrings;
    private readonly string[] _greenStrings;

    //for pasting
    bool _IsCtrl;
    bool _IsV;

    //value to fix the colour not setting first character after return key pressed
    int _returnIdxFix = 0;

    //regex patterns to use
    string _LightBlueRegX = "";
    string _BlueRegX = "";
    string _GreenRegX = "";

    //match only words
    Regex _rgxAnyWords = new Regex(@"(\w+)");

    //colour setup
    Color _LightBlueColour = Color.LightSkyBlue;
    Color _BlueColour = Color.Blue;
    Color _GreenColour = Color.DarkGreen;
    Color _DefaultColour = Color.Black;


    public Form1()
    {
        InitializeComponent();

        _skyBlueStrings = new string[] { "Main", "ClickMouseDown", "ClickMouseUp", "PressKey", "StopMoving", "Delay", "GoRight", "GoLeft", "GoUp", "GoDown", "MousePosition", "LastHorizontalDirection", "LastVerticalDirections", "CurrentDirection", "Directions" };
        _blueStrings = new string[] { "cm", "if", "else", "while", "switch", "case", "break", "return", "new" };
        _greenStrings = new string[] { "MyPosition", "MyHp", "MyMp", "OtherPeopleInMap", "RIGHT", "LEFT", "UP", "DOWN", "STOP_MOVING" };

        _LightBlueRegX = BuildRegExPattern(_skyBlueStrings);
        _BlueRegX = BuildRegExPattern(_blueStrings);
        _GreenRegX = BuildRegExPattern(_greenStrings);
    }

    string BuildRegExPattern(string[] keyworkArray)
    {
        StringBuilder _regExPatern = new StringBuilder();
        _regExPatern.Append(@"\b(");//beginning of word
        _regExPatern.Append(string.Join("|", keyworkArray));//all reserve words
        _regExPatern.Append(@")\b");//end of word
        return _regExPatern.ToString();
    }

    private void ProcessAllText()
    {
        BeginRtbUpdate();
        FormatKeywords(_LightBlueRegX, _LightBlueColour);
        FormatKeywords(_BlueRegX, _BlueColour);
        FormatKeywords(_GreenRegX, _GreenColour);

        //internal function to process words and set their colours
        void FormatKeywords(string regExPattern, Color wordColour)
        {
            var matchStrings = Regex.Matches(ScriptRichTextBox.Text, regExPattern);
            foreach (Match match in matchStrings)
            {
                FormatKeyword(keyword: match.Value, wordIndex: match.Index, wordColour: wordColour);
            }
        }

        EndRtbUpdate();
        ScriptRichTextBox.Select(ScriptRichTextBox.Text.Length, 0);
        ScriptRichTextBox.Invalidate();
    }

    void ProcessWordAtIndex(string fullText, int cursorIdx)
    {
        MatchCollection anyWordMatches = _rgxAnyWords.Matches(fullText);
        if (anyWordMatches.Count == 0)
        { return; } // no words found

        var allWords = anyWordMatches.OfType<Match>().ToList();

        //get the word just before cursor
        var wordAtCursor = allWords.FirstOrDefault(w => (cursorIdx - _returnIdxFix) == (w.Index + w.Length));
        if (wordAtCursor is null || string.IsNullOrWhiteSpace(wordAtCursor.Value))
        { return; }//no word at cursor or the match was blank

        Color wordColour = CalculateWordColour(wordAtCursor.Value);
        FormatKeyword(wordAtCursor.Value, wordAtCursor.Index, wordColour);

    }

    private Color CalculateWordColour(string word)
    {
        if (_skyBlueStrings.Contains(word))
        { return _LightBlueColour; }
        if (_blueStrings.Contains(word))
        { return _BlueColour; }
        if (_greenStrings.Contains(word))
        { return _GreenColour; }
        return _DefaultColour;
    }

    private void FormatKeyword(string keyword, int wordIndex, Color wordColour)
    {
        ScriptRichTextBox.Select((wordIndex - _returnIdxFix), keyword.Length);
        ScriptRichTextBox.SelectionColor = wordColour;
        ScriptRichTextBox.Select(wordIndex + keyword.Length, 0);
        ScriptRichTextBox.SelectionColor = _DefaultColour;
    }

    #region RichTextBox BeginUpdate and EndUpdate Methods
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
            //wait until the rtb is visible, otherwise you get some weird behaviour.
            if (ScriptRichTextBox.Visible && ScriptRichTextBox.IsHandleCreated)
            {
                if (m.LParam == ScriptRichTextBox.Handle)
                {
                    rtBox_lParam = m.LParam;
                    rtBox_wParam = m.WParam;
                }
            }
        }

        IntPtr rtBox_wParam = IntPtr.Zero;
        IntPtr rtBox_lParam = IntPtr.Zero;
        const int WM_SETREDRAW = 0x0b;
        const int EM_HIDESELECTION = 0x43f;

        void BeginRtbUpdate()
        {
            Message msg_WM_SETREDRAW = Message.Create(ScriptRichTextBox.Handle, WM_SETREDRAW, (IntPtr)0, rtBox_lParam);
            this.DefWndProc(ref msg_WM_SETREDRAW);
        }

        public void EndRtbUpdate()
        {
            Message msg_WM_SETREDRAW = Message.Create(ScriptRichTextBox.Handle, WM_SETREDRAW, rtBox_wParam, rtBox_lParam);
            this.DefWndProc(ref msg_WM_SETREDRAW);
            //redraw the RichTextBox
            ScriptRichTextBox.Invalidate();
        }
        #endregion

    private void ScriptRichTextBox_TextChanged(object sender, EventArgs e)
    {
        //only run all text if it was pasted NOT ON EVERY TEXT CHANGE!
        if (_IsCtrl && _IsV)
        {
            _IsCtrl = false;
            ProcessAllText();
        }
    }

    protected void ScriptRichTextBox_KeyPress(object sender, KeyPressEventArgs e)
    {
        if (!char.IsLetterOrDigit(e.KeyChar))
        {
            //if the key was enter the cursor position is 1 position off                
            _returnIdxFix = (e.KeyChar == '\r') ? 1 : 0;
            ProcessWordAtIndex(ScriptRichTextBox.Text, ScriptRichTextBox.SelectionStart);
        }
    }

    private void ScriptRichTextBox_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
    {
        if (e.KeyCode == Keys.ControlKey)
        {
            _IsCtrl = true;
        }
        if (e.KeyCode == Keys.V)
        {
            _IsV = true;
        }
    }

    private void ScriptRichTextBox_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
    {
        if (e.KeyCode == Keys.ControlKey)
        {
            _IsCtrl = false;
        }
        if (e.KeyCode == Keys.V)
        {
            _IsV = false;
        }
    }
}

「コード」を貼り付けると次のようになります。キーワードを含む:

入力すると次のようになります。

3

このコードは、GIF に示されているように機能しません。書くときに青や緑の色を試してもうまくいきません。 4つのイベントをすべて適用しましたコード、なぜそれがあなたのコードのように機能しないのかわかりません

– ナベ

2020 年 9 月 5 日 11:38

どの色も機能しますか?単に色が重複しているか、コードのタイプミスである可能性があります。コードをそのまま、フォームと RTBox を含む新しいプロジェクトにコピーすると、機能するはずです。

– コビーC

2020 年 9 月 5 日 21:16

わかりません、私はそれをうまく機能させることができませんでしたが、あなたはコピー&ペーストの方法はうまく機能します。本当に感謝しています。コピペしたらどうなるか、考えもしませんでした。

– ナベ

2020 年 9 月 6 日 8:58



------------------------

実際にうまく動作するもの、または迷惑なバグがあるものが 2 日間見つからなかったにもかかわらず、OK。私はそれを機能させるために大苦戦した後、なんとか自分で解決策を見つけることができました。大きなアイデアは、人々がすべての RichTextBox 単語を一度に編集しようとするため、バグが発生するということです。現在の単語をチェックしても同じ結果しか得られないのに、なぜリッチ テキスト ボックスをすべて編集する必要があるのでしょうか。これが私がやったことです。配列文字列のいずれかが現在の単語に含まれているかどうかを確認し、それらすべてが大好きでした。

private void ScriptRichTextBox_TextChanged(object sender, EventArgs e)
    {
        FindStringsInCurrentWord();
    }

    private void FindStringsInCurrentWord()
    {
        RichTextBox script = ScriptRichTextBox;
        String finalWord, forwards, backwards;
        int saveLastSelectionStart = script.SelectionStart;
        int index = script.SelectionStart;

        String[] coloredNames = { "Main", "ClickMouseDown", "ClickMouseUp", "PressKey", "StopMoving", "Delay", "GoRight", "GoLeft", "GoUp", "GoDown", "MousePosition", "LastHorizontalDirection", "LastVerticalDirections", "CurrentDirection", "Directions" };
        String[] coloredNames2 = { "cm.", "if", "else", "while", "switch", "case", "break", "return", "new" };
        String[] coloredNames3 = { "MyPosition", "MyHp", "MyMp", "OtherPeopleInMap", ".RIGHT", ".LEFT", ".UP", ".DOWN", ".STOP_MOVING" };

        String[] arr2 = coloredNames.Union(coloredNames2).ToArray();
        Array arrAll = arr2.Union(coloredNames3).ToArray(); //Gets all arrays together
        Array[] wordsArray = { coloredNames, coloredNames2, coloredNames3 }; //All found strings in the word
        List<String> wordsFoundList = new List<String>();
        int foundChangedColor = 0;
        int wordsFound = 0;


        char current = (char)script.GetCharFromPosition(script.GetPositionFromCharIndex(index)); //Where the editor thingy is
        //Check forward text where he uses space and save text
        while (!System.Char.IsWhiteSpace(current) && index < script.Text.Length)
        {
            index++;
            current = (char)script.GetCharFromPosition(script.GetPositionFromCharIndex(index));
        }
        int lengthForward = index - saveLastSelectionStart;
        script.Select(script.SelectionStart, lengthForward);
        forwards = script.SelectedText;
        //Debug.WriteLine("Forwards: " + forwards);
        script.SelectionStart = saveLastSelectionStart;
        this.ScriptRichTextBox.Select(script.SelectionStart, 0);
        index = script.SelectionStart;
        current = (char)script.GetCharFromPosition(script.GetPositionFromCharIndex(index));
        int length = 0;
        //Check backwords where he uses space and save text
        while ((!System.Char.IsWhiteSpace(current) || length == 0) && index > 0 && index <= script.Text.Length)
        {
            index--;
            length++;
            current = (char)script.GetCharFromPosition(script.GetPositionFromCharIndex(index));
        }
        script.SelectionStart -= length;
        script.Select(script.SelectionStart + 1, length - 1);
        backwards = script.SelectedText;
        //Debug.WriteLine("Backwards: " + backwards);
        script.SelectionStart = saveLastSelectionStart;
        this.ScriptRichTextBox.Select(saveLastSelectionStart, 0);
        this.ScriptRichTextBox.SelectionColor = Color.Black;
        finalWord = backwards + forwards; //Our all word!
        //Debug.WriteLine("WORD:" + finalWord);
  
        //Setting all of the word black, after it coloring the right places
        script.Select(index + 1, length + lengthForward);
        script.SelectionColor = Color.Black;
        foreach (string word in arrAll)
        {
            if (finalWord.IndexOf(word) != -1)
            {
                wordsFound++;
                wordsFoundList.Add(word);
                script.Select(index + 1 + finalWord.IndexOf(word), word.Length);
                if (coloredNames.Any(word.Contains))
                {
                    script.SelectionColor = Color.LightSkyBlue;
                    foundChangedColor++;
                }
                else if (coloredNames2.Any(word.Contains))
                {
                    script.SelectionColor = Color.Blue;
                    foundChangedColor++;
                }
                else if (coloredNames3.Any(word.Contains))
                {
                    script.SelectionColor = Color.DarkGreen;
                    foundChangedColor++;
                }
                //Debug.WriteLine("Word to edit: " + script.SelectedText);
                this.ScriptRichTextBox.Select(saveLastSelectionStart, 0);
                this.ScriptRichTextBox.SelectionColor = Color.Black;
            }
        }

        //No strings found, color it black
        if (wordsFound == 0)
        {
            script.Select(index + 1, length + lengthForward);
            script.SelectionColor = Color.Black;
            //Debug.WriteLine("WORD??: " + script.SelectedText);
            this.ScriptRichTextBox.Select(saveLastSelectionStart, 0);
            this.ScriptRichTextBox.SelectionColor = Color.Black;
        }
    }

1

最後の単語だけを見るというアイデアは非常に優れており、単語バッファを構築するよりもはるかに理にかなっているため、将来の参考のために回答を更新します。ただし、コーディングの観点 (コード レビュー) だけから見ると、かなりの量のリファクタリングが必要です。テキストが変更されるたびに文字列配列をインスタンス化するのは優れた方法ではなく、メソッドが非常に長いため、最後まで従うのが困難になります。

– コビーC

2020 年 9 月 6 日 19:29

総合生活情報サイト - OKWAVES
総合生活情報サイト - OKWAVES
生活総合情報サイトokwaves(オールアバウト)。その道のプロ(専門家)が、日常生活をより豊かに快適にするノウハウから業界の最新動向、読み物コラムまで、多彩なコンテンツを発信。