【C#】使用IExtenderProvider为控件添加扩展属性,像ToolTip那样

申明:

- 本文适用于WinForm开发

- 文中的“控件”一词是广义上的说法,泛指包括ToolStripItem、MenuItem在内单个界面元素,并不特指继承自Control类的狭义控件

用过ToolTip这个组件的童鞋都知道这样一个现象:在VS中拖入一个ToolTip,然后点击窗体中的各种控件,在其属性窗格中就会多出一个叫ToolTip的属性出来,如图:

本文要说的就是如何像ToolTip这样,为控件“扩展”出一个属性来(之所以用引号,是因为并不是真的为控件增加了一个属性,而是在VS中看起来像那么回事)。下面通过一个实际案例来说明。

需求是这样的:当用户把鼠标指向菜单项(ToolStripMenuItem)或工具栏项(ToolStripButton、ToolStripLabel之类)的时候,在状态栏标签(ToolStripStatusLabel)中显示该项的功能说明——很多软件都这样做,比如著名的Beyond Compare,如图:

对于这个效果,很容易想到的做法是分别为各个菜单项和工具栏项(下称item)注册MouseEnter和MouseLeave事件,在enter事件中设置状态栏标签(下称viewer)的Text="item的功能描述",在leave事件中viewer.Text=string.Empty,即将Text清空;又或者把所有的item的这俩事件分别绑定到两个总的enter和leave事件处理方法中,然后在方法中用switch区分处理;再或者,把item的功能描述填在各自的Tag属性里,然后在enter事件中只需一句viewer.Text=(sender as ToolStripItem).Tag as string即可~总之方法多多。题外,对于菜单项和工具栏项这样的ToolStripItem,它们天生就有ToolTipText属性可以设置气泡提示,但本文并不探讨气泡方式好还是状态栏方式好。

那么有没有一种方式,写一个像ToolTip这样的组件,比如叫ToolDescribe,在VS中拖入后,就能在item的属性窗格中多出一个叫Describe的属性来,直接在里面填写item的功能描述文本就完了,要能这样该有多好:

这不废话么,图都上了,能不没有么。上ToolDescribe的代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

namespace AhDung.WinForm.Controls
{
    /// <summary>
    /// 为菜单项提供【描述】这一扩展属性
    /// </summary>
    [Description("为菜单项或控件提供描述扩展属性")]
    [ProvideProperty("Describe", typeof(ToolStripItem))]
    public class ToolDescribe : Component, IExtenderProvider
    {
        /// <summary>
        /// 存储所服务的ToolStripItem及其描述
        /// </summary>
        readonly Dictionary<ToolStripItem, string> dic;

        /// <summary>
        /// 获取或设置用于显示菜单项描述的控件
        /// </summary>
        [DefaultValue(null), Description("获取或设置用于显示菜单项描述的控件")]
        public Component Viewer { get; set; }

        /// <summary>
        /// 创建一个ToolDescribe类
        /// </summary>
        public ToolDescribe()
        {
            dic = new Dictionary<ToolStripItem, string>();
        }

        /// <summary>
        /// 获取菜单项描述
        /// </summary>
        [Description("设置菜单项描述")] //虽然方法为Get,但在VS中显示为“设置”才符合理解
        [DefaultValue(null)]
        public string GetDescribe(ToolStripItem item)
        {
            //从集合中取出该item的描述
            string value;
            dic.TryGetValue(item, out value);
            return value;
        }

        /// <summary>
        /// 设置菜单项描述
        /// </summary>
        public void SetDescribe(ToolStripItem item, string value)
        {
            if (item == null) { return; }

            //如果赋值为null或string.Empty,视为不再扩展该ToolStripItem
            if (string.IsNullOrEmpty(value) || value.Trim().Length == 0)
            {
                //从集合中移除该item,并取消其相关事件绑定
                dic.Remove(item);
                item.MouseEnter -= item_MouseEnter;
                item.MouseLeave -= item_MouseLeave;
            }
            else
            {
                if (!dic.ContainsKey(item))//若是新添加的item,注册其相关事件
                {
                    item.MouseEnter += item_MouseEnter;
                    item.MouseLeave += item_MouseLeave;
                }

                //添加或更改该item的描述
                dic[item] = value;//这种写法对于dic中不存在的Key,会自动添加
            }
        }

