【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);
        }
    }
}

- 文畢-


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM