自动完成菜单
[乌克兰]Pavel Torgashov著,野比译
自定义用于RichTextBox、TextBox和其他控件的自动完成菜单。
codeproject.com「2012四月最佳C#文章」获奖作品
简介
我们所有人都用过VisualStudio的自动完成菜单,也就是IntelliSense。它非常管用,不是吗?不幸的是,.NET框架并没有包含内置的自动完成菜单组件。本文制作的组件将填补这个空缺。
AutocompleteMenu允许你轻松地在你的窗体上任何 TextBox或是RichTextBox里加入下拉提示框功能。(就像上面图中演示的那样——野比注)
实现
该组件包含了数个类。下面是主要的类极其功能小结:
AutocompleteMenu - 包含基本功能的主要组件。它订阅TextBox的事件,查找合适的变体,显示一个下拉菜单,并将新的文字插入文本框。
下面是AutocompleteMenu的基本属性:
AutocompleteMenuHost - 从ToolStripDropDown派生的可视化组件。该控件能让你在不丢失窗体焦点的情况下显示菜单。
AutocompleteListView - 从UserControl继承的可视化组件。使用GDI+绘制下拉菜单的菜单项。该控件和ListView很像,但能够高效地显示大量的元素。
AutocompleteItem - 菜单项。这个类包含了菜单项的所有必要信息。你可以从AutocompleteItem继承出你的元素,并覆盖其虚方法,这样来扩展菜单功能。下面是AutocompleteItem的基本属性:
下面是一些你可以重写的方法:
控件库里还提供了几个从AutocompleteMenu派生的有用的类:SnippetAutocompleteItem(可以用于插入代码段),MethodAutocompleteItem(可以在点后面插入方法名称),SubstringAutocompleteItem(用子字符串来比较文本),MulticolumnAutocompleteItem(绘制多列菜单)。
使用源代码
简单用法:
1) 把AutocompleteMenu组件扔到你的窗体上
2) 在AutocompleteMenu.Items里输入菜单项
就像这样
3) 设置你的文本框的AutocompleteMenu属性
就像这样
4) 搞定收工
高级用法:
1) 把AutocompleteMenu组件扔到你的窗体上
2) 创建一个菜单项列表,用SetAutocompleteItems()或是AddItem()方法添加到菜单。比如:
1
2
3
4
5
6
7
8
9
10
11
12
|
string
[] snippets = {
"if(^)\n{\n}"
,
"if(^)\n{\n}\nelse\n{\n}"
,
"for(^;;)\n{\n}"
,
"while(^)\n{\n}"
,
"do${\n^}while();"
,
"switch(^)\n{\n\tcase : break;\n}"
};
private
void
BuildAutocompleteMenu()
{
var items =
new
List<AutocompleteItem>();
foreach
(var item
in
snippets)
items.Add(
new
SnippetAutocompleteItem(item) { ImageIndex = 1 });
//设置为自动完成源
autocompleteMenu1.SetAutocompleteItems(items);
}
|
同样,你也可以添加自己的菜单项,就是从AutocompleteItem继承而来的那种。比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
internal
class
EmailSnippet : AutocompleteItem
{
public
EmailSnippet(
string
email):
base
(email)
{
ImageIndex = 0;
ToolTipTitle =
"Insert email:"
;
ToolTipText = email;
}
public
override
CompareResult Compare(
string
fragmentText)
{
if
(fragmentText == Text)
return
CompareResult.VisibleAndSelected;
if
(fragmentText.Contains(
"@"
))
return
CompareResult.Visible;
return
CompareResult.Hidden;
}
}
|
更多详细内容请参考Demo中的AdvancedSample例程。
快捷键:
你可以使用以下的快捷键:
注意,尽管窗体焦点位于文本框,这些按键仍然哼正常工作。
当你点选了菜单项,就会显示相应的工具提示。
自定义ListView
你可以用自定义控件来显示AutocompleteMenu(如ListView、ListBox、DataGridView、TreeView等等)。首先创建自己的控件(从Control类派生),然后实现IAutocompleteListView接口。更多详情请参考CustomListViewSample。
动态上下文菜单
如果你要显示的菜单并非固定内容,而是根据文本而动态改变,那么你会经常用到这个部分。
请注意菜单的SetAutocompleteItems()方法采用了IEnumerable接口作为要显示的菜单项集合参数。
所以,你不必在程序一开始就生成菜单项列表。你只需要在调用菜单项的时候再动态生成就可以了。
下面的代码演示了这个思路:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
autocompleteMenu1.SetAutocompleteItems(
new
DynamicCollection(tb));
....
internal
class
DynamicCollection : IEnumerable<AutocompleteItem>
{
public
IEnumerator<AutocompleteItem> GetEnumerator()
{
return
BuildList().GetEnumerator();
}
private
IEnumerable<AutocompleteItem> BuildList()
{
//找到文本中所有单词
var words =
new
Dictionary<
string
,
string
>();
foreach
(Match m
in
Regex.Matches(tb.Text,
@"\b\w+\b"
))
words[m.Value] = m.Value;
//返回自动完成项
foreach
(var word
in
words.Keys)
yield
return
new
AutocompleteItem(word);
}
}
|
完整的实现代码请参考DynamicMenuSample。
兼容性
自动完成菜单可以兼容TextBox、RichTextBox、MaskedTextBox、FastColoredTextBox(一个非常强大的支持代码着色的文本框控件。近期将对其进行翻译。——野比注)和其他派生自TextBoxBase的控件。
同样,自动完成菜单也兼容任何支持以下属性和方法的控件:
即使你的控件不支持这些方法(或属性),你也可以为它创建自己的包装器。要这样做,你必须创建自己的包装类,并实现ITextBoxWrapper接口。
下面是ITextBoxWrapper的方法和属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
interface
ITextBoxWrapper
{
Control TargetControl {
get
; }
string
Text {
get
; }
string
SelectedText {
get
;
set
; }
int
SelectionLength {
get
;
set
; }
int
SelectionStart {
get
;
set
; }
Point GetPositionFromCharIndex(
int
pos);
event
EventHandler LostFocus;
event
ScrollEventHandler Scroll;
event
KeyEventHandler KeyDown;
event
MouseEventHandler MouseDown;
}
|
做好了包装器之后,你就可以简单地把AutocompleteMenu附加到你的控件上去了。就像这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
partial
class
Form1 : Form
{
public
Form1()
{
InitializeComponent();
//把myControl1附加到autocompleteMenu1
autocompleteMenu1.TargetControlWrapper =
new
MyControlWrapper(myControl1);
}
}
internal
class
MyControlWrapper : ITextBoxWrapper
{
private
MyControl tb;
public
MyControlWrapper(MyControl tb)
{
this
.tb = tb;
}
//在这里实现ITextBoxWrapper
//(略)
}
|
示例
Demo中包含了几个示例:
SimplestSample - 展示最简单的使用控件方法
CustomItemSample - 展示了怎样创建从AutocompleteItem派生的类
AdvancedSample - 展示了怎样创建自定义的带关键字、代码段、方法提示、文本纠错等的自动完成菜单
ExtraLargeSample - 演示了在极大量(100万)菜单项情况下组件的性能
ComboboxSample - 展示了怎样创建模拟Combobox,带特别大的下拉列表和子字符串搜索功能
MulticolumnSample - 展示了怎样制作多列自动完成菜单。就像这样:
CustomListViewSample - 展示了怎样在自动完成菜单中制作自定义ListView。就像这样:
DynamicMenuSample - 这个例子展示了怎样创建动态的上下文敏感的自动完成菜单
DataGridViewSample - 展示了怎样把AutocompleteMenu附加到DataGridView。就像这样:
历史
2012年4月13日 - 首发。
2012年4月21日 - 重构了控件。增加了对FastColoredTextBox和其他控件的支持。
2012年5月9日 - 重构了控件。增加了一些例子。
许可
本文及相关源代码和文件,均采用GNU通用公共许可证(LGPLv3)授权。
(本译文也如此——野比注)
关于作者
Pavel Torgashov
我是Pavel Torgashov。我住在乌克兰基辅市。
我从1998年就开始开发软件了。
我的联系email是:tp_soft[at]mail.ru
乌克兰
关于译者
野比「Conmajia」
我是野比,你也可以叫我Conmajia。
我从93年开始学长城机,开始各种玩。
各种玩。最大的兴趣是绘画和雕刻,
欢迎指导。
中国
(全文完)
© Written by Pavel Torgashov 2012, translated by 野比「Conmajia」 2012