        //鼠标指向事件
        private void item_MouseEnter(object sender, EventArgs e)
        {
            //让Viewer.Text显示item的描述
            if (Viewer is ToolStripItem) { (Viewer as ToolStripItem).Text = dic[sender as ToolStripItem]; }
            else if (Viewer is StatusBarPanel) { (Viewer as StatusBarPanel).Text = dic[sender as ToolStripItem]; }
            else if (Viewer is Control) { (Viewer as Control).Text = dic[sender as ToolStripItem]; }
        }

        //鼠标移开事件
        private void item_MouseLeave(object sender, EventArgs e)
        {
            //清空Viewer.Text
            if (Viewer is ToolStripItem) { (Viewer as ToolStripItem).Text = string.Empty; }
            else if (Viewer is StatusBarPanel) { (Viewer as StatusBarPanel).Text = string.Empty; }
            else if (Viewer is Control) { (Viewer as Control).Text = string.Empty; }
        }

        /// <summary>
        /// 是否可为某对象扩展属性
        /// </summary>
        public bool CanExtend(object extendee)
        {
            return true;
        }
    }
}

实现说明:

1、让ToolDescribe类实现System.ComponentModel.IExtenderProvider接口,并继承System.ComponentModel.Component类,同时用ProvidePropertyAttribute特性描述该类。实现IExtenderProvider接口就表明本类是一个【扩展程序提供程序】,MSDN有相关的示例:http://msdn.microsoft.com/zh-cn/library/ms229066(v=vs.80).aspx。那么到底是要给什么类扩展出什么属性呢,这是由ProvideProperty特性定义的,本类的目的是为【ToolStripItem】类扩展出一个叫【Describe】的属性,所以这样描述[ProvideProperty("Describe", typeof(ToolStripItem))]。继承Component则是为了让ToolDescribe像ToolTip那样能拖入到VS组件栏中,这样item的属性窗格中才会多出一个Describe属性来;

2、在ToolDescribe类中定义一个集合类容器,用于存储设置了功能描述的item及其描述文本。本例采用的是Dictionary<ToolStripItem, string>,显然Key代表item,Value代表item的描述文本;

3、定义一个属性,类型为Component,用来呈现item功能描述的控件,本例是Viewer。类型之所以为Component而不是Control,是考虑到Viewer要允许设置为状态栏标签(ToolStripStatusLabel)的,而ToolStripStatusLabel并不是Control,所以得把类型定得再“基类”一点,以加大Viewer的设置灵活性;

4、实现一个public string GetDescribe(ToolStripItem item)方法,作用是获取指定item的描述文本,这也是第2步中定义容器的原因,没有容器记录下各个item及其描述文本的话,这个方法将难以实现。注意该方法的命名必须是Get+ProvideProperty中定义的扩展属性名,即Describe,合起来就是GetDescribe。另外,对该方法加DefaultValue特性是必要的,不然当拖入ToolDescribe时,VS会对所有item进行扩展,不管有没有设置某个item的Describe,这点可以从InitializeComponent方法中看出来;

5、实现一个public void SetDescribe(ToolStripItem item, string value)方法,命名要求同上。该方法的作用显然是用来设置item的描述文本的。具体实现逻辑上,它主要要做两件事:①把item及其value存入集合;②注册item的相关事件。即当item发生了什么时要做什么事,本例当然是当item发生MouseEnter和MouseLeave时,要做一些事,所以得注册item的这俩事件。说到这里,其实可以理解显示item功能描述的核心实现仍然是基于对相关事件的注册,也就是说本质上,与前面提到的分别为各个item注册事件这种看起来原始且笨的方式是一样一样的,用了ToolDescribe也没有什么高大上的地方,只是没那么麻烦了而已。当然这里说的是应用层面,底层VS对IExtenderProvider程序做了些什么那自然是高大上的;

