zoukankan      html  css  js  c++  java
  • 自动完成菜单

    「佳作赏析」自动完成菜单(Pavel Torgashov著,野比译)

     

    自动完成菜单

    [乌克兰]Pavel Torgashov著,野比译

    自定义用于RichTextBox、TextBox和其他控件的自动完成菜单。


     codeproject.com「2012四月最佳C#文章」获奖作品


    击阅读原文

     下载源代码 - 192.4KB

     下载DEMO - 22.9KB

        

    简介

    我们所有人都用过VisualStudio的自动完成菜单,也就是IntelliSense。它非常管用,不是吗?不幸的是,.NET框架并没有包含内置的自动完成菜单组件。本文制作的组件将填补这个空缺。

    AutocompleteMenu允许你轻松地在你的窗体上任何 TextBox或是RichTextBox里加入下拉提示框功能。(就像上面图中演示的那样——野比注)

    实现

    该组件包含了数个类。下面是主要的类极其功能小结:

    AutocompleteMenu - 包含基本功能的主要组件。它订阅TextBox的事件,查找合适的变体,显示一个下拉菜单,并将新的文字插入文本框。

    下面是AutocompleteMenu的基本属性:

    • AllowTabKey - 允许使用TAB键选择菜单项
    • AppearInterval - 菜单显示的间隔(毫秒)
    • ImageList - 保存菜单项用到的图片
    • Items - 菜单项列表(AutocompleteMenu最简单的用法)
    • MaximumSize - 弹出菜单最大尺寸
    • MinFragmentLength - 菜单显示的最小片段长度。只有当光标处当前片段长度不低于MinFragmentLength才会显示AutocompleteMenu。
    • SearchPattern - 搜索光标处片段的正则表达式

    AutocompleteMenuHost - 从ToolStripDropDown派生的可视化组件。该控件能让你在不丢失窗体焦点的情况下显示菜单。

    AutocompleteListView - 从UserControl继承的可视化组件。使用GDI+绘制下拉菜单的菜单项。该控件和ListView很像,但能够高效地显示大量的元素。

    AutocompleteItem - 菜单项。这个类包含了菜单项的所有必要信息。你可以从AutocompleteItem继承出你的元素,并覆盖其虚方法,这样来扩展菜单功能。下面是AutocompleteItem的基本属性:

    • Text - 要插入文本框的文本
    • MenuText - 显示在弹出菜单上的文本
    • ImageIndex - 菜单项的图片索引
    • ToolTipTitle - 工具提示标题。如果ToolTipTitle为null,则不会显示工具提示
    • ToolTipText - 工具提示文本
    • Tag - 你可在此附加任何数据

    下面是一些你可以重写的方法:

    • GetTextForReplace - 返回要插入的文本。你可以动态修改要插入的文本。例如,你可以插入当前日期。
    • Compare - 这个方法定义了菜单项显示与否。默认情况下,只有菜单项以给定的片段开头,才会显示该项。但是你可以重写这个方法的行为。比如,你可以用子字符串来比较,或是进行一些模糊比较。
    • OnSelected - 这个方法会在文本插入文本框的时候调用。你可以在这里对文本进行一些额外的操作。比如,你可以把光标移动到某处。

    控件库里还提供了几个从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例程。

    快捷键:

    你可以使用以下的快捷键:

    • Ctrl+Space - 强制打开AutocompleteMenu
    • 上、下、上翻页、下翻页 - 在菜单中来回移动
    • 回车、Tab、鼠标双击 - 插入选中的文本(Tab键只在AllowTabKey为true时才起作用)
    • Esc - 关闭菜单

    注意,尽管窗体焦点位于文本框,这些按键仍然哼正常工作。

    当你点选了菜单项,就会显示相应的工具提示。

    自定义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的控件。

    同样,自动完成菜单也兼容任何支持以下属性和方法的控件:

    • string SelectedText{get;set;}
    • int SelectionLength{get;set;}
    • int SelectionStart{get;set;}
    • Point GetPositionFromCharIndex(int charPos)

    即使你的控件不支持这些方法(或属性),你也可以为它创建自己的包装器。要这样做,你必须创建自己的包装类,并实现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

     
    标签: C#控件图形
  • 相关阅读:
    界面实现的小总结
    创建线程的三种方式
    之前总结的今天给大分享一下iOS
    MVVM
    响应者链条
    layer图层常见属性
    NSRunLoop && NSTimer
    关于Angularjs做的一个购物车小例子
    css中实现元素的绝对居中
    剑指 Offer 18. 删除链表的节点 链表
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2534611.html
Copyright © 2011-2022 走看看