WebForms RichTextBox编辑方法?

问题描述

我正在使用C#WinForm,并且我有一个RichTextBox,试图使它看起来像C#脚本。

表示使用特定单词时,我希望它们具有颜色。当他们通过更改来编辑单词时,我希望它变回黑色。

我的方法行得通,但是当创建滚动选项并需要使用它来查看下面的代码时,它确实很杂乱,并导致错误。 (键入时,丰富文本框几乎可以不停地跳来跳去

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

解决方法

我对您的代码进行了一些重构,以期希望能为着色文本提供更好的方法。每次触发TextChanged事件时,实例化字符串数组也是不理想的。

已更新该想法是建立一个单词缓冲区,以便在键入时与您的单词集匹配。

该缓冲区记录每个键,如果.IsLetterOrDigit则将其添加到StringBuilder缓冲区中。 该缓冲区还有一些其他错误,包括记录按键值,并且如果您按下退格键,则不删除记录的字符。

代替单词缓冲区,使用 RegEx 来匹配保留单词列表中的任何单词。建立备用词 RegEx ,这样您最终会得到类似\b(word|word2|word3....)\b的内容,这是通过BuildRegExPattern(..)方法中的代码完成的。

一旦您按了字母或数字以外的任意键,就会检查缓冲区中的内容,如果内容与单词匹配,则只有ScriptRichTextBox.Text中光标之前的文本才会显示。检查并更改。

从保留字中删除。(点),因为这只会使匹配条件复杂化。组合式样中的RegEx会与单词完全匹配,因此,如果键入FARRIGHTcms之类的单词,将不会部分改变颜色。

另外,我还介绍了粘贴过程Ctrl+V ,因为这在WinForms中有点痛苦,而且可能会经常发生。

还有一些较旧的问题eg. this one涵盖了滚动行为,其中显示了如何通过添加[System.Runtime.InteropServices.DllImport("user32.dll")]属性来互操作,但是没有它也可以做到。 / p>

要防止所有滚动跳动,可以使用表单上的.DefWndProc(msg)方法。 this question指向WM_SETREDRAW属性。

还有this list个其他属性可以设置。

完整的实现是这样的:

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,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;
        }
    }
}

当您将一些 “代码” 与关键字粘贴在一起时,它看起来像这样:

paste some text

并键入如下内容:

type some text

,

在两天后找不到真正有效的东西或有令人讨厌的错误的确定。在努力使它可行之后,我自己设法找到了解决方案。一个很大的想法是人们尝试一次编辑所有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","Directions" };
        String[] coloredNames2 = { "cm.","new" };
        String[] coloredNames3 = { "MyPosition",".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;
        }
    }