65.9K
CodeProject 正在变化。 阅读更多。
Home

DIY智能感知

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (77投票s)

2004年1月13日

8分钟阅读

viewsIcon

409259

downloadIcon

15568

DIY 智能感知/自动完成。

Sample Image - DIY-Intellisense.gif

快速开始

下载项目,编译并运行。转到菜单并加载 %systemroot%\Microsoft.NET\Framework\v1.xxx\system.dll。在 rich text box 中键入 System 然后输入 "."。将出现一个类/命名空间的列表框。

注意:命名空间图标同时用于命名空间和没有图标的类型,这是我偷懒的表现。

引言

如果您是一名程序员,并且不熟悉智能感知,那么您过去五年很可能一直躲在与世隔绝的洞穴里,靠透析机维生。Intellisense 是微软的商标,但其概念已被数十种代码编辑器采用。简而言之,它是在代码编辑器中键入一个已识别的单词时出现的下拉列表。例如,在 Visual Studio .NET 中键入 System 然后输入 "." 会显示 System 命名空间类型的下拉列表。另一个例子是 Macromedia Dreamweaver - 键入一个特定的 HTML 标记然后输入一个空格,就会出现该 HTML 标记的属性列表。

关于智能感知历史的模糊讨论可以在 这里找到。

在这篇文章和配套的代码中,我将演示如何为 RichTextBox 实现一个类似于智能感知(我将称之为自动完成)的系统,该系统由用户按下 "." 键触发。

完整的项目具有以下功能

  • 从下拉列表中自动完成
  • 易于自定义出现在下拉列表框中的项的存储

我还额外添加了以下功能

  • 列表框中显示图标,每个项目旁边都有图标
  • 输入方法名称和 "(" 时,会出现一个简单无华的工具提示,显示参数列表
  • 通过加载程序集来填充自动完成查找树。

所有这些在虚拟纸上看起来并不多,但源代码将近 900 行(包括窗体组件布局代码)。我将逐段解释代码,但首先是要求。

要求

该项目是一个简单的窗体,带有一个 RichTextBox 和一个主菜单。RichTextBox 控件没有以任何方式进行子类化,只是更改了字体为 Courier,仅此而已。

基本要求是,当用户输入一个单词,然后输入 ".",并且该单词是已知单词时,一个项目列表出现在 "." 下方和右侧的 ListBox 中。用户可以从中选择一个 ListBox 中的项目,该项目的文本将被粘贴到 RichTextBox 中,完成用户正在输入的单词。

该窗体还有一个 TreeView,用于存储要查找的项;一个 ListBox,用于显示项;以及一个文本字段,用作方法参数列表的廉价且实用的工具提示。

按下神奇的 "." 键

RichTextBoxOnKeyDown 事件检查 OemPeriod(我假设这在所有正在使用的机器上都有效)。当按下点时,使用 getLastWord() 方法检索前一个单词。

然后检查该单词是否存在于我们用于存储查找的树中(稍后介绍)。如果找到,则使用这些项目填充 ListBoxpopulateListBox() 管理此过程,返回一个布尔值,表示是否填充了任何项目。之后,对 ListBox 进行定位。

幸运的是,RichTextBox 提供了一个方法使此任务变得简单。使用 RichTextBoxGetPositionFromCharIndex(),我们可以获取当前插入符号位置的 Point struct。使用此信息,我们可以根据 "." 定位 ListBox 的 X 坐标,Y 坐标保持不变,但加上了字体的高度(我稍微调整了一下,移除了 Courier 的额外 2 像素);然后将其定位在 "." 的下方。Visual Studio .NET 会根据文本编辑器中行的位置将框置于当前行的上方或下方(如果您在文本编辑器的最后一行,则框会出现在上方)。我没有添加此功能,但我想这并不难实现。

查找树

如前所述,自动完成查找的所有项都存储在 TreeView 中。对于我实现的此类代码完成 - 命名空间/类/方法 - 将项存储在树中是完全合理的。对于 HTML 代码完成,这也将是理想的选择,根节点可以是核心 HTML 标记集,下面的节点是属性。对于平面列表,可以去掉 TreeView,单独使用 ListBox

