首页 > 解决方案 > WebForms RichTextBox 编辑方法?

问题描述

我正在使用 C# WinForm,并且我有一个 RichTextBox,我试图让它看起来像一个 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;
            }
        }
    }

标签: c#winformsrichtextboxrichtextediting

解决方案


我对您的代码进行了一些重构,希望能展示一种更好的文本着色方法。每次触发TextChanged事件时实例化字符串数组也不是最佳选择。

更新这个想法是建立一个单词缓冲区,在键入时将与您的一组单词匹配。

缓冲区记录每个键,如果它.IsLetterOrDigit把它添加到StringBuilder缓冲区。 缓冲区有一些额外的错误,记录按键值,如果您按退格键等,则不会删除记录的字符。

代替单词 buffer,使用RegEx匹配保留单词列表中的任何单词。建立保留词RegEx,这样你最终会得到类似\b(word|word2|word3....)\bThis is done in the code in the BuildRegExPattern(..)method.

一旦您按下字母或数字以外的任何键,就会检查缓冲区的内容,如果内容与单词匹配,ScriptRichTextBox.Text则仅检查和更改光标之前的文本。

从保留词中删除 .(点),因为这只会使匹配标准复杂化。构建模式中的正则表达式将与单词完全匹配,因此如果您键入类似的内容FARRIGHTcms单词不会部分改变颜色。

另外,我还介绍了粘贴过程的压制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;
        }
    }
}

当您粘贴一些带有关键字的“代码”时,它看起来像这样:

粘贴一些文字

打字看起来像这样:

输入一些文字


推荐阅读