6、实现上述事件的处理方法,本例就是item_MouseEnter和item_MouseLeave,实现上没什么好说的。只是上面的代码重点在演示实现套路,所以没有做额外的性能优化处理,如果代码要应用在生产环境,则需对if (Viewer is ToolStripItem)这样的语句进行处理,例如可以在Viewer属性的setter中就记录下Viewer属于何种类型,然后就不必在每次事件触发时判断Viewer类型了;

7、最后是实现IExtenderProvider接口的唯一成员:public bool CanExtend(object extendee)方法。这方法纯粹是供VS用的,方法的逻辑是,当你在VS中点击某个控件时,extendee就是该控件,返回true则在该控件的属性窗格中添加扩展属性,否则不添加。本例是直接返回true,那会不会造成点击任意控件都会多出Describe属性呢,答案是不会,因为ProvideProperty特性已经首先限定了只扩展ToolStripItem类。那该方法在什么情况下需要加逻辑呢,举例,要为Button和TextBox扩展属性,自然ProvideProperty限定为Control较合适,但这样一来,不属于Button和TextBox的其它控件也被扩展了,为了不扩展它们,就需要在CanExtend方法中加逻辑return extendee is Button || extendee is TextBox,等于一个控件是否会被扩展,取决于两个地方,一是ProvideProperty,二是CanExtend,前者是第一关,后者是第二关;

OK,套路就是这样,看看成果:

1、拖入一个ToolDescribe组件,并设置其Viewer为状态栏标签:

2、设置item的Describe属性,见图3;

3、跑起来看看:

话说回来,对于这种效果,路过高手如果有比添加扩展属性更好的方案还望不吝赐教。

下面附赠一枚正式的ToolDescribe,这个比上述Demo强在,可以为ToolStripItem、Control、MenuItem添加扩展属性,并对性能优化做了处理,可用于生产环境。同时可以看出ProvideProperty特性可以叠加使用,达到为不同控件添加不同扩展属性的目的,话说之所以不写成为Component扩展Describe属性,是因为MenuItem只有鼠标移进事件(Select),没有移出事件,要想达到指向没有设置Describe的MenuItem时,Viewer.Text清空,只有为所有MenuItem扩展,这也是没有为GetDescribeOfMenuItem加DefaultValue特性的原因~不知道看官能不能明白我在说什么,呵呵。上代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

namespace AhDung.WinForm.Controls
{
    /// <summary>
    /// 为菜单项或控件提供功能描述扩展属性
    /// </summary>
    /// <remarks>
    /// Author:AhDung
    /// Update:201412161356,初版
    /// </remarks>
    [Description("为菜单项或控件提供功能描述扩展属性")]
    [ProvideProperty("DescribeOfToolStripItem", typeof(ToolStripItem))]
    [ProvideProperty("DescribeOfControl", typeof(Control))]
    [ProvideProperty("DescribeOfMenuItem", typeof(MenuItem))]
    public class ToolDescribe : Component, IExtenderProvider
    {
        /// <summary>
        /// 存储所服务的组件及其描述
        /// </summary>
        readonly Dictionary<Component, string> dic;

        Component viewer;
        bool viewerIsToolStripItem, viewerIsStatusBarPanel, viewerIsControl;
        ToolStripItem viewerAsToolStripItem;
        StatusBarPanel viewerAsStatusBarPanel;
        Control viewerAsControl;

        /// <summary>
        /// 获取或设置用于显示功能描述的控件
        /// </summary>
        [DefaultValue(null), Description("设置用于显示功能描述的控件")]
        public Component Viewer
        {
            get { return viewer; }
            set
            {
                if (viewer == value) { return; }

                viewer = value;

                viewerIsToolStripItem = false;
                viewerIsStatusBarPanel = false;
                viewerIsControl = false;

                if (viewer is ToolStripItem) { viewerIsToolStripItem = true; viewerAsToolStripItem = viewer as ToolStripItem; }
                else if (viewer is StatusBarPanel) { viewerIsStatusBarPanel = true; viewerAsStatusBarPanel = viewer as StatusBarPanel; }
                else if (viewer is Control) { viewerIsControl = true; viewerAsControl = viewer as Control; }
            }
        }

        /// <summary>
        /// 创建一个ToolDescribe类
        /// </summary>
        public ToolDescribe()
        {
            dic = new Dictionary<Component, string>();
        }