开始项目时,我开始创建自己的树实现来存储项。然后我意识到这样做没有意义,因为 .NET 提供了 TreeView 形式的结构。它有作为控件的开销,但它允许您在 Visual Studio .NET、Borland C# builder、#develop 等 IDE 中轻松添加和编辑项,从而加快了工作速度。

代码完成行为

按下 "." 并出现项目下拉列表后,将应用以下逻辑

  • 焦点回到 RichTextBox,它会捕获上下键。这些键用于在 ListBox 中上下移动。
  • 如果选中了一个项目,并且用户按下 Return、Space 或 Tab 键,则项目列表框将隐藏,并触发单词的自动完成。
  • ListBox 可见时,可以按下任何字母数字键。除了 Backspace 或 Shift 之外的任何其他键都会隐藏 ListBox
  • 按下 Delete 键会隐藏 ListBox,前提是正在删除的字符是 "."。
  • 当按下任何字母数字键时,将从输入的字符中连接一个字符串。
  • 搜索 ListBox 以查看是否有任何项目以输入的组合开头,如果有,则选中该项目。
  • 双击 ListBox 中的项目会触发自动完成。

所有这些都在 RichTextBoxOnKeyDown 事件中进行检查。

在树中查找项

找到之前输入的单词后,会搜索 TreeView 以查看是否可以找到该单词。由于单词可能包含完整的命名空间,因此会根据点将其分解,并检查节点的 FullPath 属性与连接的字符串是否匹配。这是在一个递归函数中完成的。找到节点后,将其存储在成员变量中,然后用于用所有子节点填充 ListBox。通过创建一个实现 IComparable 的自定义类型的数组来对节点按文本排序。该数组从 TreeNode 填充,然后排序,并使用它来填充 ListBox

显然,每次填充时对项目进行排序是一种相当慢的方法,更可取的方法是在填充树时对其进行排序。一个可排序的 TreeView 将是另一篇文章的主题(有人实现过吗?!),但现在这样就足够了。

自动完成文本

这是一个相当简单的过程 - 点之前的所有内容存储在一个字符串中,点之后的所有内容存储在另一个字符串中,然后将选定的 ListBox 项目的文本插入到这两个字符串之间。然后,RichTextBox 的文本将被此新文本替换。我没有测试过这种方法处理大量文本的情况,如果出现闪烁,可能需要自定义 RichTextBox

附加功能

为了演示自动完成,源代码中还有另外 2 个方法,用于读取程序集并用其类型填充树。readAssembly() 方法加载一个程序集并遍历其所有类型,为命名空间、类、方法、字段和事件添加节点。每个类型都使用 addMembers() 来添加其字段、方法和事件作为节点。

每个 ListBox 项目都有一个图标来指示其类型 - 该类型存储在每个 TreeNodeTag 属性中。Tag 属性还存储方法的参数列表作为字符串,因此如果我们假设该节点是一个方法(如果 Tag 属性是字符串)。图标在 ListBox 中的显示是使用 GListBox 类完成的。

如上所述,当键入一个方法并按下左括号 "(" 键时,会显示一个工具提示,其中包含方法的参数列表。我找不到任何免费的自定义工具提示实现,所以我使用了一个 TextBox 来显示详细信息,并将其定位方式与项目列表框相同。

结论

我不想在此文章中深入介绍每个方法,以免内容过于冗长,因此我在这里停止。希望它对大家有用。我想指出,这是概念验证代码,而不是最终产品;我没有测试 TreeView 的性能,看看它能否处理数千个节点,而且还有一些小的改进空间(如果有一支开发团队、丰厚的报酬以及世界上最大的公司支持我!)。

已知bug

  • 当您转到查看窗体设计器时,可能会遇到索引超出界限的错误。这是 GListBox 的一个问题,我没有修复该错误。这仅发生在设计时,项目仍然可以正常编译。
  • 使用 Tab 键进行自动完成会在 RichTextBox 中添加一个 Tab 键(即使 e.Handled = true……这是我以后需要研究的问题)。
  • 工具提示未保持在窗体前面。
© . All rights reserved.