在 C# 中创建自动完成/代码补全






4.84/5 (9投票s)
在本文中,我们将使用 C# 创建一个自动完成/代码补全窗口,该窗口会自动显示单词或关键字列表的弹出窗口,并将它们插入到 RichTextBox 中。
引言
在本文中,我们将使用 C# 创建一个自动完成/代码补全弹出窗口。智能代码补全是某些编程环境中的一种上下文感知代码补全功能,它通过减少拼写错误和其他常见错误来加速应用程序编码过程。尝试这样做通常是通过输入时的自动完成弹出窗口、函数参数查询、与语法错误相关的查询提示等来实现的。智能代码补全及相关工具使用反射为变量名、函数和方法提供文档和消歧。智能代码补全出现在许多程序环境中,Visual Studio 的 Intellisense 就是一个示例实现。
那么,用 C# 构建代码补全有多难呢?
我在网上搜索了,除了已开发的编辑器,我什么代码都没找到。
我知道当输入代码时会显示一个列表,但如何将该列表显示在 RichTextBox 上呢?
在研究这段代码之前,我认为开发它非常困难,需要非常专业的知识,但我错了,这取决于你的想法。
这很简单,怎么做?
只需创建一个 Listbox 对象,将其添加到 RichTextBox,从 List Box 获取选定的项并将其插入到 RichTextBox 中,然后移除该 List Box。
你只需要向 List Box 添加或删除项目。
你可以根据自己的需求自定义代码补全窗口(列表框)(如上图 3 所示)。
我们还将自动完成括号(参见 ProcessAutoCompleteBrackets() 函数)。
在本文中,我们将创建简单的 C# 代码补全。下载代码以使用 XML 查看代码补全。
这无非就是添加或删除 Listbox。
请参阅下面的算法以获得更好的理解。
为了理解,请仔细阅读代码(ccrichtextbox)中的所有注释。
算法
1] : 创建 ListBox 对象。(我使用的是 CodeCompleteBox 对象名)
2] : 从 RichTextBox 读取 X-Y 坐标
3] : 声明一个布尔变量以确定是否已添加 CodeCompleteBox。(我使用的是 isCodeCompleteBoxAdded 变量)& 一个字符串变量以确定完整的字符串(我使用的是 EnteredKey 变量)
4] : 声明关键字的 ArrayList。(我使用的是 keywordslist)
5] : 向 RichTextBox 添加以下事件
按键 (Key Press)
1 : 识别按下的键是否为字母。
2 : 从 CodeCompleteBox 中移除所有项。
3 : 读取 keywordslist 中的每一项。
4 : 如果 keywordslist 中的每一项都以按下的键字符开头,则将其添加到 CodeCompleteBox。
5 : 读取 keywordslist 中的每一项。
6 : 如果 keywordslist 中的每一项都以按下的键开头,则将其设置为选中状态。
7 : 设置 CodeCompleteBox 的默认光标。
8 : 设置 CodeCompleteBox 的大小。
9 : 通过读取第 2 步中的(x,y)坐标设置 CodeCompleteBox 的位置。
10 : 将 CodeCompleteBox 添加到 RichTextBox。
11 : 将 isCodeCompleteBoxAdded 设置为 true。
文本已更改 (Text Changed) :
如果 RichTextBox 文本为 null,则从 RichTextBox 中移除 CodeCompleteBox。移除前请先检查 isCodeCompleteBoxAdded 是否为 true。
按键 (Key Down) :
1 : 检查按下的键是否为 Space、Enter、Escape & Back,如果是则转到下一步;
2 : 如果 isCodeCompleteBoxAdded 为 true,则从 RichTextBox 中移除 CodeCompleteBox。
鼠标单击 (Mouse Click) :
如果 isCodeCompleteBoxAdded 为 true,则从 RichTextBox 中移除 CodeCompleteBox。
垂直滚动 (VScroll) :
如果 isCodeCompleteBoxAdded 为 true,则从 RichTextBox 中移除 CodeCompleteBox。
6] : 向 CodeCompleteBox 添加以下事件
按键 (Key Down) :
1 : 检查 isCodeCompleteBoxAdded 是否为 true,如果是则转到下一步。
2 : 如果按下的键是 Enter 或 Space,则转到下一步。
3 : 读取 CodeCompleteBox 中选定的项,并将其插入到 RichTextBox 的 SelectionStart 位置。
4 : 从 RichTextBox 中移除 CodeCompleteBox。
按键 (Key Press) :
它用于将按下的字符插入到 RichTextBox 中,并选择 CodeCompleteBox 中的项。
鼠标单击 (Mouse Click) :
与上面的 Key Down 事件步骤 3 相同。
<stron
工具提示 (Tool Tip)
在这里,为了显示工具提示或显示 CodeCompletebBox 选定项的信息,我们将使用 Label 来显示信息,并将此 Label 添加到一个 Panel 中,然后将此 Panel 添加到 RichTextBox 的特定位置或 CodeCompleteBox 的下一个位置。
在本文中,我们不创建工具提示,但下载源文件,它包含带有工具提示的完整代码。
所有操作与 CodeCompleteBox 相同,只需向 CodeCompleteBox 添加 Key Up 事件。你可以在 CodeCompleteBox 的选定项上设置工具提示,只需在 CCRichTextBox 代码的 ProcessToolTips() 函数中声明你的项及其描述。
使用代码
我创建了一个名为 CCRichTextBox 的类,其超类是 RichTextBox。
首先,让我们看一下一些重要的函数。
我使用了一个名为 ProcessCodeCompletionAction(String key) 的函数,在程序生成按键事件时调用它。我读取 getWidth() 和 getHeight() 函数来为 CodeCompleteBox 设置宽度和高度,但你可以将其设置为默认值。将按下的字符与 EnteredKey 连接起来,执行上面算法中 Key Press 中定义的所有步骤。这里我们不看如何为 CodeCompleteBox 添加工具提示,所以如果你遇到错误,请移除所有工具提示组件。
public void ProcessCodeCompletionAction(String key)
{
EnteredKey = "";
// concat the key & EnteredKey postfix
EnteredKey = EnteredKey + key;
if (Char.IsLetter(key[0]))
{
// Clear the CodeCompleteBox Items
CodeCompleteBox.Items.Clear();
//add each item to CodeCompleteBox
foreach (String item in keywordslist)
{
//check item is starts with EnteredKey or not
if (item.StartsWith(EnteredKey))
{
CodeCompleteBox.Items.Add(item);
}
}
// read each item from CodeCompleteBox to set SelectedItem
foreach (String item in keywordslist)
{
if (item.StartsWith(EnteredKey))
{
CodeCompleteBox.SelectedItem = item;
// set Default cursor to CodeCompleteBox
CodeCompleteBox.Cursor = Cursors.Default;
// set Size to CodeCompleteBox
// width=this.getWidth() & height=this.getHeight()+(int)this.Font.Size
CodeCompleteBox.Size = new System.Drawing.Size(this.getWidth(), this.getHeight() + (int)this.Font.Size);
// set Location to CodeCompleteBox by calling getXYPoints() function
CodeCompleteBox.Location = this.getXYPoints();
// adding controls of CodeCompleteBox to CCRichTextBox
this.Controls.Add(CodeCompleteBox);
// set Focus to CodeCompleteBox
CodeCompleteBox.Focus();
// set isCodeCompleteBoxAdded to true
isCodeCompleteBoxAdded = true;
// set location to ToolTipControl
ToolTipControl.Location = new Point(CodeCompleteBox.Location.X + CodeCompleteBox.Width, CodeCompleteBox.Location.Y);
// call ProcessToolTips() function
this.ProcessToolTips(CodeCompleteBox.SelectedItem.ToString());
// add ToolTipControl to CCRichTextBox
this.Controls.Add(ToolTipControl);
isToolTipControlAdded = true;
break;
}
else
{
isCodeCompleteBoxAdded = false;
}
}
}
}
我们还将自动完成括号。
我使用的是函数 ProcessAutoCompleteBrackets(KeyPressEventArgs e)。
每当按下(、{、"、'、<、[ 键时,都会在 RichTextBox 的 SelectionStart 位置的下一个位置插入相反的字符。
public void ProcessAutoCompleteBrackets(KeyPressEventArgs e)
{
String s = e.KeyChar.ToString();
int sel = this.SelectionStart;
switch (s)
{
case "(": this.Text = this.Text.Insert(sel, "()");
e.Handled = true;
this.SelectionStart = sel + 1;
break;
case "{":
String t = "{\n \n}";
this.Text = this.Text.Insert(sel, t);
e.Handled = true;
this.SelectionStart = sel + t.Length - 6;
break;
case "[": this.Text = this.Text.Insert(sel, "[]");
e.Handled = true;
this.SelectionStart = sel + 1;
break;
case "<": this.Text = this.Text.Insert(sel, "<>");
e.Handled = true;
this.SelectionStart = sel + 1;
break;
case "\"": this.Text = this.Text.Insert(sel, "\"\"");
e.Handled = true;
this.SelectionStart = sel + 1;
break;
case "'": this.Text = this.Text.Insert(sel, "''");
e.Handled = true;
this.SelectionStart = sel + 1;
break;
}
}
好的,让我们根据上面的算法执行所有步骤。
1) 创建 List Box 对象
public ListBox CodeCompleteBox = new ListBox();
2) 从 RichTextBox 读取 X-Y 坐标。
此函数通过添加或减少 RichTextBox 字体大小来返回 (x,y) 坐标。
请看下图,它显示了 CodeCompleteBox 上下颠倒的 x-y 位置。
public Point getXYPoints()
{
//get current caret position point from CCRichTextBox
Point pt = this.GetPositionFromCharIndex(this.SelectionStart);
// increase the Y co-ordinate size by 10 & Font size of CCRichTextBox
pt.Y = pt.Y + (int)this.Font.Size + 10;
// check Y co-ordinate value is greater than CCRichTextBox Height - CodeCompleteBox
// for add CodeCompleteBox at the Bottom of CCRichTextBox
if (pt.Y > this.Height - CodeCompleteBox.Height)
{
pt.Y = pt.Y - CodeCompleteBox.Height - (int)this.Font.Size - 10;
}
return pt;
}
3) 声明一个布尔变量以确定是否已添加 CodeCompleteBox
public static Boolean isCodeCompleteBoxAdded = false; <span style="color: rgb(17, 17, 17); font-family: 'Segoe UI', Arial, sans-serif; font-size: 14px;"> </span>
声明一个字符串变量以确定完整的字符串
public static String EnteredKey = "";
4) 声明关键字的 ArrayList。在这里,你可以预定义列表,也可以通过 XML 文件读取关键字。下载源代码,我同时使用了 Array list 和 string 的 <List>。这里,让我们声明列表的数组。这里我声明了其中的一些项。
public String[] keywordslist = {
"bool",
"break",
"case",
"catch",
"char",
"class",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"false",
"float",
"for",
"goto",
"if"
};
5) 向 CCRichTextBox 添加 Key Press、Text Changed、Key Down、Mouse Click、VScroll 事件。
按键事件 (Key Press Event) :
在这里,我们将直接调用 ProcessCodeCompletionAction() 函数。
但在这里我们可以执行更多操作,例如创建类或数据类型。
我声明了两个额外的变量:布尔型 isClassCreated = false; 和布尔型 isDataTypeDeclared = false; 用来确定从 CodeCompleteBox 插入的项是否是类/数据类型。为此,你需要声明类和数据类型的列表。例如:一旦你从 CodeCompleteBox 中选择了一个项,CodeCompleteBox 将不会出现,直到你按下 =/; 因为创建 Form 的对象可以有两种方式定义
Form frm; 或 Form frm=new Form();
下载源代码,一旦你将 CCRichTextBox 拖放到你的窗体上,在属性中你可以输入关键字/类/数据类型的列表。
protected override void OnKeyPress(KeyPressEventArgs e)
{
base.OnKeyPress(e);
ProcessAutoCompleteBrackets(e);
String key = e.KeyChar.ToString();
if (isClassCreated && (key == "=" || key == ";"))
{
ProcessCodeCompletionAction(key);
isClassCreated = false;
}
else if (isClassCreated && key != "=")
{ }
else if (isDataTypeDeclared && (key == ";" || key == "{"||key=="}" || key == "(" || key == ")"))
{
ProcessCodeCompletionAction(key);
isDataTypeDeclared = false;
}
else if (isDataTypeDeclared && key != ";")
{ }
else
{
ProcessCodeCompletionAction(key);
}
}
文本已更改事件 (Text Changed Event) : 如果其文本为 null,则从 CCRichTextBox 中移除 CodeCompleteBox。
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
if (this.Text == "")
{
if (isCodeCompleteBoxAdded)
{
this.Controls.Remove(CodeCompleteBox);
EnteredKey = "";
}
}
}
按键事件 (Key Down Event) : 检查按下的键是否为 Space、Enter、Escape & Back,如果是,则从 CCRichTextBox 中移除 CodeCompleteBox。这里我只展示了关于 Space 键,其他键请自行添加。
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
switch(e.KeyCode)
{
case Keys.Space:
if (isCodeCompleteBoxAdded)
{
this.Controls.Remove(CodeCompleteBox);
EnteredKey = "";
}
break
}
}
鼠标单击事件 (Mouse Click Event) : 如果 isCodeCompleteBoxAdded 为 true,则从 CCRichTextBox 中移除 CodeCompleteBox,与上面的按键事件代码相同。
垂直滚动事件 (VScroll Event) : 代码与上面的鼠标单击事件相同。
这是一个从弹出窗口插入所选文本的函数,
private void CodeCompleteBox_InsertSelectedText(int length)
{
int sel = this.SelectionStart;
String text = CodeCompleteBox.SelectedItem.ToString();
text = text.Remove(0, length);
this.Text = this.Text.Insert(sel, text + " ");
this.SelectionStart = sel + (text + " ").Length;
this.Controls.Remove(CodeCompleteBox);
this.ProcessDeclaredClasses(CodeCompleteBox.SelectedItem.ToString());
this.ProcessDeclaredDataTypes(CodeCompleteBox.SelectedItem.ToString());
if (isToolTipControlAdded)
{
this.Controls.Remove(ToolTipControl);
}
}
6) 向 CodeCompleteBox 添加 Key Down、Key Press 和 Mouse Click 事件。
public CCRichTextBox()
{
CodeCompleteBox.KeyDown += new KeyEventHandler(CodeCompleteBox_KeyDown);
CodeCompleteBox.KeyUp += new KeyEventHandler(CodeCompleteBox_KeyUp);
CodeCompleteBox.KeyPress += new KeyPressEventHandler(CodeCompleteBox_KeyPress);
CodeCompleteBox.MouseClick += new MouseEventHandler(CodeCompleteBox_MouseClick);
}
按键 (Key Down) : 首先识别按下的键是 Enter/Space 还是其他键,然后识别 CodeCompleteBox 是否已添加到 CCRichTextBox,然后识别 CodeCompleteBox 中选定的项是否以 EnteredKey 开头,然后读取 CodeCompleteBox 中选定的项。读取 EnteredKey 的长度,根据长度替换 CodeCompleteBox 中选定项的前几个字符。现在将该选定的项插入到 CCRichTextBox 的 SelectionStart 位置,然后从 CCRichTextBox 中移除 CodeCompleteBox。
如果按下的键是 Space,则在项后面插入一个空格。
如果按下的键是 Left/Right,则从 CCRichTextBox 中移除 CodeCompleteBox。
这是 Enter 和 Space 键的代码
private void CodeCompleteBox_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
// if Space key is down when CodeCompleteBox is added to the CCRichTextBox
// then insert SelectedItem from CodeCompleteBox to this at SelectionStart location
// also inserting a single space because space bar key is down
case Keys.Space:
if (isCodeCompleteBoxAdded)
{
if (CodeCompleteBox.SelectedItem.ToString().StartsWith(EnteredKey))
{
if (EnteredKey != "")
{
// before inserting a selected item first check EnteredKey length
// if it is 1 then remove first character of selected item from CodeCompleteBox
// if it is 2 then remove first 2 characters of selected item from CodeCompleteBox
// if it is 3 then remove first 3 characters
// if it is greater than 3 then replace EnteredKey with null/"" in selected item text
// this all arrangement is important because characters keywords added to CodeCompleteBox could be same
if (EnteredKey.Length == 1)
{
this.CodeCompleteBox_InsertSelectedText(1);
}
else if (EnteredKey.Length == 2)
{
this.CodeCompleteBox_InsertSelectedText(2);
}
else if (EnteredKey.Length == 3)
{
this.CodeCompleteBox_InsertSelectedText(3);
}
else
{
this.CodeCompleteBox_InsertSelectedText(EnteredKey.Length);
}
}
}
else
{
int sel = this.SelectionStart;
this.Text = this.Text.Insert(sel, " ");
this.SelectionStart = sel + " ".Length;
}
}
break;
// if Enter key is down when CodeCompleteBox is added to the CCRichTextBox
// then insert SelectedItem from CodeCompleteBox to this at SelectionStart location
// same all procedure as used when Space key is down only without inserting a single space here
case Keys.Enter:
if (isCodeCompleteBoxAdded)
{
if (EnteredKey != "")
{
if (EnteredKey.Length == 1)
{
this.CodeCompleteBox_InsertSelectedText(1);
}
else if (EnteredKey.Length == 2)
{
this.CodeCompleteBox_InsertSelectedText(2);
}
else if (EnteredKey.Length == 3)
{
this.CodeCompleteBox_InsertSelectedText(3);
}
else
{
this.CodeCompleteBox_InsertSelectedText(EnteredKey.Length);
}
}
}
break;
// if Left key is down then remove CodeCompleteBox from this
case Keys.Left:
if (isCodeCompleteBoxAdded)
{
this.Controls.Remove(CodeCompleteBox);
EnteredKey = "";
if (isToolTipControlAdded)
{
this.Controls.Remove(ToolTipControl);
}
}
break;
// if Right key is down then remove CodeCompleteBox from this
case Keys.Right:
if (isCodeCompleteBoxAdded)
{
this.Controls.Remove(CodeCompleteBox);
EnteredKey = "";
if (isToolTipControlAdded)
{
this.Controls.Remove(ToolTipControl);
}
}
break;
}
}
按键抬起事件 (Key Up Event) : 此事件会移除代码补全框,并根据按键插入代码片段。
private void CodeCompleteBox_KeyUp(object sender, KeyEventArgs e)
{
switch(e.KeyCode)
{
case Keys.Up :
case Keys.Down:
if (isCodeCompleteBoxAdded)
{
ToolTipControl.Visible = true;
this.ProcessToolTips(CodeCompleteBox.SelectedItem.ToString());
}
break;
case Keys.Tab:
if (isCodeCompleteBoxAdded)
{
this.InsertingCodeSnippetCodes();
this.Controls.Remove(CodeCompleteBox);
EnteredKey = "";
if (isToolTipControlAdded)
{
this.Controls.Remove(ToolTipControl);
}
}
break;
}
}
按键事件 (Key Press Event) : 此事件用于选择以 EnteredKey 开头的项(在连接之后),然后读取 CodeCompleteBox 中的所有项,并将以 EnteredKey 开头的项设置为选中状态,并且还识别是否按下了特殊字符,如果按下了,则从 CCRichTextBox 中移除 CodeCompleteBox。
private void CodeCompleteBox_KeyPress(object sender, KeyPressEventArgs e)
{
String str = e.KeyChar.ToString();
// in this event we must insert pressed key to this because Focus is on CodeCompleteBox
// first check pressed key is not Space,Enter,Escape & Back
// Space=32, Enter=13, Escape=27, Back=8
if (Convert.ToInt32(e.KeyChar) != 13 && Convert.ToInt32(e.KeyChar) != 32 && Convert.ToInt32(e.KeyChar) != 27 && Convert.ToInt32(e.KeyChar) != 8)
{
if (isCodeCompleteBoxAdded)
{
// insert pressed key to CCRichTextBox at SelectionStart position
int sel = this.SelectionStart;
this.Text = this.Text.Insert(sel, str);
this.SelectionStart = sel + 1;
e.Handled = true;
// concat the EnteredKey and pressed key on CodeCompleteBox
EnteredKey = EnteredKey + str;
// search item in CodeCompleteBox which starts with EnteredKey and set it to selected
foreach (String item in CodeCompleteBox.Items)
{
if (item.StartsWith(EnteredKey))
{
CodeCompleteBox.SelectedItem = item;
break;
}
}
}
}
// if pressed key is Back then set focus to CCRichTextBox
else if (Convert.ToInt32(e.KeyChar) == 8)
{
this.Focus();
}
// if pressed key is not Back then remove CodeCompleteBox from CCRichTextBox
else if (Convert.ToInt32(e.KeyChar) != 8)
{
if (isCodeCompleteBoxAdded)
{
this.Controls.Remove(CodeCompleteBox);
EnteredKey = "";
}
}
// check pressed key on CodeCompleteBox is special character or not
// if it is a special character then remove CodeCompleteBox from CCRichTextBox
switch (str)
{
case "~":
case "`":
case "!":
case "@":
case "#":
case "$":
case "%":
case "^":
case "&":
case "*":
case "-":
case "_":
case "+":
case "=":
case "(":
case ")":
case "[":
case "]":
case "{":
case "}":
case ":":
case ";":
case "\"":
case "'":
case "|":
case "\\":
case "<":
case ">":
case ",":
case ".":
case "/":
case "?":
if (isCodeCompleteBoxAdded)
{
this.Controls.Remove(CodeCompleteBox);
EnteredKey = "";
}
break;
}
}
鼠标单击事件 (Mouse Click Event) : 此事件代码与 CodeCompleteBox 中的 Enter 键按下事件相同。
以上就是算法的所有步骤。下载源代码以使用 XML 文件创建代码补全,以及自定义代码补全弹出窗口。在属性中,你可以更改代码补全窗口的背景色和前景色。
</stron