        /// <summary>
        /// 获取ToolStripItem描述
        /// </summary>
        [DefaultValue(null), Description("设置菜单项的功能描述")]
        public string GetDescribeOfToolStripItem(ToolStripItem item)
        {
            return GetDescribe(item);
        }

        /// <summary>
        /// 获取Control描述
        /// </summary>
        [DefaultValue(null), Description("设置控件的功能描述")]
        public string GetDescribeOfControl(Control item)
        {
            return GetDescribe(item);
        }

        /// <summary>
        /// 获取MenuItem描述
        /// </summary>
        [Description("设置菜单项的功能描述")]
        public string GetDescribeOfMenuItem(MenuItem item)
        {
            return GetDescribe(item);
        }

        //获取组件描述(私有)
        private string GetDescribe(Component item)
        {
            string value;
            dic.TryGetValue(item, out value);
            return value;
        }

        /// <summary>
        /// 设置ToolStripItem描述
        /// </summary>
        public void SetDescribeOfToolStripItem(ToolStripItem item, string value)
        {
            if (item == null) { return; }

            if (string.IsNullOrEmpty(value) || value.Trim().Length == 0)
            {
                dic.Remove(item);
                item.MouseEnter -= item_MouseEnter;
                item.MouseLeave -= item_MouseLeave;
            }
            else
            {
                if (!dic.ContainsKey(item))
                {
                    item.MouseEnter += item_MouseEnter;
                    item.MouseLeave += item_MouseLeave;
                }

                dic[item] = value;
            }
        }

        /// <summary>
        /// 设置Control描述
        /// </summary>
        public void SetDescribeOfControl(Control item, string value)
        {
            if (item == null) { return; }

            if (string.IsNullOrEmpty(value) || value.Trim().Length == 0)
            {
                dic.Remove(item);
                item.MouseEnter -= item_MouseEnter;
                item.MouseLeave -= item_MouseLeave;
            }
            else
            {
                if (!dic.ContainsKey(item))
                {
                    item.MouseEnter += item_MouseEnter;
                    item.MouseLeave += item_MouseLeave;
                }

                dic[item] = value;
            }
        }

        /// <summary>
        /// 设置MenuItem描述
        /// </summary>
        public void SetDescribeOfMenuItem(MenuItem item, string value)
        {
            if (item == null) { return; }

            if (!dic.ContainsKey(item))
            {
                item.Select += item_MouseEnter;
            }

            dic[item] = value;
        }

        //组件鼠标指向事件
        private void item_MouseEnter(object sender, EventArgs e)
        {
            this.SetViewerText(dic[sender as Component]);
        }

        //组件鼠标移开事件
        private void item_MouseLeave(object sender, EventArgs e)
        {
            this.SetViewerText(string.Empty);
        }

        /// <summary>
        /// 设置Viewer的Text值
        /// </summary>
        private void SetViewerText(string s)
        {
            if (viewerIsToolStripItem) { viewerAsToolStripItem.Text = s; }
            else if (viewerIsStatusBarPanel) { viewerAsStatusBarPanel.Text = s; }
            else if (viewerIsControl) { viewerAsControl.Text = s; }
        }

        //实现IExtenderProvider成员
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool CanExtend(object extendee)
        {
            return !(extendee is Form);
        }
    }
}

- 文毕-

时间: 2024-08-08 09:58:20

【C#】使用IExtenderProvider为控件添加扩展属性,像ToolTip那样的相关文章

使用xib给label等文字显示控件添加attributed属性

如题,之前没注意过这个xib这个属性,可能很多小伙伴也没注意,下面为大家介绍一下xib这个逆天的操作.比起手动加attribute,简直简单粗暴.下面进入正题: 首先  你需要一个xib  在上边添加个label之类用于显示文本的控件 创建好之后,注意右边栏 选中第四栏,会发现label有个text的下拉框,点击下拉框 会发现,我们想要的attributed藏在这,之后我们选择attributed 我们之前的界面会变成如上的样子.其中 红框中的内容就是来给label的文字添加attributed

WPF系列 —— 控件添加依赖属性

