從本篇開始會通過實例介紹如何實現組件控件編程。在上一篇中提到通過組合實現組件編程,達到靈 活添加功能的效果。那麼是如何組合的呢?一般是通過事件,在組件中處理控件的相關事件,在事件處理 程序中封裝需要的功能。
本篇的實例是用Label模擬網頁鏈接的效果。在.NET控件庫中已經提供了LinkLabel控件,但該控件強 制顯示下劃線,而且只能改變鏈接顏色,不能改變背景色。這裡通過處理Label控件的鼠標事件,動態改 變其顯示相關屬性,即可模擬出網頁鏈接的效果。而且在事件中可以加入更多的效果,比LinkLabel控件 更絢麗。
下面介紹實現的過程。
首先打開VS.NET,創建項目,選擇項目類型為類庫,輸入項目名稱SimulateLinkLabel,然後確定。
項目建立好後,在“解決方案資源管理器”中對項目點右鍵,選擇添加組件。
在添加新項對話框中輸入組件名稱SimulateLinkLabel,點擊“添加”按鈕。即可完成添加組件,此時 ,可以將默認添加的類Class1刪掉。
因為本組件涉及到WinForm控件和顏色,需要添加相關引用。對項目或者項目引用點右鍵,選擇添加引 用。
在“添加引用”對話框中選擇System.Drawing,點擊確定。然後重復上一步,選擇 System.Windows.Forms,點擊確定。
添加完引用之後,打開SimulateLinkLabel組件的代碼,在代碼頂部添加命名空間。
using System.Drawing;
using System.Windows.Forms;
以上是創建組件的初始化步驟,需要變化的是根據不同的需要添加不同的引用。如果創建的是Windows 控件庫,也可以在新建項目時選擇“Windows控件庫”。但類庫是最通用的項目類型,在其中可以包含組 件,控件甚至窗體,所以一般選擇類庫即可。接下來就開始真正實現本組件。
分析這段代碼,首先在屬性上方有[Category(c_ControlCategory), Description("目標 Label 控件 。")]。這叫做Attribute,中文稱為特性,以便與Property(屬性)區別。關於特性的詳細介紹,請參考開 篇中給出的相關鏈接資料。
這裡介紹一下涉及到的特性的相關知識。要使用特性,必須添加對System.ComponentModel命名控件的 引用,即在代碼頂部添加using System.ComponentModel。而特性最直觀的應用就是在PropertyGrid中可 以看到,如下圖。詳細的介紹請參考如下鏈接:
http://www.cnblogs.com/guanjinke/archive/2006/12/06/584714.html
http://www.cnblogs.com/mapserver/articles/344458.html
Catagory特性是用來在PropertyGrid控件中進行分類的,通過相同的Category特性標記的屬性會被分 配到一組中。這裡聲明了兩個私有常量,因為Category特性不接受變量。
//CategoryAttribute相關常量
private const string c_ControlCategory = "控制";
private const string c_ApparanceCategory = "Appearance";
這裡聲明了兩個用於Category特性的常量,一個是“控制”,一個是“Appearance”,但是在 PropertyGrid控件中顯示的卻都是中文。因為在.NET中已經集成了常用的Category,比如Appearance, Behavior,Layout等,當Category設置為這些值的時候,IDE就會將其顯示為IDE的當前語言。
Description特性是用來在PropertyGrid控件中顯示屬性的描述信息的。當需要對屬性進行詳細解釋的 時候,就采用Description特性,方便用戶理解。
在set代碼段中首先判斷之前的目標Label控件是否與設置的值是否一致,不一致則取消之前的Label控 件的事件綁定,然後再綁定新控件。為什麼必須先取消呢,因為事件綁定到處理方法上之後會一直有效, 如果不取消,就算將其他對象的事件綁定到同一個處理方法上,只要之前的事件發生,還是會調用處理方 法。這一點就必須理解委托的概念,因為事件就是通過委托實現的。關於委托,這裡不作過多的闡述,不 了解的朋友可以查閱相關資料。
這裡又提出一個問題:如果窗體上有很多個Label控件都需要實現網頁鏈接的效果,那豈不是需要有很 多個SimulateLinkLabel組件與之一一對應?這樣做當然可以,但效率有點低了。通過上面介紹的事件綁 定機制,可以靈活避免這個問題。因為多個控件的事件可以綁定到同一個方法,那麼可以將 HandleLabelMouseEvent方法設置為public,通過代碼去綁定需要網頁鏈接效果的Label控件,這些Label 控件任何一個觸發了事件都會去調用對應的事件處理方法。
另外在HandleLabelMouseEvent方法中有一個this.DesignMode,這代表當前狀態是否是設計時。有時 候需要根據設計時還是運行時做出一些特定的操作。這裡表示如果在設計時就不需要綁定控件事件,因為 這時候綁定了也沒什麼效果。但有時候確實需要在設計時執行特定操作,就必須判斷這個屬性了。至於具 體的應用,在以後的文章中遇到了再詳細分析。
為了模擬網頁鏈接效果,在鼠標相關事件處理方法中需要改變Label控件的外觀。而這些外觀要做到靈 活設置,就必須添加相關的屬性。這裡添加了部分顏色相關的屬性,如果需要,還可以添加其他外觀相關 的屬性,比如圖片,邊框,不同狀態的字體等。
這裡為每一個顏色屬性提供了一個對應的標志位屬性,指示是否啟用對應顏色屬性。這些屬性上方除 了Category和Description特性外,還多了一個DefaultValue特性。DefaultValue特性是用來指示屬性的 默認值的,詳細資料可以參考 http://www.cnblogs.com/guanjinke/archive/2006/12/24/602451.html
DefaultValue特性最直觀的理解就是:在PropertyGrid中如果是默認值,則顯示為普通字體,如果被 修改了,則顯示為粗體。在Designer.cs代碼中如果是默認值,則不會生成設置該屬性的代碼。
一般情況下DefaultValue特性只接受簡單類型的值,比如數值、邏輯值、字符串。對顏色的設置則需 要特殊處理,因為Color不是簡單類型。從代碼中可以看到顏色屬性上方的DefaultValue特性格式為 DefaultValue(typeof(Color), "ColorName"),MSDN中的解釋是:
參數
type
類型:System.Type
表示要將值轉換為的類型的 Type。
value
類型:System.String
可以通過該類型的 TypeConverter 和美國英語轉換為該類型的 String。
這裡又出現了新的概念,TypeConverter,詳細介紹可以參考
http://www.cnblogs.com/guanjinke/archive/2006/12/11/588609.html
http://www.cnblogs.com/guanjinke/archive/2006/12/11/589372.html
http://www.cnblogs.com/guanjinke/archive/2006/12/14/592605.html
http://www.cnblogs.com/mapserver/articles/353722.html
當然並不是所有類型的屬性都可以通過這種方法設置默認值的,這種屬性的類型必須有一個對應的 TypeConverter,其中的CanConvertFrom方法必須能接受字符串才行。通過Reflector查看Color的代碼, 可以看到Color類有一個對應的TypeConvert特性,其中的類型是ColorConverter。
ColorConverter繼承了TypeConverter,其中的CanConvertFrom方法如下,說明可以從字符串轉換成 Color。當然,真正從字符串轉換為Color的代碼必須在ConvertFrom中實現。
以上介紹完屬性,就該介紹方法了。綁定事件的方法HandleLabelMouseEvent已經在上面給出了,剩下 就是鼠標事件的處理方法。
#region 鼠標事件處理方法
/// <summary>
/// 【鼠標進入事件】處理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Label_MouseEnter(object sender, EventArgs e)
{
Label item = sender as Label;
if (item != null && item.Enabled)
{
//保存之前的控件樣式
m_LabelForeColor = item.ForeColor;
m_LabelBackColor = item.BackColor;
m_LabelFont = item.Font;
item.Cursor = Cursors.Hand;//改變光標樣式
item.Font = new Font(m_LabelFont, m_LabelFont.Style | FontStyle.Underline);//顯示下劃線以模擬鏈接效果
if (m_EnableHoverForeColor)
item.ForeColor = m_HoverForeColor;//改變前景色
if (m_EnableHoverBackColor)
item.BackColor = m_HoverBackColor;//改變背景色
}
}
/// <summary>
/// 【鼠標離開事件】處理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Label_MouseLeave(object sender, EventArgs e)
{
Label item = sender as Label;
if (item != null && item.Enabled)
{
//恢復控件默認樣式
item.Cursor = Cursors.Default;
item.ForeColor = m_LabelForeColor;
item.BackColor = m_LabelBackColor;
item.Font = m_LabelFont;
}
}
/// <summary>
/// 【按下鼠標按鈕事件】處理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Label_MouseDown(object sender, MouseEventArgs e)
{
Label item = sender as Label;
if (item != null && item.Enabled)
{
if (m_EnableActiveForeColor)
item.ForeColor = m_ActiveForeColor;//顯示活動前景 色
if (m_EnableActiveBackColor)
item.BackColor = m_ActiveBackColor;//顯示活動背景 色
}
}
/// <summary>
/// 【釋放鼠標按鈕事件】處理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Label_MouseUp(object sender, MouseEventArgs e)
{
Label item = sender as Label;
if (item != null && item.Enabled)
{
if (m_EnableActiveForeColor)
item.ForeColor = m_EnableHoverForeColor ? m_HoverForeColor : m_LabelForeColor;//恢復前景色
if (m_EnableActiveBackColor)
item.BackColor = m_EnableHoverBackColor ? m_HoverBackColor : m_LabelBackColor;//恢復背景色
}
}
#endregion 鼠標事件處理方法
這些方法的邏輯都很簡單,主要就是根據不同的鼠標動作去改變Label控件的樣式。其中需要注意的是 Font類型,該類型的屬性都是只讀的,比如它的字體大小是沒法修改的。如果需要修改字體,只能重新實 例化一個。如果需要根據之前的字體實例化,則可以通過Font(Font, FontStyle)構造函數 進行實例化, 或者用更多參數的構造函數。
另外在類頂部給類添加一個DefaultProperty特性,指定為TargetLabel,這樣在選中本組件的時候, 屬性窗口中的PropertyGrid就會默認選中TargetLabel,給開發人員提供更好的交互操作。
下面給出本組件的類圖
完成以上代碼之後,生成一下。添加一個測試的Windows應用程序項目,在該項目用添加對 SimulateLinkLabel項目的引用。添加項目引用的方法和之前添加引用類似,只是要在“添加引用”對話 框中選擇“項目”選項卡,然後選擇要引用的項目即可。此時工具箱中就會多出一個選項卡,如下圖:
將該組件拖動到窗體上,然後在窗體上添加一個Label控件,設置TargetLabel為新增的Label控件。然 後按F5運行,就可以看到當鼠標對Label控件操作時,該控件會顯示成網頁鏈接的效果。
到此就實現了一個簡單的組件,如果希望有更多的功能也可以根據需要添加。例如這裡沒有實現 VisitedLinkColor,可以加入一個bool字段記錄點擊狀態。文中給出一些鏈接資料,不懂的朋友可以參考 示例代碼:http://www.bianceng.net/dotnet/201212/728.htm