问题描述
我正在使用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会与单词完全匹配,因此,如果键入FARRIGHT
或cms
之类的单词,将不会部分改变颜色。
另外,我还介绍了粘贴过程按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;
}
}
}
当您将一些 “代码” 与关键字粘贴在一起时,它看起来像这样:
并键入如下内容:
,在两天后找不到真正有效的东西或有令人讨厌的错误的确定。在努力使它可行之后,我自己设法找到了解决方案。一个很大的想法是人们尝试一次编辑所有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;
}
}