依赖属性的概念,用途 ,如何新建与使用.本文用做一个自定义TimePicker控件来演示WPF的依赖属性的简单应用. 先上TimePicker的一个效果图. 概念 和 用途:依赖属性是对传统.net 属性的一种封装,使一个传统.net属性支持 WPF 中的 数据绑定.动画.样式 等功能. 新建:任意代码代码文件中 ,输入 propdp 再双击tab键.生成如下的代码块. MyProperty: 依赖属性的名称: ownerclass: 当前依赖属性绑定的所有类; new PropertyMeta

WPF对某控件添加右键属性

代码创建右键属性 ContextMenu cm = new ContextMenu(); MenuItem mi = new MenuItem(); mi.Header = "打开此文件所有文件夹"; mi.Click += mi_Click; cm.Items.Add(mi); lv.ContextMenu = cm;

为Kindeditor控件添加图片自动上传功能

Kindeditor是一款功能强大的开源在线HTML编辑器,支持所见即所得的编辑效果.它使用JavaScript编写,可以无缝地与多个不同的语言环境进行集成,如.NET.PHP.ASP.Java等.官方网站可以查看这里:http://kindeditor.net/index.php Kindeditor本身提供了许多非常实用的插件,由于代码开源,开发人员可以根据需要对其进行任意扩展和修改. 在使用Kindeditor编辑网站内容时考虑这样一个场景:编辑人员往往会从其它页面或者Word文档将内容复

C# WinForm给Button或其它控件添加快捷键响应

今天做东西遇到要给按钮添加快捷键.就在这介绍三种添加快捷键的方式. 第一种Alt + *(按钮快捷键) 在大家给button.label.menuStrip等控件设置Text属性时在名字后边加&键名就可以了,比如button1.text= "确定(&A)".就会有快捷键了,这时候按Alt+A就可以执行按钮单击事件. 第二种Ctrl+*及其他组合键   在WinForm中设置要使用组合键的窗体的KeyPreview(向窗体注册键盘事件)属性为True;然后使用窗体的Key

c#给用户控件添加事件处理程序

1.首先在usercontrol后台添加如下代码: public partial class MyControl: UserControl { //添加事件代理       public event EventHandler AX; //在需要响应的事件中添加 private void MyControl_MouseClick(object sender, MouseEventArgs e)        {            if (AX != null)            {    

自动为DEV GridView控件添加SizeChanged事件

实现gdv设置的抽象对象,不用每个gdv控件都添加sizechanged事件,只需执行gdc绑定sql函数,在其中会自动添加SizeChanged事件. //2016.5.13 by sngk //根据控件大小自动调整GridView列宽模式,尽量使列充满 //2016.11.19 实现自动添加sizechanged事件 //该函数只执行一次,在赋值时 public static void BestFitGridViewColumnsWidth(DevExpress.XtraGrid.GridC

鸡啄米MFC教程笔记之七:对话框:为控件添加消息处理函数

MFC为对话框和控件等定义了诸多消息,我们对它们操作时会触发消息,这些消息最终由消息处理函数处理.比如我们点击按钮时就会产生BN_CLICKED消息,修改编辑框内容时会产生EN_CHANGE消息等.一般为了让某种操作达到效果,我们只需要实现某个消息的消息处理函数. 一.添加消息处理函数 鸡啄米仍以前面的加法计算器的程序为例,说明怎样为“计算”按钮控件添加消息处理函数.添加方法列出4种: 1.使用Class Wizard添加消息处理函数 用过的VC++6.0的朋友应该对Class Wizard很熟

MFC 控件添加热键

给MFC中的控件添加我们想要的控件热键,在动手之前,必须清楚,热键分为local的和global的, 其中local的职能在当前程序有焦点(被激活)时有效,而global的,则无论什么时候都有效,测试local的要优先于global的,就是如果当前激活窗口的快捷键与未激活窗口的快捷键重叠,当前激活窗口优先响应.另外还包括menu,button. 自然而然,创建热键的方法也有多种,不同的创建方法创建的热键作用范围不一定相同.应该根据需求合理的选择自己的方法. 方法一: 打开对话框资源,选择指定控件