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#控件图形
  • 相关阅读:
    219. Contains Duplicate II
    189. Rotate Array
    169. Majority Element
    122. Best Time to Buy and Sell Stock II
    121. Best Time to Buy and Sell Stock
    119. Pascal's Triangle II
    118. Pascal's Triangle
    88. Merge Sorted Array
    53. Maximum Subarray
    CodeForces 359D Pair of Numbers (暴力)
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2534611.html
Copyright © 2011-2022 走看看