★★★【庖丁解牛:縱向切入Asp.net 3.5控件和組件開發技術系列—(4)服務器控件屬性】★★★

 

 

 

4  庖丁解牛系列服務器控件屬性

 

 

本章內容

4.1  控件屬性的作用

4.2  簡單屬性

4.3  屬性的設計時特性

4.4  複雜屬性

4.5  深入研究—定製自己的屬性編輯器

4.6  類型轉換器

4.7  實現自定義屬性

[點擊下載本書word格式完整目錄介紹]

4.1  控件屬性的作用

 

屬性、方法和事件是控件使用者與控件交互的接口。本節主要介紹控件屬性。屬性分爲系統屬性和自定義的屬性。

 

4-1 Webcontrol類屬性窗口

4.1.1  系統屬性

當開發控件時如果選擇基類,比如選擇繼承WebControl基類,一旦繼承於此類,一些默認的系統屬性就會成爲當前控件的屬性集的一部分,圖4-1所示的是WebControl類的系統屬性。

可以看到一個通用Web控件所應具備的基本屬性都已經有了,在實際開發控件時選擇某個基類。

4.1.2  自定義屬性

4.1.1節所講的是系統已有的屬性,在開發控件時一般都要爲自己的控件增加一些自定義屬性。自定義屬性與系統屬性完全一樣。只是由於不具有系統屬性的通用性而需要開發者自己去實現。下面看一下屬性的語法格式:

string strText = "默認值";

public string Text

{

    get

    {

        return strText;

    }

 

    set

    {

        strText = value;

    }

}

以上是一個最簡單的屬性,由一個setget語段組成。注意,setget段不是必需的,比如可以去掉set段表示此屬性只允許讀取而不允許接收值。

事實上屬性的特性範疇還比較多,如簡單屬性、複雜屬性,以及屬性在設計時的特性和標記形式的格式等,下面將對這些特性一一進行介紹。

4.2  簡單屬性

簡單屬性是類型爲字符串的或容易轉換爲字符串的屬性。簡單屬性在控件的開始標記上自行保留爲屬性。.NET Framework 類庫中的基元值類型,如StringBooleanInt16Int32DateTimeByteCharDoubleEnum均爲簡單屬性。可以通過添加代碼將簡單屬性存儲在ViewState字典中,以便在回髮間進行狀態管理。請看例子:

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

public string Value

{

    get

    {

        String s = (String)ViewState["Value"];

        return ((s == null) ? String.Empty : s);

    }

 

    set

    {

        ViewState["Value"] = value;

    }

}

上面聲明的簡單屬性中,屬性可接收及其返回值的類型是String,表示本屬性爲簡單屬性。另外,簡單屬性可以直接使用ViewState存儲其值,因爲簡單屬性可以直接映射爲字符串,而ViewState中可以直接接收的格式也是字符串。

ViewState是頁面視圖狀態機制中的存儲機制,是爲解決在Web瀏覽器兩次訪問之間無狀態保持而提供的一種機制,視圖信息存儲在網頁中專用HiddenField區域,而且每次頁面提交都會往返於客戶端和服務器,因此一般視圖主要用於存儲少量的文本數據信息,而不適合存儲數據量比較大的業務數據。另外,複雜屬性的存儲也要自己實現視圖機制功能,這一點在後面討論視圖機制的章節會詳細介紹,這裏僅作了解即可。

只要控件中定義了上面的代碼段,對應在頁面設計器屬性窗口中就會包含此項,如圖4-2所示。

 

 

4-2  屬性窗口中的屬性

 

在屬性窗口中輸入一些文本,打開設計器中的源代碼會看到如下標記的ASP.NET代碼:

<cc1:controlproperty id="ControlProperty1" runat="server" Value="簡單屬性">

</cc1:controlproperty>

同樣,BooleanInt16Int32DateTimeByteCharDoubleEnum等類型的屬性與上面的String類型屬性代碼標記完全一樣。簡單屬性比較簡單,就講解到這裏。

4.3  屬性的設計時特性

.NET Framework爲控件設計時屬性提供了很多豐富的類,這些屬性的功能非常靈活,控制範圍廣泛,比如可以控制該屬性在屬性窗口中的顯示模式,如:是否在屬性窗口中顯示該屬性,也可以指定此屬性必須接收值類型描述,按組分類等,也可以控制文本的標記呈現格式等,甚至可以自己定義一個屬性類,實現自己想實現的功能。下面講一下常用的.NET Framework的屬性類對控件的支持功能。

Ø  Bindable

指定屬性是否可以綁定一個有效數據源,通常使用布爾值進行設置。例如:Bindable(true)。如果使用值true標記屬性,表示該屬性可以綁定一個有效數據源

Ø  Browsable

指定屬性是否應該在屬性窗口中顯示,使用布爾值設置。一般情況下,對於常用的和比較重要的屬性設置Browsabletrue,否則設置Browsablefalse

Ø  EditorBrowsable

設置屬性在編輯器中的可見性,比如設置在智能提示列表不顯示或高級用戶纔可以看到該屬性。

Ø  Category

指定屬性在屬性瀏覽器中進行分組顯示的類別。該設計時特性幫助可視化編輯器將屬性進行邏輯分組。通常分爲:外觀(Appearance)、行爲(Behavior)、佈局(Layout)、數據(Data)、操作(Action)、鍵盤(Key)和鼠標(Mouse)等。如果您安裝的是中文版的IDE則默認情況下中文分類和英文分類是通用的即設置成“數據”或“Data”類別是等價的

Ø  Description

設置顯示在屬性窗口最下面的描述屬性功能的文字說明。

Ø  DesignOnly

如果此屬性設置爲true,表示該屬性只能在設計期間使用,不能在頁面代碼中設置其值。

Ø  ReadOnly

設置該屬性是否爲只讀狀態。如果此特性設置爲true,則在屬性窗口能看到屬性,但不能設置其值。另外,通過在屬性語句體中把 set 語句段去掉也可以起到相同的效果。

Ø  Themeable

設置該屬性是否支持主題特性,默認情況下屬性都支持主題。當該屬性與界面無關時可以設置其值爲false,禁用該屬性的主題功能。

Ø  DesignerSerializationVisibility

指定屬性是否以及如何在代碼中序列化,其值爲DesignerSerializationVisibility的枚舉值,存在3種設置方式:

—  DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)指定序列化程序不應該序列化屬性值;

—  DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)指定應該允許序列化程序序列化屬性的值;

—  DesignerSerializationVisibility(DesignerSerializationVisibility.Content)指定序列化程序應該序列化屬性的內容,而不是屬性本身。此字段爲只讀。Visible爲其默認值。

這裏說的序列化是指在IDE中的設計器界面切換到代碼視圖時,看到的代碼標記,或反向切換時把代碼標記轉化到設計器界面。後面講複雜屬性時會通過示例介紹此屬性功能。

Ø  NotifyParentProperty

指示當此設計特性應用到的屬性的值被修改時將通知其父屬性。換言之,如果屬性的父屬性應該在該屬性值被修改時接到通知,則向該屬性應用NotifyParentProperty特性。通常使用布爾值進行設置。一般常用於複雜屬性通知轉換器更新到父級標記

Ø  ParseChildren

使用該設計特性指示當在頁面上以聲明方式使用控件時,嵌套在服務器控件標記內的XML元素是應該視爲屬性還是應視爲子控件。通常情況下,包含兩種聲明方式:

—  ParseChildren(true)表示將子XML元素作爲服務器控件的屬性分析;

—  ParseChildren(bool childrenasProperty, string defaultProperty),其中childrenasProperty和上面的方式中的布爾值參數意義相同,defaultProperty定義默認情況下將子控件分析爲服務器控件的集合屬性。

Ø  PersistChildren

該設計特性指示設計時是否應將服務器控件的子控件作爲內部嵌套控件保持。如果該特性爲PersistChildren(true),則將服務器控件的子控件作爲嵌套服務器控件標記保持。如果爲PersistChildren(false),則將該控件的屬性作爲嵌套元素保持。

Ø  PersistenceMode

指定如何將服務器控件屬性或事件保持到ASP.NET頁面的元數據屬性,共存在4種枚舉設置方式:

—  PersistenceModePersistenceMode.Attribute)指定屬性或事件保持爲屬性;

—  PersistenceModePersistenceMode.EncodedInnerDefaultProperty)指定屬性作爲服務器控件的唯一內部文本,如果屬性值是HTML編碼的,只能對字符串作這種指定;

—  PersistenceModePersistenceMode.InnerDefaultProperty)指定屬性在服務器控件中保持爲內部文本,還指示將該屬性定義爲元素的默認屬性,只能指定一個屬性爲默認屬性;

—  PersistenceModePersistenceMode.InnerProperty)指定屬性在服務器控件中保持爲嵌套標記,通常用於複雜對象,它們具有自己的持久性屬性。

關於以上4種標記的具體用法下一節會詳細介紹

Ø  DefaultValue

指定屬性的默認值此特性的設置需要特別謹慎假如設置的值不爲空則開發人員在使用時如果自己輸入的值與默認值相同則控件不會裝載開發人員輸入的值也就是說此默認值不能指定爲具有有效意義或業務意義的實際值一般設置爲空即可。

Ø  DisplayName

指定在屬性窗口中顯示的別名。此別名僅在屬性窗口中看到,當轉換器轉換到代碼視圖,以及在頁面後面的代碼中編碼還是以實際的屬性名稱爲準,而不是以該別名爲準。

Ø  ParenthesizedPropertyName

指定屬性在屬性窗口中顯示時是否帶有括號相當於在Category分組特性基礎上的對屬性窗口屬性集的排序功能如果不帶括號該屬性會自動排在該組的前面

Ø  PasswordPropertyText

指定是否設置成密碼文本。如果設置爲true,則在屬性窗口中輸入的文本會用特定的密碼符號顯示,而不是顯示原文本;另外,在代碼視圖中看到的仍爲原文本。

Ø  TypeConverter

指定用作此特性所綁定到的對象的轉換器的類型。用於轉換的類必須從TypeConverter繼承。使用ConverterTypeName屬性來獲取爲該特性所綁定到的對象提供數據轉換的類名。後面會通過代碼示例講解如何自定義一個自己的類型轉換器。

Ø  Editor

指定該屬性的編輯器,如系統的文件編輯器、文本編輯器、顏色編輯器,還有集合編輯器等,也可以自己實現編輯器,具體用法後面會講到。

Ø  ToolBoxItem

此屬性爲類特性。屬於工具箱屬性,可以設置當前控件是否在工具箱中顯示,以及所在工具箱項的類型名稱等信息。默認生成的控件都顯示在工具箱中。

Ø  ToolBoxData

此特性爲類特性,即不是屬性的特性,而是類的特性,設置位置也是在類的上面。ToolBoxData表示從工具箱中拖一個控件到設計界面上時默認顯示標記格式,如:

[ToolboxData("<{0}:ControlProperty runat=server></{0}:ControlProperty>")]

可以修改參數字符串,定製爲自己想要的格式,但要保證所添加的屬性爲有意義的屬性。

Ø  DefaultProperty

此特性爲類特性。它指定服務器控件的默認屬性,例如:[DefaultProperty("Text")]

指定用黑色粗體顯示默認屬性特性的屬性名稱。一般設置比較重要或常用的屬性爲默認的屬性。如TextBox控件的Text屬性。

Ø  DefaultEvent

此特性爲類特性指定服務器控件的默認事件,例如:[DefaultEvent("OnClicked")]

指定用黑色粗體顯示默認事件特性的事件名稱。一般設置比較重要或常用的屬性爲默認的事件,如Button控件的OnClick事件。

Ø  ValidationProperty

此特性爲類特性,指定該控件的哪個屬性作爲驗證屬性。當該控件與驗證控件組合使用時,驗證控件會自動驗證該特性指定的屬性。

Ø  AspNetHostingPermission

此屬性爲JIT編譯時代碼訪問安全屬性。需要使用此屬性確保鏈接到控件的代碼具有適當的安全權限。Control類帶有兩個JIT編譯時代碼訪問安全屬性標記:

AspNetHostingPermission(SecurityAction.Demand,Level=AspNetHostingPermissionLevel.Minimal)AspNetHostingPermission(SecurityAction.InheritanceDemand,Level=AspNetHosting PermissionLevel.Minimal).在使用時應把第一個屬性應用於當前開發的控件,第二個屬性是可選的,因爲繼承請求是可傳遞的,在派生類中仍有效。

Ø  ControlBuilder

分析時特性,將自定義控件生成器與控件關聯。只有在您希望使用自定義控件生成器,對頁分析器用分析控件的聲明性語法的默認邏輯進行修改時,才需要應用此特性。如果僅希望指定控件標記中的內容是否與屬性或子控件對應,請使用ParseChildrenAttribute,而不要使用自定義控件生成器。

Ø  Designer

設計時特性,指定與控件關聯的設計器類。控件設計器類用於控制關聯的控件在可視化設計器的設計圖面上的外觀和行爲。

還有一些更復雜的,包括在設計模式下的元數據屬性類在這裏沒有列出,因爲在後面有專門的章節詳細介紹,通過代碼示例更容易理解。在這裏只要理解上面這些屬性類功能,開發一般的控件是沒有問題了。

4.4  複雜屬性

4.4.1  概述

複雜屬性是屬性的類型不是簡單值類型,或者是一個包含其他屬性的類。例如.NET Framework中的StyleFontPoint等都是複雜屬性。另外還有集合屬性,這裏也將它作爲複雜屬性歸類,對於集合屬性在本章後面會單獨拿出來一節進行詳細講解。

4.4.2  複雜屬性的幾種標記形式

先看看一個典型的代碼段:

<asp:GridView ID="GridView1" runat="server">

    <FooterStyle BackColor="#507CD1" Font-Bold="true" ForeColor="White" />

    <RowStyle BackColor="#EFF3FB" />

    <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign= "Center" />

    <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="true" ForeColor= "#333333" />

    <HeaderStyle BackColor="#507CD1" Font-Bold="true" ForeColor="White" />

    <EditRowStyle BackColor="#2461BF" />

    <AlternatingRowStyle BackColor="White" />

</asp:GridView>       

<asp:ListBox ID="ListBox1" runat="server">

    <asp:ListItem Value="1"></asp:ListItem>

    <asp:ListItem Value="0"></asp:ListItem>

</asp:ListBox>

代碼非常簡單,一段是GridView控件的一些屬性,另一段是ListBox控件的一些屬性。仔細觀察一下這些控件的屬性標記,我們能很容易給它們歸類,比如:GridViewID/Runat屬性標記類型相似,FootStyle/RowStyle這樣的標記類似,還有Font-Bold這樣的屬性標記,ListBox的集合項ListItem標記也比較特殊等這麼多標記類型。我們在開發控件時當然也希望能夠生成這麼多靈活的標記類型,那麼本節就詳細介紹一下服務端控件的這些標記類型是怎樣生成的。

開始之前,有必要說明一下,下面所有代碼示例在調試時都是在設計模式下進行的。關於在設計模式下如何調試代碼在第2章已經詳細講解過了,如果讀者還有疑問請再回顧一下第2章的內容。

通常情況下,複雜屬性表現爲幾種形式:連字符形式屬性、內部嵌套形式屬性和內部嵌套形式默認屬性。下面將介紹以上幾種形式複雜屬性的具體實現方法。

4.4.2.1  連字符形式的複雜屬性標記

連字符複雜屬性標記是指屬性通過“複雜屬性名稱-複雜屬性的子屬性名稱”的格式追加到主控件的標記形式。下面用一個例子來講解這種標記。

首先,定義一個複合類Person,結構如下:

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

public class Person

{

    private string strName;

    /// <summary>

    /// 姓名

    /// </summary>

    public string Name

    {

        get { return strName; }

        set { strName = value; }

    }

   

    private int intAge;

    /// <summary>

    /// 年齡

    /// </summary>

    public int Age

    {

        get { return intAge; }

        set { intAge = value; }

    }

}

 

u  再在控件中增加一個類型爲Person的屬性,將以下代碼增加到控件中:

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. private Person pPerson;
  6. [Description("複雜屬性")]
  7. [Category("複雜屬性")]
  8. public Person Person
  9. {
  10.     get
  11.     {
  12.         if (pPerson == null)
  13.         {
  14.             pPerson = new Person();
  15.         }
  16.         return pPerson; 
  17.     }
  18. }

此屬性與簡單屬性的區別有兩點:第一,屬性接收和返回的類型不是簡單類型(intstring 等),而是用我們自己定義的Person類;第二,複雜屬性一般沒有set語句,因爲一般是對複雜屬性的子屬性(或子對象)賦值,只要保證它的子屬性(子對象)中具有get/set語句即可。編譯此控件,在IDE中打開頁面,並打開控件的屬性窗口,會看到如圖4-3所示的界面。

 

圖4-3 複雜屬性

 

 

另外,在屬性窗口中比較這兩個屬性,您會發現上節講的簡單屬性的Value屬性可以設置其值,複雜屬性Person是隻讀的,上面我們沒有設置ReadOnly特性。這是因爲複雜屬性中類型比較複雜,甚至還有嵌套。如果把所有複雜屬性包含其子屬性的值都放到這一個框中,顯然不太方便。這就要求我們自己根據複雜屬性類型增加一些序列化的特性。

解決辦法是,爲主控件屬性Person增加PersistenceModeDesignerSerializationVisibility兩個設計特性片段代碼如下所示:

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. … …
  6. [PersistenceMode(PersistenceMode.Attribute)]
  7. [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
  8. public Person Person
  9. {
  10.     … …
  11. }

1

PersistenceMode特性

PersistenceMode特性指定在頁面*.aspx*.ascx文件中如何保持複雜屬性,理解此特性用法非常重要,這裏詳細介紹一下它的用法。PersistenceMode有四種枚舉狀態:

Ø PersistenceMode.Attribute

表示複雜屬性的標記作爲主控件的屬性,如果複雜屬性包含子屬性,則子屬性持久化成破折號連接的樣式,比如:

<asp:GridView ID="GridView1" runat="server">           

    <HeaderStyle BackColor="#507CD1" Font-Bold="true" ForeColor="White" />

</asp:GridView>

上面代碼中的Font-Bold對於<HeaderStyle>來說就是使用了PersistenceMode下的Attribute枚舉標記類型。本節例子中就是實現此標記形式。

Ø PersistenceMode.InnerProperty

表示用屬性名稱作爲嵌套標籤表示複雜屬性,比如GridViewHeaderStyle屬性,就是使用了PersistenceMode下的InnerProperty標記形式。代碼如下:

<asp:GridView ID="GridView1" runat="server">           

    <HeaderStyle BackColor="#507CD1" Font-Bold="true" ForeColor="White" />

</asp:GridView>

Ø PersistenceMode.InnerDefaultProperty

該特性值與InnerProperty類似都是在主控件外標記複雜屬性不同的是InnerDefaultProperty不需要像InnerProperty那樣把屬性名作爲最外標籤一般用於常用的或重要複雜屬性或集合如:

<asp:ListBox ID="ListBox1" runat="server">

    <asp:ListItem Value="1"></asp:ListItem>

    <asp:ListItem Value="0"></asp:ListItem>

</asp:ListBox>

以上代碼中的ListItem它的特點是直接把ListItem單項放到ListBox的標記內部而沒有增加一個類似<Items>的標記在ListItem的外面另外InnerDefaultProperty在一個控件類中只能設置一個複雜屬性InnerProperty可以設置任意多個複雜屬性

一般情況下會把最重要的一個集合屬性設置爲InnerDefaultProperty枚舉標記類型

Ø PersistenceMode.EncodedInnerDefaultProperty

上面的代碼中ListItem.Text屬性(值爲“男”或“女”)除了標記方式與InnerDefaultProperty有點區別外其內容會進行HTML編碼比如把HTML標記<div>編碼爲&lt;div&gt;,即要保證其內容不能再存儲HTML標記和子標籤。

2DesignerSerializationVisibility特性

此特性表示指定在設計時序列化複雜對象的方式它有三個枚舉類型

Ø DesignerSerializationVisibility.Visible

表示代碼生成器要對屬性本身生成代碼。

Ø DesignerSerializationVisibility.Hidden

表示代碼生成器不對屬性生成代碼即在屬性窗口設置的值不會被代碼生成器生成到*.aspx*.ascx文件中

Ø DesignerSerializationVisibility.Content

表示代碼生成器生成複雜屬性內容的代碼而不是其本身比如在上面的People類中我們實際要操作的數據是People類下面的 Name/Sex/Age屬性即我們在屬性窗口中修改了Name/Sex/Age的值後會僅把這些值通過代碼生成器映射到*.aspx*.axcx頁面中

果沒有設置DesignerSerializationVisibility特性則其值默認爲DesignerSerialization Visibility. Visible;一般複雜屬性都要設置爲DesignerSerializationVisibility.Content

理解了PersistenceMode DesignerSerializationVisibility兩個特性的用法我們再繼續完成上面進行中的代碼部分爲屬性Person增加了這兩個特性後,再打開Person類定義代碼,爲該類增加一個類特性TypeConverter,如下所示:

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. [TypeConverter(typeof(ExpandableObjectConverter))]  
  6. public class Person
  7. {
  8.     … …
  9. }

TypeConverter

特性指定轉換器的類型,ExpandableObjectConverter表示可擴展對象與其他類型的轉換器類,該類爲系統提供。另外,也可以自己定義轉換器規則類,本章後面會有專門介紹。

增加以上屬性之後,編譯控件再查看屬性窗口,就可以在屬性窗口中進行設置Person屬性的值了,如圖4-5所示。

 

 

4-5  設置Person屬性值

 

在屬性瀏覽器中爲Person設置值,後切換到代碼視圖,會看到如下標記:

<cc1:controlproperty id="ControlProperty1" runat="server" Person-Age="26" Person-Name="King Zheng"></cc1:controlproperty>

到此我們就實現以上功能:連字符複雜屬性的標記形式。

4.4.2.2  內部嵌套複雜屬性標記

連字符複雜屬性標記雖然能夠實現複雜屬性,且代碼生成器能夠進行正/反向轉換,但它把所有複雜屬性都擠到主控件的屬性上,顯示比較臃腫,設想一下,如果GridView把它的<HeadStyle><Rowstyle>等屬性都擠到GridView主標記內部,會是什麼樣子,爲了解決這個問題,下面我們就實現一個類似以下代碼中的RowStyle標記形式的複雜屬性。

<asp:GridView ID="GridView1" runat="server">

    <RowStyle BackColor="#EFF3FB" />

</asp:GridView>

u  在控件所在項目中增加一個類文件 RowStyle.cs,定義其內容如下:

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. [TypeConverter(typeof(ExpandableObjectConverter))]  
  6. public class RowStyle    //TableItemStyle: Table的Row和Cell樣式基類,也可以直
  7.                              //接繼承此類
  8. {
  9.     private Color bcBackColor;        
  10.     [NotifyParentProperty(true)]
  11.     public Color BackColor
  12.     {
  13.         get { return bcBackColor; }
  14.         set { bcBackColor = value; }
  15.     }
  16. }

注意不要漏掉

TypeConverterNotifyParentProperty,其用途在前面中已經講過了。

再在主控件中增加一個RowStyle類型的屬性,屬性名爲RowStyle,增加後的屬性代碼片段如下所示:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. [PersistenceMode(PersistenceMode.InnerProperty)]
  6. [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
  7. [NotifyParentProperty(true)]
  8. [Category("複雜屬性")]
  9. [Description("複雜屬性——內部嵌套形式")]
  10. public RowStyle RowStyle
  11. {
  12.     get
  13.     {
  14.         if (rsRowStyle == null)
  15.         {
  16.             rsRowStyle = new RowStyle();
  17.         }
  18.         return rsRowStyle; 
  19.     }
  20. }

選擇

PersistenceMode特性的InnerProperty枚舉項,表示生成嵌套標記;至於DesignerSerializationVisibility特性,依然選擇Content枚舉值,這裏也是對複雜屬性RowStyle的類對象子屬性進行序列化。如還不清楚這兩個屬性的使用,請到前面的4.1.1節回顧一下。

然後,在主控件加兩個類特性,如下所示:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. [ParseChildren(true), PersistChildren(false)] //繼承WebControl時可以省略此行
  6. public class ControlProperty : WebControl
  7. {
  8.     … …
  9. }

PerseChildren

特性指定頁面分析器把控件標記中的內容解析爲屬性還是子控件,該屬性值設置爲true,則表示解析爲屬性。PersistChildren指定設計器把控件標記中的內容保存爲屬性還是子控件,該屬性值設置爲false,表示保存爲屬性。

設置瞭如上幾個重要特性後,編譯控件,在設計器屬性窗口中設置RowStyle屬性值,並切換到代碼視圖,會看到RowStyle的標記形式如下所示:

<cc1:controlproperty id="ControlProperty1" runat="server">

    <RowStyle BackColor="CornflowerBlue" />

</cc1:controlproperty>

只要實現RowStyle複雜類型,那麼類似GridView的其他嵌套屬性如:<HeaderStyle><FooterStyle><SelectedRowStyle><EditRowStyle>等實現方法用同樣方式也可以實現。

在嵌套標記屬性比較多的情況下,這些屬性看起來效果比上節講過的連字符複雜屬性標記要清晰許多。

另外,還可以按上面所說的步驟對集合類型生成類似的內部嵌套默認屬性,如:

<asp:DropDownList id="DropDownList1" runat="server"  >

    <Items>

        <asp:ListItem Value="red">紅色</asp:ListItem>

        <asp:ListItem Value="green">綠色</asp:ListItem>

    <Items>

</asp:DropDownList>

基於實現原理與RowStyle類似且本章後面有專門章節詳細探討集合屬性,這裏不作代碼示範集合屬性也是非常重要和常用的複雜屬性類型

4.4.2.3  內部嵌套默認複雜屬性標記

內部嵌套默認屬性與內部嵌套屬性非常類似,一般用於設置某個控件的集合屬性。比如標準服務器控件中的DropDownList控件中的屬性均爲內部嵌套默認屬性,代碼如下:

<asp:DropDownList id="DropDownList1" runat="server" >

    <asp:ListItem Value="red">紅色</asp:ListItem>

    <asp:ListItem Value="green">綠色</asp:ListItem>

</asp:DropDownList>

內部嵌套默認屬性的ListItem標記外部沒有像內部集合屬性一樣嵌套在<Items></Items>中,然後把<Items>嵌套在主控件標記中,而是直接把<asp:listItem></asp:listItem>嵌套在主控件標記內部,一般當該控件只有一個集合複雜屬性的情況時使用;而當一個集合中有多個集合或複雜屬性時一般設置爲內部嵌套複雜屬性標記形式。

爲主控件增加集合屬性之前,先要建立兩個類:

Ø ListItem類:集合中的單項定義類。

Ø Items類:集合類,提供ListItem的容器以及一些常用的添加/刪除等子項方法。

1ListItem類完整代碼

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. [ToolboxItem(false)]
  6. [TypeConverter(typeof(ExpandableObjectConverter))]    
  7. public class ListItem : Control
  8. {        
  9.     private string _Text;
  10.     private string _Value;        
  11.     public ListItem()
  12.     { }
  13.     public ListItem(string strText,string strValue)
  14.     {            
  15.         this._Text = strText;
  16.         this._Value = strValue;            
  17.     }        
  18.     /// <summary>
  19.     /// 文本屬性
  20.     /// </summary>     
  21.     [NotifyParentProperty(true)]           
  22.     public string Text
  23.     {
  24.         get { return _Text; }
  25.         set { _Text = value; }
  26.     }
  27.     /// <summary>
  28.     /// 值屬性
  29.     /// </summary>     
  30.     [NotifyParentProperty(true)]
  31.     public string Value
  32.     {
  33.         get { return _Value; }
  34.         set { _Value = value; }
  35.     }
  36. }

此子項類的代碼比較簡單

唯一要說明的是上面的[ToolBoxItem(false)]表示不在IDE工具箱的控件集合中顯示很顯然這不是一個控件不能在工具箱集合列表中顯示一般除了主控件之外的其餘類都要把ToolBoxItem類元數據特性置爲false否則當使用者拖一個不完整的控件標記到頁面上時可能出現控件不能使用的情況

2Items類的完整代碼

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. /// <summary>
  6. /// 菜單實現類[實用泛型集合]    
  7. /// </summary>
  8. [
  9. ToolboxItem(false),
  10. ParseChildren(true)
  11. ]
  12. public class Items : List<ListItem>
  13. {
  14.     #region 定義構造函數
  15.     public Items()
  16.         : base()
  17.     {
  18.     }
  19.     #endregion
  20.     /// <summary>
  21.     /// 得到集合元素的個數
  22.     /// </summary>
  23.     public new int Count
  24.     {
  25.         get
  26.         {
  27.             return base.Count;
  28.         }
  29.     }
  30.     /// <summary>
  31.     /// 表示集合是否爲只讀
  32.     /// </summary>
  33.     public bool IsReadOnly
  34.     {
  35.         get
  36.         {
  37.             return false;
  38.         }
  39.     }
  40.     /// <summary>
  41.     /// 添加對象到集合
  42.     /// </summary>
  43.     /// <param name="item"></param>
  44.     public new void Add(ListItem item)
  45.     {
  46.         base.Add(item);
  47.     }
  48.     /// <summary>
  49.     /// 清空集合
  50.     /// </summary>
  51.     public new void Clear()
  52.     {
  53.         base.Clear();
  54.     }
  55.     /// <summary>
  56.     /// 判斷集合中是否包含元素
  57.     /// </summary>
  58.     /// <param name="item"></param>
  59.     /// <returns></returns>
  60.     public new bool Contains(ListItem item)
  61.     {
  62.         return base.Contains(item);
  63.     }
  64.     /// <summary>
  65.     /// 移除一個對象
  66.     /// </summary>
  67.     /// <param name="item"></param>
  68.     /// <returns></returns>
  69.     public new bool Remove(ListItem item)
  70.     {
  71.         return base.Remove(item);
  72.     }
  73.     /// <summary>
  74.     /// 設置或取得集合索引項
  75.     /// </summary>
  76.     /// <param name="index"></param>
  77.     /// <returns></returns>
  78.     public new ListItem this[int index]
  79.     {
  80.         get
  81.         {
  82.             return base[index];
  83.         }
  84.         set
  85.         {
  86.             base[index] = value;
  87.         }
  88.     }          
  89. }

這裏的

Items採用泛型集合,繼承list<T>強類型集合作爲基類,此外在System.Collections. Generic命名空間中還有其他一些強類型集合。

增加完上面兩個類後,實現內部默認集合屬性,還需要設置兩個類設計特性:一是在控件類前設置ParseChildren(true,“默認屬性名稱”),指定主控件中的屬性名稱表示是屬性,而不是子控件,ParseChildren4.3節已經做了講解;二是設置[PersistChildren(false)]類特性表示要把集合標記作爲屬性方式保持和進行序列化

在主控件的集合屬性前要設置如下三個特性:

[PersistenceMode(PersistenceMode.InnerDefaultProperty)]

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]

[NotifyParentProperty(true)]

第一個特性,指定集合屬性爲內部默認屬性;第二個特性,指定要序列化的是集合屬性的內容,而不是集合屬性本身;第三個特性,指定集合屬性的子屬性修改時會通知父屬性。

新建一個Web自定義控件文件,並按以上所述進行設置,控件主類核心代碼如下:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. /// <summary>
  6. /// 控件主類[複雜屬性-內部默認屬性]
  7. /// </summary>
  8. [DefaultProperty("Text")]
  9. [ToolboxData("<{0}:CollectionControlProperty runat=server></{0}:Collection ControlProperty>")]
  10. [PersistChildren(false)]
  11. [ParseChildren(true"Items")] 
  12. public class CollectionControlProperty : WebControl
  13. {
  14.     private Items items;
  15.     [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
  16.     [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
  17.     [NotifyParentProperty(true)]
  18.     [TypeConverter(typeof(CollectionConverter))]
  19.     [Category("複雜屬性")]
  20.     [Description("複雜屬性——內部默認嵌套形式")]
  21.     public Items Items
  22.     {
  23.         get
  24.         {
  25.             if (this.items == null)
  26.             {
  27.                 this.items = new Items();
  28.             }
  29.             return this.items;
  30.         }
  31.     }
  32.     … …
  33. }

除了設置上面所提到的屬性外,本集合類還多了一個特性

[TypeConverter(typeof(collection Converter)]。此特性指定本集合屬性轉換到代碼視圖時採用系統默認的集合轉換器。針對常用的類型,系統提供了一組默認的轉換器,後面章節會介紹怎樣創建自定義複雜類型的類型轉換器。

經過以上設置後,在頁面上拖動一個控件,並在屬性窗口中增加填加幾個子項,如圖4-6所示。

 

4-6  集合編輯器

 

設置完後,回到源代碼視圖,會看到剛纔設置好的幾個子項:

<cc1:CollectionControlProperty ID="CollectionControlProperty1" runat="server">

    <cc1:ListItem ID="ListItem1" runat="server" Text="紅色" Value="red">

    </cc1:ListItem>

    <cc1:ListItem ID="ListItem2" runat="server" Text="藍色" Value="blue">

    </cc1:ListItem>

    <cc1:ListItem ID="ListItem3" runat="server" Text="綠色" Value="green">

    </cc1:ListItem>

</cc1:CollectionControlProperty>

本節主要是完成一個複雜集合屬性,並把集合屬性設置爲默認屬性。本節示例控件的所有源代碼請參閱隨書光盤中的內容。

4.4.2.4  內部嵌套編碼默認屬性

請看下面這段我們經常使用的代碼:

<asp:DropDownList id="DropDownList1" runat="server" >

    <asp:ListItem Value="red">紅色</asp:ListItem>

    <asp:ListItem Value="green">綠色</asp:ListItem>

</asp:DropDownList>

細心的讀者可能看到,表示Text(“紅色”位置的屬性)的屬性不像Value屬性是附屬於ListItem標記,而是在兩個<asp:ListItem></asp:ListItem>標記之間呈現。這樣的標記主要用於顯示非HTML標記或非子控件的純文本,本節主要完成這種格式屬性的實現。

爲了保留前面控件已有的功能,重新定義兩個類Item2ListItem2

1Items集合類

代碼如下:

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. /// <summary>
  6. /// 菜單實現類[實用泛型集合]
  7. /// </summary>
  8. [
  9. ToolboxItem(false),
  10. ParseChildren(true)
  11. ]
  12. public class Items2 : List<ListItem2>
  13. {
  14.     //省略,此集合類內部代碼與Items完全相同
  15. }
2
ListItem2子項類

代碼如下:

 

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. /// <summary>
  6. /// 子項類
  7. /// </summary>
  8. [ToolboxItem(false)]
  9. [TypeConverter(typeof(ExpandableObjectConverter))]    
  10. [ParseChildren(true"Text")]  
  11. [PersistChildren(false)]       
  12. public class ListItem2 : Control  
  13. {        
  14.     private string _Text;
  15.     private string _Value;        
  16.     public ListItem2()
  17.     { }
  18.     public ListItem2(string strText, string strValue)
  19.     {            
  20.         this._Text = strText;
  21.         this._Value = strValue;            
  22.     }        
  23.     /// <summary>
  24.     /// 文本屬性
  25.     /// </summary>     
  26.     [NotifyParentProperty(true)]
  27.     [PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]
  28.     [Description("複雜屬性——內部默認嵌套形式")]
  29.     public string Text
  30.     {
  31.         get { return _Text; }
  32.         set { _Text = value; }
  33.     }
  34.     /// <summary>
  35.     /// 值屬性
  36.     /// </summary>     
  37.     [NotifyParentProperty(true)]
  38.     public string Value
  39.     {
  40.         get { return _Value; }
  41.         set { _Value = value; }
  42.     }
  43. }

此子項類要做一些特性設置,類元特性需要增加兩個特殊的特性:

Ø [ParseChildren(true, "Text")] 

Ø [PersistChildren(false)]       

第一個特性表示將子Text元素作爲服務器控件的屬性分析;第二個特性表示將該控件的屬性作爲嵌套元素保持。

另外,還要注意要對作爲編碼內部屬性的屬性進行設置,比如這裏爲Text屬性加上:

[PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)] 

u  進行如上設置後,增加一個主控件文件,並進行如下所示設置:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. [DefaultProperty("Text")]
  6. [ToolboxData("<{0}:EncodedInnerDefaultPropertyControl runat=server></{0}: EncodedInnerDefaultPropertyControl>")]
  7. [PersistChildren(false)]
  8. [ParseChildren(true"Items")]
  9. public class EncodedInnerDefaultPropertyControl : WebControl
  10. {
  11.     public EncodedInnerDefaultPropertyControl()
  12.     { 
  13.     }
  14.     private Items2 items;
  15.     [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
  16.     [DesignerSerializationVisibility(DesignerSerializationVisibility. Content)]
  17.     [NotifyParentProperty(true)]
  18.     [TypeConverter(typeof(CollectionConverter))]
  19.     [Category("複雜屬性")]
  20.     [Description("複雜屬性——內部默認嵌套形式")]
  21.     public Items2 Items
  22.     {
  23.         get
  24.         {
  25.             if (this.items == null)
  26.             {
  27.                 this.items = new Items2();
  28.             }
  29.             return this.items;
  30.         }
  31.     }
  32.     … …
  33. }

上面主控件類與

4.4.2.3中主控件類設置完全相同,這裏就不再作說明。

設置完成後編譯控件庫,拖動此控件到頁面中,可以看到在屬性窗口增加了幾個集合項,如圖4-7所示。

 

4-7  屬性窗口

集合設置界面與4.4.2.3節中的完全相同,但切換到代碼視圖界面,會發現序列化後的代碼變化了,如下所示:

<cc1:EncodedInnerDefaultPropertyControl ID="EncodedInnerDefaultProperty Control1" runat="server" Items-Capacity="4">    

    <cc2:ListItem2 ID="ListItem22" runat="server" Value="red">紅色</cc2:ListItem2>

    <cc2:ListItem2 ID="ListItem23" runat="server" Value="blue">藍色</cc2:ListItem2>

</cc1:EncodedInnerDefaultPropertyControl>

以看到Text屬性已經不再作爲ListItem的直接屬性,而是嵌套在<ListItem2> </ListItem2>之間。

本節主要說明控件內部嵌套編碼默認屬性格式的實現。在實現時需要注意的是,對ListItem2子項類進行一些元數據特性設置,因爲Text是屬於ListItem2類的屬性。

4.4.3  深入研究—複雜屬性分析器

4.4.3.1  使用AddParsedSubObject控制複雜內容(子控件)

4.4.2節已經把各種各樣的複雜屬性類型都實現了,這些都是在實際開發中常用的屬性格式,能夠滿足絕大多數開發需要。

這一節講解稍微複雜一點的屬性格式。一般在一個控件中只能設置單個的屬性爲內部默認屬性,比如4.4.2.3節中實現的屬性:

<cc1:CollectionControlProperty ID="CollectionControlProperty1" runat="server">

    <cc1:ListItem ID="ListItem1" runat="server" Text="紅色" Value="red">

    </cc1:ListItem>

    <cc1:ListItem ID="ListItem2" runat="server" Text="藍色" Value="blue">

    </cc1:ListItem>

    <cc1:ListItem ID="ListItem3" runat="server" Text="綠色" Value="green">

    </cc1:ListItem>      

</cc1:CollectionControlProperty>  

其中Items屬性設置成了內部默認屬性,如果控件中需要多個內部默認屬性的格式,默認分析器對此是不支持的。如果強行設置了兩個默認屬性格式的屬性,控件可以編譯通過,但在頁面的屬性窗口設置多個複雜屬性後,進行代碼與設計器視圖切換時系統會報以下錯誤:

 

 

這說明不能利用默認的解析器分析多個設置了默認屬性格式的子標記。爲了解決這個問題,其中一種方法可以重寫AddParsedSubObject來定製自己的頁面解析子控件方法。主控件核心源代碼如下:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. /// <summary>
  6. /// 本控件包含三個集合複雜屬性: 兩個內部默認嵌套形式; 一個內部嵌套形式.
  7. /// </summary>
  8. [ToolboxData("<{0}:MultiCollectionControlProperty runat=server></{0}:Multi CollectionControlProperty>")]
  9. [ParseChildren(false)]     
  10. public class MultiCollectionControlProperty : WebControl
  11. {
  12.     private Items items;
  13.     [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
  14.     [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
  15.     [NotifyParentProperty(true)]
  16.     [TypeConverter(typeof(CollectionConverter))]
  17.     [Category("複雜屬性")]
  18.     [Description("複雜屬性——內部默認嵌套形式")]
  19.     public Items Items
  20.     {
  21.         get
  22.         {
  23.             if (this.items == null)
  24.             {
  25.                 this.items = new Items();
  26.             }
  27.             return this.items;
  28.         }
  29.     }
  30.     private Items2 items2;
  31.     [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
  32.     [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
  33.     [NotifyParentProperty(true)]
  34.     [TypeConverter(typeof(CollectionConverter))]
  35.     [Category("複雜屬性")]
  36.     [Description("複雜屬性——內部默認嵌套形式")]
  37.     public Items2 Items2
  38.     {
  39.         get
  40.         {
  41.             if (this.items2 == null)
  42.             {
  43.                 this.items2 = new Items2();
  44.             }
  45.             return this.items2;
  46.         }
  47.     }
  48.     private Items3 items3;
  49.     [PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]
  50.     [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
  51.     [NotifyParentProperty(true)]
  52.     [TypeConverter(typeof(CollectionConverter))]
  53.     [Category("複雜屬性")]
  54.     [Description("複雜屬性——內部編碼嵌套形式")]
  55.     public Items3 Items3
  56.     {
  57.         get
  58.         {
  59.             if (this.items3 == null)
  60.             {
  61.                 this.items3 = new Items3();
  62.             }
  63.             return this.items3;
  64.         }
  65.     }
  66.     protected override void AddParsedSubObject(object obj)
  67.     {            
  68.         if (obj is ListItem)
  69.         {
  70.             if (this.items == null)
  71.             {
  72.                 this.items = new Items();
  73.             }
  74.             this.items.Add((ListItem)obj);
  75.         }
  76.         if (obj is ListItem2)
  77.         {
  78.             if (this.items2 == null)
  79.             {
  80.                 this.items2 = new Items2();
  81.             }
  82.             this.items2.Add((ListItem2)obj);
  83.         }
  84.         if (obj is ListItem3)
  85.         {
  86.             if (this.items3 == null)
  87.             {
  88.                 this.items3 = new Items3();
  89.             }
  90.             this.items3.Add((ListItem3)obj);
  91.         }
  92.     }
  93. }

本主控件類包含三個集合複雜屬性

兩個內部默認嵌套形式屬性(ItemsItems2);一個內部編碼嵌套形式屬性(Items3)。這三個集合類的子項也是使用前面示例中的子項類,其中ListItem3代碼省略它與前面的ListItem1類的內部代碼完全一樣只是類名不同

主類MultiCollectionControlProperty前面要加個很重要的元特性 [ParseChildren(false)],指定頁解析器把嵌套的內容作爲子控件解析。

另外,還可以重寫AddParseSubObject,定製自定義的頁面解析實現,頁面在設計模式下解析(從代碼視圖切換到設計視圖)時,每檢測到一個內部子控件都會觸發此方法,此方法的參數object就是當前內部子控件標記生成的對象。方法體中的三個if語句分別判斷當前對象是什麼類型,如果是ListItem類型就把它添加到相關的類集合中,如上面代碼把ListItem類型的對象增加到了Items集合中。只有這樣,我們在設計視圖中查看屬性窗口中值時,當前集合纔有值顯示在屬性集合編輯器中(彈出窗口編輯器)。在增加一個子項到集合中時,還要注意第一次往集合中增加子項時,集合值爲null,要先爲當前集合生成對象實例。

事實上控件中的嵌套標記,不僅可以內置集合類型子標記,還可以增加任意類型的標記,只要子標記具有前綴標誌和runat屬性即可;如果沒有前綴和runat屬性,系統也不會報錯,只是頁面解析器會把不具有前綴和runat屬性的整個塊標記都用LiteralControl包裝後返回LiteralControl的對象(返回給AddParseSubObject的參數obj),而不管此塊有多大。

編譯此控件後拖一個控件到頁面中會在屬性窗口中看到三個並列的集合屬性如圖4-8所示。

 

4-8  屬性窗口

 

爲三個集合屬性分別設置幾個子項切換到源代碼視圖會看到如下源代碼:

<cc1:MultiCollectionControlProperty ID="MultiCollectionControlProperty1" runat="server">

    <cc2:ListItem runat="server" Text="紅色" Value="red" ID="ListItem1"> </cc2:ListItem>

    <cc2:ListItem runat="server" Text="綠色" Value="green" ID="ListItem2"> </cc2:ListItem>

    <cc2:ListItem2 runat="server" Value="blue" ID="ListItem21">藍色</cc2:ListItem2>

    <cc2:ListItem2 runat="server" Value="gray" ID="ListItem22">灰色</cc2:ListItem2>

    <cc2:ListItem3 runat="server" Text="黃色" Value="yellow" ID="ListItem31"> </cc2:ListItem3>

    <cc2:ListItem3 runat="server" Text="淡藍" Value="lightblue" ID="ListItem32"> </cc2:ListItem3>

</cc1:MultiCollectionControlProperty>

可以看到ListItemListItem2ListItem3非常有序地嵌套在主控件內部從而實現了主控件內部多個複雜默認屬性嵌套功能

AddParseSubObject方法固然能夠幫助我們實現控件內部多個複雜默認屬性的嵌套功能但它也有侷限性就是前面提到過的子標記必須是子控件形式標記子標記要具有前綴標誌和runat屬性,否則整個非子控件類型塊標記都用LiteralControl包裝後返回LiteralControl的對象(返回給AddParseSubObject的參數obj而不管此塊有多大

以上是通過重寫AddParseSubObject方法實現頁面解析功能;另外,.NET Framework爲控件設計模式支持專門提供了一個控件構造類:System.Web.UI.ControlBuilder,通過繼承此類也可以實現定製頁面解析,而且更靈活,後面會專門對比進行介紹。

4.4.3.2  使用ControlBuilder解析複雜內容

通過System.Web.UI.ControlBuilder類定製頁面解析邏輯,可以定製任意類型的標記,而不像重寫AddParseSubObject方法那樣限定子標記必須是子控件,且必須有前綴和runat屬性,下面直接通過一個例子來說明一下此類的用法。

首先建立兩個文件ScriptItem.csScriptItemCollection.cs,分別定義ScriptItem類和ScriptItemCollection類。其中,ScriptItem類主要存儲用戶自定義的客戶端腳本命令(JavaScript塊),ScriptItemCollection可以定義一個集合容器,每個項都是一個 ScriptItem項。與前面講的集合實現非常類似。這兩個類的完整代碼如下:

1ScriptItem

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. private string _Text;        
  6. [DefaultValue("")]
  7. [Editor("System.ComponentModel.Design.MultilineStringEditor,System.Design"typeof(UITypeEditor))]
  8. [PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]       
  9. [NotifyParentProperty(true)]
  10. /// <summary>
  11. /// JavaScript腳本塊
  12. /// </summary>        
  13. public string Text
  14. {
  15.     get
  16.     {
  17.         return _Text;
  18.     }
  19.     set
  20.     {
  21.         _Text = value;
  22.     }
  23. }

該類中的

Text就是用於存儲用戶定義的腳本塊;Editor元數據特性指定在屬性窗口中Text屬性的編輯器是一個下拉塊輸入編輯器,關於屬性編輯器下一節會詳細講解,這裏僅知道它的功能即可。

需要注意的是,在上一節使用AddParsedSubObject實現頁面解析子控件時,要嵌套的三個集合的子標記:ListItemListItem2ListItem3都繼承了Control基類,目的是把這些子標記作爲子控件(也就具有了前綴和runat屬性),而這裏的ScriptItem沒有繼承任何基類,這樣就避免了繼承一些基類中的冗餘屬性和方法。

2ScriptItemCollecton

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

 

[ToolboxItem(false)]   

public class ScriptItemCollection : List<ScriptItem>

{

    public ScriptItemCollection() : base() { }

    public ScriptItemCollection(int capacity) : base(capacity) { }

    public ScriptItemCollection(IEnumerable<ScriptItem> collection):base (collection) { }

}

 

u  定義這兩個類之後,實現我們自己的ControlBuilder類,可以直接繼承該類並實現自己的方法,已經預先實現好了的構造器類代碼如下所示:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. public class ScriptItemBuilder : ControlBuilder
  6. {       
  7.     public override Type GetChildControlType(string tagName, IDictionary attributes)
  8.     {
  9.         if (string.Compare(tagName.ToLower(), "scriptitem"false, CultureInfo. InvariantCulture) == 0)
  10.         {
  11.             return typeof(ScriptItem);
  12.         }
  13.         return null;
  14.     }
  15.     public override bool AllowWhitespaceLiterals()
  16.     {
  17.         return false;            
  18.     }        
  19. }

在該類中要做的最重要的事情是重寫方法

GetChildControlType,在頁面解析器分析主控件的每個子標記時,都會調用一次此方法。

該方法的第一個參數表示當前正在解析的控件標記字符串,第二個參數表示標記上所有特性的字典集合。方法體中的if語句的功能是,假如當前解析的標記是“scriptitem”(就是後面定義到主控件的集合屬性名稱),則返回ScriptItem類的類型,且通過ToLower()方法實現不區分大小寫。需要注意的是,這裏我們做的工作非常簡單,只是匹配相應的字符串標記並返回一個類型。而AddParsedSubObject則要自己處理當前對象的值。還有個重寫方法AllowWhitespaceLiterals用於指定控件的開始標記和結束標記之間是否允許存在空白。

定義完自己的構造器後,通過爲主控件增加如下元數據特性,指定主控件的解析器:

[ControlBuilder(typeof(ScriptItemBuilder))]

u  設置完後,完整的主控件類源代碼如下:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. [ToolboxData("<{0}:ControlBuilderControl runat=server></{0}:ControlBuilder Control>")]
  6. [ParseChildren(true"ScriptItems")]
  7. [ControlBuilder(typeof(ScriptItemBuilder))]
  8. public class ControlBuilderControl : WebControl
  9. {
  10.     private ScriptItemCollection _ScriptItems = new ScriptItemCollection();
  11.     /// <summary>
  12.     /// 腳本命令集合屬性
  13.     /// </summary>
  14.     [PersistenceMode(PersistenceMode.InnerProperty)]
  15.     [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
  16.     [Description("工具按鈕集設置")]
  17.     [Category("工具按鈕——屬性設置")]
  18.     public ScriptItemCollection ScriptItems
  19.     {
  20.         get
  21.         {
  22.             if (_ScriptItems == null)
  23.             {
  24.                 _ScriptItems = new ScriptItemCollection();
  25.             }
  26.             return _ScriptItems;
  27.         }
  28.     }
  29.     //… …  
  30. }

整個主控件只包括一個集合屬性。需要注意的是我們把這個屬性定義成內部嵌套標記形式,即我們在

<ScriptItem>標記外面又嵌套了一個<ScriptItems>,把ScriptItems作爲主控件的直接嵌套標記。

編譯此控件後往頁面中添加一個控件,並在屬性窗口中增加命令項,然後切換到代碼視圖會看到如下格式的標記代碼:

<cc1:controlbuildercontrol id="ControlBuilderControl1" runat="server">

   <ScriptItems>

       <cc1:ScriptItem>alert('Hello King');</cc1:ScriptItem>

       <cc1:ScriptItem>alert('Hello Rose');</cc1:ScriptItem>         

       <cc1:ScriptItem>alert('Hello James');</cc1:ScriptItem>               

   </ScriptItems>          

</cc1:controlbuildercontrol>

上面生成了一個具有ScriptItem集合屬性的標記集合。與上節我們使用的AddParsedSub Object相比,嵌套標記中多了一個<ScriptItems>內部嵌套標記,且ScriptItem沒有前綴和runat屬性,如果使用AddParsedSubObject,會把整個<ScriptItems>塊(包括其中的ScriptItem一塊作爲文本LiteralControl返回,這顯然不符合我們的要求;另外,這裏的<ScriptItem>雖然具有前綴,但它也不具有runat的屬性,但也能夠正確被頁面解析器進行正反向解析

到目前爲止,已經分別使用重載基類AddParsedSubObject方法和繼承ControlBuilder的構造實現類實現了自定義的頁面解析功能。那麼使用它們兩個的場景是怎樣呢?其實在講解它們的過程中筆者已經作了不少比較,再總結一下:

1)在絕大多數情況下,如果頁面中只要設置一個內部嵌套標記屬性或不需要設置內部嵌套標記屬性,則不需要重寫AddParsedSubObject和實現ControlBuilder的繼承類。這兩種方式主要是在實現頁面中有多個默認嵌套屬性時使用。

2AddParsedSubObject實現比較簡單,僅實現一個方法,一般用於複雜屬性單一且比較少的情況。

3)實ControlBuilder的定製構造器類比重載AddParsedSubObject要麻煩些,但功能更強,能處理更靈活的嵌套標記。AddParsedSubObject最大的限制是它的內部必須是子控件類型。

4)兩種方式都是ASP.NET提供的兩種解決方案,都有它們使用的場景,可以根據自己喜好選擇,當習慣使用構造器後,會發現構造器功能更強大、更靈活,用起來更順手,它可以完全替代重載AddParsedSubObject方式。

到現在爲止基本上已經把我們見過的所有的屬性標記格式都實現了一遍,4.4.3.2節也把平時很少用到的定製頁面解析器功能詳細地講解了一下,其中有些標記在平常開發中比較少用到。本章可以作爲查找手冊使用,什麼時候用到這些內容,什麼時候過來查即可。下一節會有更精彩的內容。

4.5  深入研究—定製自己的屬性編輯器

對於控件的所有屬性,如果都提供非常友好的屬性編輯器,使用者使用起來會更加方便。本節主旨就是講解一下控件屬性編輯器,4.5.1節提供一些系統通用編輯器;在複雜屬性中,集合屬性是最重要的最常用的屬性,4.5.2節將主要講解怎樣定製複雜集合類型編輯器以及一些特殊比較酷的編輯器類型。

這些設計器的執行主要是在設計模式下,直接與IDE交互,在編程時可以直接使用System.Windows命名空間開頭的一些命名空間下的類。這裏首先加入幾個本節需要使用到的引用。右擊控件庫工程,選擇“添加引用”命令,如圖4-9所示。

選擇“添加引用”命令後會打開“添加引用”對話框,如圖4-10所示。

     

 

4-9  添加引用                                          

 

4-10 “添加引用”對話框

 

在對話框中找到以下三個引用程序集:

1System.Designer

2System.Drawing.Design

3System.Windows.Forms

單擊“確定”按鈕,這樣在需要使用的地方打開程序集中的命名空間,就可以使用程序集中的系統類了。

4.5.1  系統屬性編輯器

很有必要用一小節講解一下系統提供的一些編輯器。讀者對這些編輯器可能都比較熟悉,但它們是怎麼使用的呢?其實使用都很簡單,僅在每個需要配置的屬性前面指定一個標誌某種屬性編輯器的元數據特性即可。下面就分別介紹一下它們。

4.5.1.1  多行下拉文本屬性編輯器

1.配置方式

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

[Editor("System.ComponentModel.Design.MultilineStringEditor,System.Design", typeof(UITypeEditor))]

public string TxtEditor

{

    //... ...

}

Editor特性就是指定屬性編輯器類型,後面的幾種系統屬性編輯器類型也是如此。

2.屬性瀏覽器中的效果(如圖4-11所示)

 

4-11  多行下拉文本屬性編輯器

 

4.5.1.2  色值選擇屬性編輯器

1.配置方式

[Editor("System.ComponentModel.Design.ColorEditor,System.Design", typeof(UITypeEditor))]

public Color ColorEditor

{

    //... ...

}

2.屬性瀏覽器中的效果(如圖4-12所示)

 

4-12  色值選擇屬性編輯器

 

4.5.1.3  文件選擇屬性編輯器

1.配置方式

[Editor(typeof(FileNameEditor), typeof(UITypeEditor))]

public string FileName

{

    //... ...

}

2.屬性瀏覽器中的效果(如圖4-13所示)

 

4-13  文件選擇屬性編輯器

上圖即爲單擊屬性窗口中“文件名屬性”按鈕彈出的“文件選擇屬性編輯器”對話框,其實也是調用的Windows系統的“打開文件”對話框。

4.5.1.4  目錄選擇屬性編輯器

1.配置方式

[Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]

public string FolderNameEditor

{

    //... ...

}

2.屬性瀏覽器中的效果(如圖4-14所示)

 

4-14  目錄選擇屬性編輯器

 

4.5.1.5  連接字符串屬性編輯器

1.配置方式

[Editor(typeof(System.Web.UI.Design.ConnectionStringEditor), typeof(UITypeEditor))]

public string ConnectionStringEditor

{

    //... ...

}

2.屬性瀏覽器中的效果(如圖4-15所示)

 

4-15  連接字符串屬性編輯器

 

4.5.1.6  表達式綁定集合屬性編輯器

1.配置方式

[Editor(typeof(System.Web.UI.Design.ExpressionsCollectionEditor), typeof(UITypeEditor))]

public string ExpressionsCollectionEditor

{

    //... ...

}

2.屬性瀏覽器中的效果(如圖4-16所示)

 

4-16  表達式綁定集合屬性編輯器

 

在此窗口可以爲屬性指定要綁定到應用程序的配置文件、連接字符串或者資源文件。

4.5.1.7  用戶控件對話框編輯器

1.配置方式

[Editor(typeof(System.Web.UI.Design.UserControlFileEditor), typeof(UITypeEditor))]

public string UserControlFileEditor

{

    //... ...

}

2.屬性瀏覽器中的效果(如圖4-17所示)

 

4-17  用戶控件對話框編輯器

 

此窗口用於選擇當前站點下的用戶控件文件(*.ascx),且默認的可選擇路徑不像文件和目錄選擇是本計算機硬盤,而是當前站點。

主控件的完整源代碼如下:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. [ToolboxData("<{0}:EditorControl runat=server></{0}:EditorControl>")]
  6. public class EditorControl : WebControl
  7. {
  8.     string strTxtEditor;
  9.     [Category("編輯器")]       
  10.     [Description("下拉多行文本編輯器")] 
  11.     [Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design"typeof(UITypeEditor))]
  12.     public string TxtEditor
  13.     {
  14.         get
  15.         {
  16.             return strTxtEditor;
  17.         }
  18.         set
  19.         {
  20.             strTxtEditor = value;
  21.         }
  22.     }
  23.     Color cColorEditor;
  24.     [Category("編輯器")]
  25.     [Description("顏色編輯器")]
  26.     [Editor("System.ComponentModel.Design.ColorEditor,System.Design"typeof(UITypeEditor))]
  27.     public Color ColorEditor
  28.     {
  29.         get
  30.         {              
  31.             return cColorEditor;
  32.         }
  33.         set
  34.         {
  35.             cColorEditor = value;
  36.         }
  37.     }
  38.     string strFileName;
  39.     [Category("編輯器")]
  40.     [Description("文件選擇編輯器")]
  41.     [Editor(typeof(FileNameEditor), typeof(UITypeEditor))]
  42.     public string FileName
  43.     {
  44.         get
  45.         {
  46.              return strFileName;
  47.         }
  48.         set
  49.         {
  50.             strFileName = value;
  51.         }
  52.     }
  53.     string strFolderNameEditor;
  54.     [Category("編輯器")]
  55.     [Description("目錄選擇編輯器")]
  56.     [Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]
  57.     public string FolderNameEditor
  58.     {
  59.         get
  60.         {
  61.             return strFolderNameEditor;
  62.         }
  63.         set
  64.         {
  65.             strFolderNameEditor = value;
  66.         }
  67.     }
  68.     string strConnectionStringEditor;
  69.     [Category("編輯器")]
  70.     [Description("連接字符串編輯器")]
  71.     [Editor(typeof(System.Web.UI.Design.ConnectionStringEditor), typeof(UITypeEditor))]
  72.     public string ConnectionStringEditor
  73.     {
  74.         get
  75.         {
  76.             return strConnectionStringEditor;
  77.         }
  78.         set
  79.         {
  80.             strConnectionStringEditor = value;
  81.         }
  82.     }
  83.     string strExpressionsCollectionEditor;
  84.     [Category("編輯器")]
  85.     [Description("編輯表達式綁定集合的編輯器")]
  86.     [Editor(typeof(System.Web.UI.Design.ExpressionsCollectionEditor), typeof(UITypeEditor))]
  87.     public string ExpressionsCollectionEditor
  88.     {
  89.         get
  90.         {
  91.             return strExpressionsCollectionEditor;
  92.         }
  93.         set
  94.         {
  95.             strExpressionsCollectionEditor = value;
  96.         }
  97.     }
  98.     string strUserControlFileEditor;
  99.     [Category("編輯器")]
  100.     [Description("用戶控件(ascx)對話框編輯器")]
  101.     [Editor(typeof(System.Web.UI.Design.UserControlFileEditor), typeof(UITypeEditor))]
  102.     public string UserControlFileEditor
  103.     {
  104.         get
  105.         {
  106.             return strUserControlFileEditor;
  107.         }
  108.         set
  109.         {
  110.             strUserControlFileEditor = value;
  111.         }
  112.     }
  113. //... ...
  114. }

這些代碼比較簡單,就不作解釋了。以上僅列出一些可能會經常用到的屬性編輯器,系統提供的屬性編輯器不止這些,像前面講的集合也是使用系統默認的集合屬性編輯器。其他的屬性編輯器可以在使用的過程中慢慢研究。下一節通過幾個例子詳細講一下怎樣定製自己的屬性編輯器。

4.5.2  定製屬性編輯器

提供了很多屬性編輯器,能夠滿足絕大多數複雜度不是很高的控件的需要。這一節主要通過四個小例子講解怎樣定製個性化的屬性編輯器,其實只要能夠想到,我們就能夠做到。

4.5.2.1  定製多類型集合屬性編輯器

前面我們在爲控件增加集合屬性時,默認情況下會使用系統默認的集合編輯器,而我們這裏自定義的集合屬性編輯器功能更強,先看一下它實現後的效果圖,如圖4-18所示。

可以看到,該編輯器除了能夠實現基本的集合屬性編輯功能外,通過單擊“添加”按鈕右邊的下拉按鈕還可以選擇添加項的類型,即我們可以定義任意個不同類型的集合項作爲屬性集合內容,這裏我們定義兩個集合子項類別:CommonItem(子項)和CommandSeperator(分隔符),以它們作爲示例講解。下面就來看一下它的實現過程。

 

4-18  定製多類型集合屬性編輯器

 

由於涉及集合,首先還是定義幾個與集合實現相關的類。定義一個抽象類ItemBase,表示每個集合子項類的子類,任何集合子類都要繼承該類。其類代碼如下所示:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. /// <summary>
  6. /// 命令項基類
  7. /// </summary>
  8. public abstract class ItemBase
  9. {
  10.     private bool _EnableViewState = true;
  11.     public bool EnableViewState
  12.     {
  13.         get
  14.         {
  15.             return _EnableViewState;
  16.         }
  17.         set
  18.         {
  19.             _EnableViewState = value;
  20.         }
  21.     }
  22. }

這裏爲了簡化代碼,只定義了一個

EnableViewState的屬性,表示是否啓用視圖狀態,在使用時還可以繼承Control等基類,使用Control等類的類成員。這裏要注意的是此類定義成了抽象類,此類不會單獨生成實例添加到集合中,也不會被作爲一種集合子類型顯示到屬性編輯窗口中供用戶選擇,因爲ItemBase在這裏沒有表示具體的集合子項,也不具有集合子項意義。所有集合子類型最終是以它們的基類型ItemBase添加到集合中統一管理的。

下面定義一個具體的集合子項類CommonItem,表示一個按鈕類型,類結構代碼如下:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. /// <summary>
  6. ///  命令按鈕類
  7. /// </summary>    
  8. [ToolboxItem(false)]    
  9. public class CommandItem : ItemBase
  10. {
  11.     private CommandActionType _CommandActionType;
  12.     //命令按鈕文本
  13.     private string _Text = null;
  14.     //快捷鍵
  15.     private string _AccessKey = null;
  16.     //提示
  17.     private string _ToolTip = null;
  18.     //是否可用
  19.     private bool _Enable = true;        
  20.     /// <summary>
  21.     /// 默認構造方法
  22.     /// </summary>        
  23.     public CommandItem()
  24.     {
  25.     }
  26.     /// <summary>
  27.     /// 構造方法[ButtonCommand]
  28.     /// </summary>
  29.     /// <param name="bitButtonItemType"></param>        
  30.     /// <param name="strCommandText"></param>
  31.     /// <param name="strAccessKey"></param>
  32.     /// <param name="strToolTip"></param>        
  33.     public CommandItem(CommandActionType commandActionType, string strText, string strAccessKey, string strToolTip)
  34.     {
  35.         this._CommandActionType = commandActionType;
  36.         this._Text = strText;
  37.         this._AccessKey = strAccessKey;
  38.         this._ToolTip = strToolTip;            
  39.     }
  40.     /// <summary>
  41.     /// 命令按鈕類型
  42.     /// </summary>
  43.     [NotifyParentProperty(true)]
  44.     public CommandActionType CommandActionType
  45.     {
  46.         get
  47.         {
  48.             return _CommandActionType;
  49.         }
  50.         set
  51.         {
  52.             _CommandActionType = value;
  53.         }
  54.     }
  55.     /// <summary>
  56.     /// 命令按鈕文本
  57.     /// </summary>
  58.     [NotifyParentProperty(true)]
  59.     [Browsable(false)]
  60.     public string Text
  61.     {
  62.         get
  63.         {
  64.             return _Text;
  65.         }
  66.         set
  67.         {
  68.             _Text = value;
  69.         }
  70.     }
  71.     /// <summary>
  72.     /// 快捷鍵
  73.     /// </summary>
  74.     [NotifyParentProperty(true)]
  75.     [Browsable(false)]
  76.     public string AccessKey
  77.     {
  78.         get
  79.         {
  80.             return _AccessKey;
  81.         }
  82.         set
  83.         {
  84.             _AccessKey = value;
  85.         }
  86.     }
  87.     /// <summary>
  88.     /// 幫助提示文本
  89.     /// </summary>
  90.     [NotifyParentProperty(true)]
  91.     [Browsable(false)]
  92.     public string ToolTip
  93.     {
  94.         get
  95.         {
  96.             return _ToolTip;
  97.         }
  98.         set
  99.         {
  100.             _ToolTip = value;
  101.         }
  102.     }
  103.     /// <summary>
  104.     /// 是否可用
  105.     /// </summary>
  106.     [NotifyParentProperty(true)]
  107.     [Browsable(false)]
  108.     public bool Enable
  109.     {
  110.         get
  111.         {
  112.             return _Enable;
  113.         }
  114.         set
  115.         {
  116.             _Enable = value;
  117.         }
  118.     }
  119. }

類代碼很簡單,主要包括描述按鈕的一些基本信息:文本、快捷鍵、提示、可用性以及按鈕類型(新增

/保存/刪除等)。這裏僅需要注意CommandItem類繼承了上面我們定義的抽象集合子項基類ItemBase,所有類型的子項都要繼承於該類。

CommandItem的第一個屬性是枚舉類型,表示此按鈕的功能類型(新增/刪除/上一頁/下一頁等),此屬性CommandActionType對應的枚舉代碼結構如下所示:

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

 

/// <summary>

/// 命令項枚舉

/// </summary>

public enum CommandActionType

{

    //保存

    Save,

 

    //新增

    Add,

 

    //編輯

    Edit,

 

    //刪除

    Delete,

 

    //關閉

    Close

 

    //...

}

 

u  接下來再定義一個子項類型:CommandSeperator類型,表示分隔符類型,即一組按鈕與另一組按鈕之間的分隔符。比如:“首頁”、“上一頁”、“下一頁”、“末頁”,這是一組具有類似功能的一級按鈕。另一組:“新增”、“修改”、“刪除”、“查看”屬於一組功能類似按鈕,這兩組按鈕之間需要用某個分隔符分開,這樣可以使使用者更容易區分各個按鈕的功能,外觀佈局也不會顯示零亂。類代碼如下所示:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. /// <summary>
  6. /// 分隔符類
  7. /// </summary>
  8. [ToolboxItem(false)]
  9. public class CommandSeperator : ItemBase
  10. {
  11.     private Unit width;
  12.     private Unit Width
  13.     {
  14.         get
  15.         {
  16.             return width;
  17.         }
  18.         set
  19.         {
  20.             width = value;
  21.         }
  22.     }
  23.     private Unit height;
  24.     private Unit Height
  25.     {
  26.         get
  27.         {
  28.             return height;
  29.         }
  30.         set
  31.         {
  32.             height = value;
  33.         }
  34.     }
  35. }

此分隔符類僅包括兩個屬性:寬度和高度。另外,它也繼承了

ItemBase抽象類。

到現在爲止,已經定義完四個類:抽象基類(ItemBase),按鈕類(CommandItem),分隔按鈕類(CommandSeperator),以及一個功能枚舉類(CommandActionType)。然後就可以定義一個存儲以上各個按鈕類型的集合類了,類名爲CommandCollection,此集合爲強類型集合類,類代碼如下:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. /// <summary>
  6. /// 工具按鈕集合類
  7. /// </summary>
  8. [ToolboxItem(false)]
  9. [ParseChildren(true)]
  10. [Editor(typeof(CommandCollectionEditor), typeof(UITypeEditor))]
  11. public class CommandCollection : Collection<ItemBase>
  12. {
  13.     #region 定義構造函數
  14.     public CommandCollection()
  15.         : base()
  16.     {
  17.     }
  18.     #endregion
  19.     /// <summary>
  20.     /// 得到集合元素的個數
  21.     /// </summary>
  22.     public new int Count
  23.     {
  24.         get
  25.         {
  26.             return base.Count;
  27.         }
  28.     }
  29.     /// <summary>
  30.     /// 表示集合是否爲只讀
  31.     /// </summary>
  32.     public bool IsReadOnly
  33.     {
  34.         get
  35.         {
  36.             return false;
  37.         }
  38.     }
  39.     /// <summary>
  40.     /// 添加對象到集合
  41.     /// </summary>
  42.     /// <param name="item"></param>
  43.     public new void Add(ItemBase item)
  44.     {
  45.         base.Add(item);
  46.     }
  47.     /// <summary>
  48.     /// 清空集合
  49.     /// </summary>
  50.     public new void Clear()
  51.     {
  52.         base.Clear();
  53.     }
  54.     /// <summary>
  55.     /// 判斷集合中是否包含元素
  56.     /// </summary>
  57.     /// <param name="item"></param>
  58.     /// <returns></returns>
  59.     public new bool Contains(ItemBase item)
  60.     {
  61.         return base.Contains(item);
  62.     }
  63.     /// <summary>
  64.     /// 移除一個對象
  65.     /// </summary>
  66.     /// <param name="item"></param>
  67.     /// <returns></returns>
  68.     public new bool Remove(ItemBase item)
  69.     {
  70.         return base.Remove(item);
  71.     }
  72.     /// <summary>
  73.     /// 設置或取得索引項
  74.     /// </summary>
  75.     /// <param name="index"></param>
  76.     /// <returns></returns>
  77.     public new ItemBase this[int index]
  78.     {
  79.         get
  80.         {
  81.             return base[index];
  82.         }
  83.         set
  84.         {
  85.             base[index] = value;
  86.         }
  87.     }        
  88. }

該集合類繼承

Collection<ItemBase>類,表示強類型集合,且每個子項的類型爲ItemBase,從這裏可以想到我們上面定義的兩個子項類CommandItemCommandSeperator都要繼承於ItemBase的原因了。[ParseChilderen(true)]表示把當前屬性作爲主控件的屬性(而非子控件)進行解析。

本節的重點,也是最重要的一個屬性[Editor(typeof(CommandCollectionEditor)typeof (UITypeEditor))]表示指定此集合類的集合編輯器爲CommandCollectionEditor,即在主控件中凡是定義爲CommandCollection類的屬性都會把CommandCollectionEditor作爲它的編輯器。下面詳細介紹一下編輯器類是怎麼使用的。還是先看一下 CommandCollectionEditor編輯器類的源代碼:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. /// <summary>
  6. /// 集合屬性編輯器
  7. /// </summary>
  8. public class CommandCollectionEditor : CollectionEditor
  9. {
  10.     public CommandCollectionEditor(Type type)
  11.         : base(type)
  12.     { }
  13.     protected override bool CanSelectMultipleInstances()
  14.     {
  15.         return true;
  16.     }
  17.     protected override Type[] CreateNewItemTypes()
  18.     {            
  19.         return new Type[] { typeof(CommandItem), typeof(CommandSeperator) };
  20.     }
  21.     protected override object CreateInstance(Type itemType)
  22.     {
  23.         if (itemType == typeof(CommandItem))
  24.         {
  25.             return new CommandItem();
  26.         }
  27.         if (itemType == typeof(CommandSeperator))
  28.         {
  29.             return new CommandSeperator();
  30.         }
  31.         return null;
  32.     }
  33. }

實現一個集合編輯器一般要繼承

System.ComponentModel.Design.CollectionEditor,並重寫該類的一些方法。下面分別說一下各個方法的作用。

Ø 集合編輯器中的構造方法 CommandCollectionEditor ,主要完成自定義的初始化功能。該方法中的參數返回該編輯器作用的對象實例(在這裏是CommandCollection的一個對象實例),可以取到當前CommandCollection對象的所有數據。

Ø 方法CanSelectMultipleInstances的返回值表示是否能夠在編輯窗口選擇多個實例,這裏設置返回true

Ø 重寫方法CreateNewItemTypes,返回我們定義的兩個集合類型:

return new Type[] { typeof(CommandItem), typeof(CommandSeperator) };

CommandItemCommandSeperator是我們定義的兩個集合類型。在單擊主控件屬性窗口中集合屬性編輯器“”形狀按鈕時,此方法執行,把所有定義的類型加載到系統集合中緩存起來,然後根據此集合值在編輯器界面中呈現可能選擇類型的列表。

Ø CreateInstance方法主要是負責建立一個集合子項類型實例。

至此所有功能類都建立完成了,最後建立主控件類,並應用CommandCollection集合類作爲控件的一個屬性,代碼如下所示:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. [DefaultProperty("ToolBarItems")]
  6. [ToolboxData("<{0}:MultiTypeCollectionEditorControl runat=server></{0}: MultiTypeCollectionEditorControl>")]
  7. [ParseChildren(true"ToolBarItems")]
  8. public class MultiTypeCollectionEditorControl : WebControl
  9. {
  10.     private CommandCollection _ToolBarItems = new CommandCollection();
  11.     [PersistenceMode(PersistenceMode.InnerProperty)]
  12.     [DesignerSerializationVisibility(DesignerSerializationVisibility. Content)]
  13.     [Description("工具按鈕集設置")]
  14.     [Category("集合設置")]
  15.     public CommandCollection ToolBarItems
  16.     {
  17.         get
  18.         {
  19.             if (_ToolBarItems == null)
  20.             {
  21.                 _ToolBarItems = new CommandCollection();
  22.             }         
  23.             return _ToolBarItems;
  24.         }
  25.     }
  26. //....
  27. }

主控件定義了一個重要的類元數據特性

[ParseChildren(true"ToolBarItems")],表示把ToolBarItems作爲控件的屬性進行解析。其他屬性在前面的章節已經講解多次了,這裏就不再贅述。

編譯控件源代碼庫,在頁面設計器中置一個控件,然後單擊屬性窗口中對應的集合屬性,會打開我們剛剛定義的集合屬性編輯器,如圖4-19所示。

 

4-19  集合編輯器

 

在這裏就可以選擇命令按鈕或分隔符按鈕填充集合了。另外,在成員列表中如果不想看到帶命名空間的類名項,比如只讓它顯示CommandItem而不是KingControls.CommandItem,只要爲其增加一個類型轉換器即可。後面4.6節會詳細講解類型轉換器的實現。這個功能比較簡單,如果需要,讀者可以自己實現它。

好了,本節內容已經講解完,在講解過程中使用到了很多類,這些類都是在實際開發中常用的一些類,只是限於篇幅筆者把它們都精簡化了,讀者也可以體會一下它們的用途。

4.5.2.2  定製模態屬性編輯器

這一節我們學習定製另一種屬性編輯器:模態編輯器,在此編輯器中單擊一個按鈕將彈出一個窗體,從窗體選擇數據後會把值返回到屬性窗口中。最重要的一點是我們可以自定義此選擇數據的模態窗口內容,比上面的集合編輯器更靈活。還是先看一下效果圖,如圖4-20所示。

 

4-20  模態屬性編輯器

 

上圖是以一個表示選擇食品(水果/肉類/蔬菜等)的屬性爲例而定製的一個模態選擇窗口,單擊屬性旁邊的“”按鈕就會彈出圖中左側的模態數據選擇窗口。

下面就來說一下它是怎麼實現的。首先要說明的是由於在設計模式下且模態是直接供IDE接口調用的,因此這裏彈出的窗口就是一個非常普通的WinForm窗口。在我們控件中新增一個WinForm文件CategoryWindow.cs,如圖4-21所示。

 

4-21 “添加新項”對話框

 

增加完後,放置一個ComboBox(提供綁定食品類別數據的選擇列表)和兩個Button控件(“確定”和“取消”)到窗體中,再在“確定”按鈕的事件中增加數據返回功能。

系統會把窗體類分成兩個部分類文件:CategoryWindow.csategoryWindow.Designer.csCategoryWindow.Designer.cs主要存儲窗體和內部控件內容信息;CategoryWindow.cs主要供開發人員完成交互邏輯使用。下面分別來看一下它們的源代碼。

1CategoryWindow.Designer.cs文件中窗體部分的類代碼

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

 

partial class CategoryWindow

{

    /// <summary>

    /// 必需的設計器變量。

    /// </summary>

    private System.ComponentModel.IContainer components = null;

 

    /// <summary>

    /// 清理所有正在使用的資源。

    /// </summary>

    /// <param name="disposing">如果應釋放託管資源,爲 true;否則爲 false</param>

    protected override void Dispose(bool disposing)

    {

        if (disposing && (components != null))

        {

            components.Dispose();

        }

        base.Dispose(disposing);

    }

 

    #region Windows 窗體設計器生成的代碼

 

    /// <summary>

    /// 設計器支持所需的方法 - 不要

    /// 使用代碼編輯器修改此方法的內容。

    /// </summary>

    private void InitializeComponent()

    {

        this.comboBox1 = new System.Windows.Forms.ComboBox();

        this.button1 = new System.Windows.Forms.Button();

        this.button2 = new System.Windows.Forms.Button();

        this.SuspendLayout();

        //

        // comboBox1

        //

        this.comboBox1.FormattingEnabled = true;

        this.comboBox1.Location = new System.Drawing.Point(23, 12);

        this.comboBox1.Name = "comboBox1";

        this.comboBox1.Size = new System.Drawing.Size(217, 20);

        this.comboBox1.TabIndex = 0;

        //

        // button1

        //

        this.button1.DialogResult = System.Windows.Forms.DialogResult.OK;

        this.button1.Location = new System.Drawing.Point(84, 53);

        this.button1.Name = "button1";

        this.button1.Size = new System.Drawing.Size(75, 23);

        this.button1.TabIndex = 1;

        this.button1.Text = "確定";

        this.button1.UseVisualStyleBackColor = true;            

        //

        // button2

        //

        this.button2.DialogResult = System.Windows.Forms.DialogResult.Cancel;

        this.button2.Location = new System.Drawing.Point(165, 53);

        this.button2.Name = "button2";

        this.button2.Size = new System.Drawing.Size(75, 23);

        this.button2.TabIndex = 2;

        this.button2.Text = "取消";

        this.button2.UseVisualStyleBackColor = true;

        //

        // CategoryWindow

        //

        this.AcceptButton = this.button1;

        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);

        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;

        this.ClientSize = new System.Drawing.Size(252, 96);

        this.Controls.Add(this.button2);

        this.Controls.Add(this.button1);

        this.Controls.Add(this.comboBox1);

        this.Cursor = System.Windows.Forms.Cursors.Default;

        this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;

        this.MaximizeBox = false;

        this.MinimizeBox = false;

        this.Name = "CategoryWindow";

        this.StartPosition=System.Windows.Forms.FormStartPosition.CenterScreen;

        this.Text = "CategoryWindow";

        this.TopMost = true;

        this.ResumeLayout(false);

 

    }

 

    #endregion      

 

    public System.Windows.Forms.ComboBox comboBox1;

    private System.Windows.Forms.Button button1;

    private System.Windows.Forms.Button button2;

}

u  需要說明的一點是上面代碼中把兩個Button設置爲窗體返回結果的枚舉值,如下:

this.button1.DialogResult = System.Windows.Forms.DialogResult.OK;

this.button2.DialogResult = System.Windows.Forms.DialogResult.Cancel;

以上語句表示:單擊“確定”按鈕則返回選中的數據給父窗體;單擊“取消”按鈕則不返回數據。

2CategoryWindow.cs 文件中窗體部分的類代碼

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. public partial class CategoryWindow : Form
  6. {
  7.     public CategoryWindow()
  8.     {
  9.         InitializeComponent();
  10.         SetSelectData();
  11.     }
  12.     public void SetSelectData()
  13.     {
  14.         try
  15.         {
  16.             this.comboBox1.Items.Add("水果");
  17.             this.comboBox1.Items.Add("蔬菜");
  18.             this.comboBox1.Items.Add("肉類");
  19.             this.comboBox1.Items.Add("蛋類");
  20.             this.comboBox1.Items.Add("麪食");
  21.         }
  22.         catch (Exception eee)
  23.         {
  24.             throw eee;
  25.         }
  26.         finally
  27.         {
  28.         }
  29.     }  
  30. }

該頁面沒有複雜的交互邏輯,僅在類構造方法中調用

SetSelectData方法爲窗體中的ComboBox控件綁定食品數據列表。這裏限於篇幅僅做了一個儘量簡單的窗體,在實際開發中還可以定製任意複雜的窗體,在本節最後還提供了一個比較複雜的可以實現計算器功能的模態窗體。

數據選擇窗體已經建立好之後,再創建控件的屬性編輯器文件,該編輯器文件中的類主要用於調用上面創建的數據選擇窗體,包括打開窗體,選擇完數據後,接收值並賦給屬性窗口的對應屬性。在講解源代碼之前,要先打開幾個命名空間:

using System.Drawing;

using System.Drawing.Design;

using System.Windows.Forms;

using System.Windows.Forms.Design;

u  這些命名空間主要是提供控件對WinForm的設計模式支持。下面還是先看一下此編輯器類的代碼:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. public class CategoryModalEditor : System.Drawing.Design.UITypeEditor
  6. {
  7.     public CategoryModalEditor()
  8.     {
  9.     }
  10.     public override System.Drawing.Design.UITypeEditorEditStyle GetEditStyle (System.ComponentModel.ITypeDescriptorContext context)
  11.     {            
  12.         return UITypeEditorEditStyle.Modal;
  13.     }
  14.     public override object EditValue(System.ComponentModel.ItypeDescriptor Context context, System.IServiceProvider provider, object value)
  15.     {
  16.         IWindowsFormsEditorService service = (IWindowsFormsEditorService) provider.GetService(typeof(IWindowsFormsEditorService));
  17.         if (service == null)
  18.         {
  19.             return null;
  20.         }
  21.         
  22.         CategoryWindow form = new CategoryWindow();            
  23.         if (service.ShowDialog(form) == DialogResult.OK)
  24.         {
  25.             return form.comboBox1.SelectedItem; 
  26.         }
  27.         
  28.         return value;
  29.     }
  30. }

這裏使用的編輯器基類與前面我們定義的集合編輯器不一樣,前面集合編輯器是使用

System.ComponentModel.Design下的集合基類,這裏使用的是System.Drawing.Design下的UItypeEdit基類。在實際開發中可以任意選擇系統提供的編輯器基類,在4.5.1節已經列出了很多基類,也可以直接繼承這些類定製自己的編輯器。

方法GetEditStyleSystem.ComponentModel.ITypeDescriptorContext類型參數,表示要轉換的對象的上下文;方法GetEditStyleUITypeEditorEditStyle.ModalUITypeEditorEditStyle枚舉表示以什麼樣的形式打開編輯窗體,它有三個枚舉值:ModalDropDownNone。其中Modal表示以模態形式彈出編輯屬性界面;DropDown表示以下拉形式顯示屬性編輯界面;None表示不提供任何形式的UI界面。這裏我們選擇的是Modal枚舉值,表示以模態形式彈出上面我們建立好的食品類別選擇窗體。

EditValue方法是主要的屬性編輯方法,當單擊屬性窗口中的屬性按鈕時會執行此方法。它有三個參數:第一個參數表示當前上下文對象,可以從此對象獲得當前父窗口和屬性的設計時元數據信息等;第二個參數是服務提供者對象,可以根據此對象獲取當前我們需要的服務;第三個參數爲當前屬性的默認值,即編輯當前屬性之前的值。比如:

IWindowsFormsEditorService service = (IWindowsFormsEditorService)provider. GetService(typeof(IWindowsFormsEditorService));

u  以上語句表示獲取IwindowsFormsEditorService類型的服務對象(專門爲WinForm窗體編輯器提供一些功能),獲取對象後就可以使用服務的方法:

if (service.ShowDialog(form) == DialogResult.OK)

{

    return form.comboBox1.SelectedItem;

}

ShowDialog就是IWindowsformsEditorService類型對象的一個方法,表示打開form對象窗體。另外,ShowDialog還有一個DialogResult枚舉的返回值,這裏表示如果返回值爲OK枚舉項,才真正把窗體中當前ComboBoxSelectedItem項返回。看到這裏,我們可能會想起前面把數據選擇窗體中的“確定”和“取消”兩個按鈕的DialogResult屬性值分別設置爲DialogResult.OKDialogResult.Cancel的用途了。

本方法中第三個參數表示當前屬性窗口中對應屬性的當前值。有時您可以根據此值寫一些相關的交互邏輯,比如根據此值設置彈出窗口的默認選中項(該功能比較簡單,您可以擴展該控件功能,自己去實現它)。

整個EditValue方法返回一個object類型的值,系統會根據此返回值對屬性窗口進行填充。

整個數據選擇就是這樣的一個過程,最後我們在主控件代碼中對上面定義的數據選擇窗體和編輯器進行應用。主控件源代碼如下:

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

 

[DefaultProperty("SelectFood")]

[ToolboxData("<{0}:CustomeModalEditorControl runat=server></{0}: CustomeModalEditorControl>")]

public class CustomeModalEditorControl : WebControl

{

    [Bindable(true)]

    [Category("類別")]

    [DefaultValue("")]

    [Localizable(true)]

    [Editor(typeof(CategoryModalEditor), typeof(System.Drawing.Design.UIType Editor))]

    [Description("選擇食品類別")]

    public string SelectFood

    {

        get

        {

            String s = (String)ViewState["SelectFood"];

            return ((s == null) ? String.Empty : s);

        }

 

        set

        {

            ViewState["SelectFood"] = value;

        }

    }

    //… …

}

 

u  主控件中有個選擇食品類別的屬性SelectFood,該屬性上面有一句:

[Editor(typeof(CategoryModalEditor),typeof(System.Drawing.Design.UITypeEditor))]

該句代碼指定屬性的編輯器爲CategoryModalEditor。最後,編譯控件,在屬性窗口中即可看到我們定義的屬性,如圖4-22所示。

 

4-22  模態屬性編輯器

 

4.5.2.3  定製下拉控件屬性編輯器

這一節我們再學習定製另一種形式的屬性編輯器:下拉編輯器,單擊按鈕會下拉一個控件,當使用者從控件選擇數據後該數據值被返回到屬性窗口中。並且此選擇數據的模態窗口內容也是可以自定義的。還是先看一下效果圖,如圖4-23所示。

上圖也是以一個表示選擇食品(水果/肉類/蔬菜等)屬性爲例而定製的編輯器示例,單擊屬性旁邊的下拉按鈕會下拉一個數據選擇的界面,並且此界面也是可以任意定製的。

下面就來說一下此編輯器是怎麼實現的,在控件中新增一個用戶控件(這次不是Windows窗體),如圖4-24所示。

  

4-23  下拉控件屬性編輯器                                    

 

4-24  添加新項對話框

 

然後放置一個ComboBox(提供綁定食品類別數據的選擇列表)和兩個Button控件(“確定”和“取消”)到窗體中,再在“確定”按鈕的事件中增加數據返回功能。

增加用戶控件文件後,系統會把窗體類分成兩個部分類文件:CategoryDropDown.csCategoryDropDown.Designer.csCategoryDropDown.cs主要供開發人員完成交互邏輯;CategoryDropDown.Designer.cs主要存儲窗體和內部控件內容信息,下面分別來看一下它們的源代碼。

1CategoryDropDown.Designer.cs文件中用戶控件部分類的代碼

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. partial class CategoryDropDown
  6. {
  7.     /// <summary> 
  8.     /// 必需的設計器變量。
  9.     /// </summary>
  10.     private System.ComponentModel.IContainer components = null;
  11.     /// <summary> 
  12.     /// 清理所有正在使用的資源。
  13.     /// </summary>
  14.     /// <param name="disposing">如果應釋放託管資源,爲 true;否則爲 false。</param>
  15.     protected override void Dispose(bool disposing)
  16.     {
  17.         if (disposing && (components != null))
  18.         {
  19.             components.Dispose();
  20.         }
  21.         base.Dispose(disposing);
  22.     }
  23.     #region 組件設計器生成的代碼
  24.     /// <summary> 
  25.     /// 設計器支持所需的方法 - 不要
  26.     /// 使用代碼編輯器修改此方法的內容。
  27.     /// </summary>
  28.     private void InitializeComponent()
  29.     {
  30.         this.btnCancel = new System.Windows.Forms.Button();
  31.         this.btnOK = new System.Windows.Forms.Button();
  32.         this.comboBox1 = new System.Windows.Forms.ComboBox();
  33.         this.SuspendLayout();
  34.         // 
  35.         // btnCancel
  36.         // 
  37.         this.btnCancel.DialogResult=System.Windows.Forms.DialogResult.Cancel;
  38.         this.btnCancel.Location = new System.Drawing.Point(161, 56);
  39.         this.btnCancel.Name = "btnCancel";
  40.         this.btnCancel.Size = new System.Drawing.Size(75, 23);
  41.         this.btnCancel.TabIndex = 5;
  42.         this.btnCancel.Text = "取消";
  43.         this.btnCancel.UseVisualStyleBackColor = true;
  44.         this.btnCancel.Click+=new System.EventHandler(this.btnCancel_Click);
  45.         // 
  46.         // btnOK
  47.         // 
  48.         this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK;
  49.         this.btnOK.Location = new System.Drawing.Point(80, 56);
  50.         this.btnOK.Name = "btnOK";
  51.         this.btnOK.Size = new System.Drawing.Size(75, 23);
  52.         this.btnOK.TabIndex = 4;
  53.         this.btnOK.Text = "確定";
  54.         this.btnOK.UseVisualStyleBackColor = true;
  55.         this.btnOK.Click += new System.EventHandler(this.btnOK_Click);
  56.         // 
  57.         // comboBox1
  58.         // 
  59.         this.comboBox1.FormattingEnabled = true;
  60.         this.comboBox1.Location = new System.Drawing.Point(19, 15);
  61.         this.comboBox1.Name = "comboBox1";
  62.         this.comboBox1.Size = new System.Drawing.Size(217, 20);
  63.         this.comboBox1.TabIndex = 3;
  64.         // 
  65.         // CategoryDropDown
  66.         // 
  67.         this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
  68.         this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
  69.         this.Controls.Add(this.btnCancel);
  70.         this.Controls.Add(this.btnOK);
  71.         this.Controls.Add(this.comboBox1);
  72.         this.Name = "CategoryDropDown";
  73.         this.Size = new System.Drawing.Size(254, 95);
  74.         this.ResumeLayout(false);
  75.     }
  76.     #endregion
  77.     private System.Windows.Forms.Button btnCancel;
  78.     private System.Windows.Forms.Button btnOK;
  79.     public System.Windows.Forms.ComboBox comboBox1;
  80. }

這裏沒有像模態編輯器示例一樣設置控件的

DialogResult屬性,而是換了一種方式,分別爲“確定”和“取消”兩按鈕定義事件,在事件中進行數據返回邏輯處理,關於事件將在接下來要講解的另一個部分類中介紹。

2CategoryDropDown.cs文件中用戶控件部分類的代碼

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

 

public partial class CategoryDropDown : UserControl

{

    public string strReturnValue = "";

 

    private IWindowsFormsEditorService service=null;

    public CategoryDropDown(IWindowsFormsEditorService service)

    {

        InitializeComponent();

        SetSelectData();

        this.service = service;

    }

 

    public void SetSelectData()

    {

        try

        {

            this.comboBox1.Items.Add("水果");

            this.comboBox1.Items.Add("蔬菜");

            this.comboBox1.Items.Add("肉類");

            this.comboBox1.Items.Add("蛋類");

            this.comboBox1.Items.Add("麪食");

        }

        catch (Exception eee)

        {

            throw eee;

        }

        finally

        {

 

        }

    }

 

    private void btnOK_Click(object sender, EventArgs e)

    {

        strReturnValue = this.comboBox1.SelectedItem.ToString();

        service.CloseDropDown();

    }

 

    private void btnCancel_Click(object sender, EventArgs e)

    {

        strReturnValue = "";

        service.CloseDropDown();

    }

}

 

u  4.5.2.2節講的模態編輯器一樣構造函數中的SetSelectData方法是提供控件ComboBox下拉界面中的食品類別數據列表另外構造函數中多了一個IWindowsFormsEditorService類型的參數(在4.5.2.2節對該類型進行了說明)把窗體編輯器服務對象傳遞過來因爲這裏我們要在“確定”和“取消”事件中調用關閉當前界面的方法:

service.CloseDropDown();

編輯器的EditValue方法負責打開下拉界面在下拉界面中的兩個按鈕中要分別調用關閉自己的代碼

單擊“確定”按鈕時會把當前選擇的值賦給類內部變量strReturnValue在下面講的編輯器中會獲取該變更的值並賦值到屬性

至此下拉界面已經定義完成接下來定義編輯器類代碼如下:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. public class CategoryDropDownEditor : System.Drawing.Design.UITypeEditor
  6. {
  7.     public CategoryDropDownEditor()
  8.     {
  9.     }
  10.     public override System.Drawing.Design.UITypeEditorEditStyle GetEditStyle (System.ComponentModel.ITypeDescriptorContext context)
  11.     {
  12.         //指定編輯樣式爲下拉形狀, 且基於Control類型
  13.         return UITypeEditorEditStyle.DropDown;
  14.     }
  15.     public override object EditValue(System.ComponentModel.ItypeDescriptor Context context, System.IServiceProvider provider, object value)
  16.     {
  17.         //取得編輯器服務對象
  18.         IWindowsFormsEditorService service = (IWindowsFormsEditorService) provider.GetService(typeof(IWindowsFormsEditorService));
  19.         if (service == null)
  20.         {
  21.             return null;
  22.         }
  23.         //定義一個用戶控件對象
  24.         CategoryDropDown form = new CategoryDropDown(service);
  25.         service.DropDownControl(form);        
  26.     
  27.         string strReturn = form.strReturnValue;
  28.         if (strReturn + String.Empty != String.Empty)
  29.         {
  30.             return strReturn;
  31.         }
  32.         return (string)value;      
  33.     }
  34. }

下拉編輯器與前面小節講的彈出式模態編輯器都通過繼承

System.Drawing.Design. UITypeEditor類實現定製編輯器。

GetEditStyle方法中的代碼:

return UITypeEditorEditStyle.DropDown;

指定編輯器類型爲下拉模式。

EditValue中主要創建數據選擇界面並以下拉模式打開。下面說一下它內部代碼實現邏輯。

IWindowsFormsEditorService service = (IWindowsFormsEditorService)provider. GetService(typeof(IWindowsFormsEditorService));

u  以上代碼主要獲取窗體編輯對象的服務對象,在本例中用到了它的下拉和關閉數據選擇界面的方法。

CategoryDropDown form = new CategoryDropDown(service);

service.DropDownControl(form);

u  以上代碼主要是創建一個數據選擇界面(用戶控件類型),並使用service對象的DropDownControl 方法把參數指定的用戶控件以下拉形式顯示出來,展現形式爲模態形式(暫停執行,直到單擊界面中的按鈕關閉下拉窗體程序才繼續執行)。而EditValue方法是在單擊屬性窗口中屬性旁邊的下拉按鈕時觸發。

string strReturn = form.strReturnValue;

return (string)value;

以上代碼是獲取下拉窗體中當前選擇的數據(單擊“確定”按鈕時把值暫存到form.str ReturnValue變量中)並返回,系統會自動把返回值賦給當前屬性。

最後定義主控件代碼類,代碼如下;

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

 

[DefaultProperty("SelectFood")]

[ToolboxData("<{0}:CustomeDropDownEditorControl runat=server></{0}: CustomeDropDownEditorControl>")]

public class CustomeDropDownEditorControl : WebControl

{

    [Bindable(true)]

    [Category("類別")]

    [DefaultValue("")]

    [Localizable(true)]

    [Editor(typeof(CategoryDropDownEditor), typeof(System.Drawing.Design. UITypeEditor))]

    [Description("選擇食品類別")]

    public string SelectFood

    {

        get

        {

            String s = (String)ViewState["SelectFood"];

            return ((s == null) ? String.Empty : s);

        }

 

        set

        {

            ViewState["SelectFood"] = value;

        }

    }

    //… …

}

u  主控件中僅包括一個SelectFood屬性。這裏僅需要說明的是它的設計時元數據代碼段,如下:

[Editor(typeof(CategoryDropDownEditor), typeof(System.Drawing.Design.UITypeEditor))]

以上代碼指定編輯器爲我們上面定義的CategoryDropDownEditor編輯器。

最後,編譯控件,在屬性窗口中即可看到我們定義的屬性,如圖4-25所示。

 

4-25  屬性窗口

 

4.5.2.4  定製計算器屬性編輯器

本節沒有講解新的控件開發知識,而是利用前面所講解的知識編寫了一個有用的自定義屬性編輯器—計算器屬性編輯器,如圖4-26所示。

 

4-26  計算器屬性編輯器

這個屬性編輯器比較簡單,下面就簡要地說一下它的實現過程。首先看一下計算器面板Form的實現代碼:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. public class FormKeyBoard : System.Windows.Forms.Form
  6. {
  7.     private System.Windows.Forms.Label label2;
  8.     private System.Windows.Forms.TextBox expressBox;
  9.     private System.ComponentModel.Container components = null;
  10.     public string strReturnValue = "";
  11.     
  12.     //定義存放運算符(包括:'+','-',...,'sin',...,'arcsin',...,'(',...等)及其特
  13.     //性的數據結構
  14.     public struct opTable   //定義存放運算符及其優先級和單雙目的結構
  15.     {
  16.         public string op;   //用於存放運算符,op爲operator的簡寫
  17.         public int code;    //用於存放運算符的優先級
  18.         public char grade;  //用於判斷存放的運算符是單目還是雙目
  19.     }
  20.     
  21.     //用於存放制定好的運算符及其特性(優先級和單雙目)的運算符表,其初始化在方
  22.     //法Initialize()中
  23.     public opTable[] opchTbl=new opTable[19];  
  24.     public opTable[] operateStack=new opTable[30];//用於存放從鍵盤掃描的運算符的棧
  25.     
  26.     //定義優先級列表1,2,3,4,5,6,7,8,9,
  27.     //數組中元素依次爲: //"sin","cos","tan","cot","arcsin","arccos","arctan", "sec","csc","ln","^","*","/","+","-","(",")",""   的棧外(因爲有的運算符是從右向左計算,有的是從左往右計算,用內外優先級可以限制其執行順序)優先級
  28. public int[]osp=new int[19]{6,6,6,6,6,6,6,6,6,6,6,5,3,3,2,2,7,0,1};  
  29.     //數組中元素依次爲: //"sin","cos","tan","cot","arcsin","arccos","arctan", "sec","csc","ln","^","*","/","+","-","(" ,"end" 的棧內(因爲有的運算符是從右向左計算,有的是從左往右計算,用內外優先級可以限制其執行順序)優先級
  30. public int[]isp=new int[18]{5,5,5,5,5,5,5,5,5,5,5,4,3,3,2,2,1,1};      
  31.         
  32.     //定義存放從鍵盤掃描的數據的棧
  33.     public double[]dataStack=new double[30]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  34.     //定義表態指針
  35.     public int opTop=-1;  //指向存放(從鍵盤掃描的)運算符棧的指針
  36.     public int dataTop=-1;//指向存放(從鍵盤掃描的)數據棧指針
  37.     
  38.     //定義存放從鍵盤輸入的起始字符串
  39.     public string startString;
  40.     public int startTop=0;
  41.     public double variableX=0;
  42.     public double variableY=0;
  43.     const double PI=3.1415926;
  44.     int number=1;
  45.     public int startTopMoveCount=0;  
  46.     private System.Windows.Forms.Button button1;
  47.     private System.Windows.Forms.Button button2;
  48.     private System.Windows.Forms.Button button3;
  49.     private System.Windows.Forms.Button button4;
  50.     private System.Windows.Forms.Button button5;
  51.     private System.Windows.Forms.Button button6;
  52.     private System.Windows.Forms.Button button7;
  53.     private System.Windows.Forms.Button button8;
  54.     private System.Windows.Forms.Button button9;
  55.     private System.Windows.Forms.Button button10;
  56.     private System.Windows.Forms.Button button11;
  57.     private System.Windows.Forms.Button button12;
  58.     private System.Windows.Forms.Button button13;
  59.     private System.Windows.Forms.Button button14;
  60.     private System.Windows.Forms.Button button15;
  61.     private System.Windows.Forms.Button button16;
  62.     private System.Windows.Forms.Button button17;
  63.     private System.Windows.Forms.Button button18;
  64.     private System.Windows.Forms.Button button19;
  65.     private System.Windows.Forms.Button button20;
  66.     private System.Windows.Forms.Button button21;
  67.     private System.Windows.Forms.Button button22;
  68.     private System.Windows.Forms.Button button23;
  69.     private System.Windows.Forms.Button button24;
  70.     private System.Windows.Forms.Button button25;
  71.     private System.Windows.Forms.Button button26;
  72.     private System.Windows.Forms.Button button27;
  73.     private System.Windows.Forms.Button button28;
  74.     private System.Windows.Forms.Button button29;
  75.     private System.Windows.Forms.Button button30;
  76.     private System.Windows.Forms.Button button31;
  77.     private System.Windows.Forms.Button button32;
  78.     private System.Windows.Forms.Label label1;
  79.     private System.Windows.Forms.TextBox endbox;
  80.     private System.Windows.Forms.Button button33;
  81.     private Button btnClear;
  82.     private Button button34;
  83.     private System.Windows.Forms.Button btnReturn;   
  84.     
  85.     
  86.     
  87.     #region Windows Form Designer generated code
  88.     public FormKeyBoard()
  89.     {
  90.         InitializeComponent();      
  91.     }
  92.     protected override void Dispose( bool disposing )
  93.     {
  94.         if( disposing )
  95.         {
  96.             if (components != null
  97.             {
  98.                 components.Dispose();
  99.             }
  100.         }
  101.         base.Dispose( disposing );
  102.     }
  103.     #endregion 
  104.     #region Windows Form Designer generated code
  105.     /// <summary>
  106.     /// 設計器支持所需的方法- 不要使用代碼編輯器修改
  107.     /// 此方法的內容。
  108.     /// </summary>
  109.     private void InitializeComponent()
  110.     {
  111.     //本方法主要完成控件的創建,初始化,註冊事件等邏輯。完整代碼在本書隨書光盤中
  112.     }
  113.     #endregion
  114.     private void Form1_Load(object sender, System.EventArgs e)
  115.     {
  116.     }
  117.     //制定運算符及其特性(優先級和單雙目)的運算符表
  118.     public void InitializeOpchTblStack()  
  119.     {
  120.         opchTbl[0].op="sin"; opchTbl[0].code=1; opchTbl[0].grade='s';
  121.         opchTbl[1].op="cos"; opchTbl[1].code=2; opchTbl[1].grade='s'
  122.         opchTbl[2].op="tan"; opchTbl[2].code=3; opchTbl[2].grade='s'
  123.         opchTbl[3].op="cot"; opchTbl[3].code=4; opchTbl[3].grade='s';
  124.         opchTbl[4].op="arcsin"; opchTbl[4].code=5; opchTbl[4].grade='s';
  125.         opchTbl[5].op="arccos"; opchTbl[5].code=6; opchTbl[5].grade='s';
  126.         opchTbl[6].op="arctan"; opchTbl[6].code=7; opchTbl[6].grade='s';
  127.         opchTbl[7].op="arccot"; opchTbl[7].code=8; opchTbl[7].grade='s';
  128.         opchTbl[8].op="sec"; opchTbl[8].code=9; opchTbl[8].grade='s';
  129.         opchTbl[9].op="csc"; opchTbl[9].code=10; opchTbl[9].grade='s';
  130.         opchTbl[10].op="ln"; opchTbl[10].code=11; opchTbl[10].grade='s';
  131.         opchTbl[11].op="^"; opchTbl[11].code=12; opchTbl[11].grade='d';
  132.         opchTbl[12].op="*"; opchTbl[12].code=13; opchTbl[12].grade='d';
  133.         opchTbl[13].op="/"; opchTbl[13].code=14; opchTbl[13].grade='d';
  134.         opchTbl[14].op="+"; opchTbl[14].code=15; opchTbl[14].grade='d';
  135.         opchTbl[15].op="-"; opchTbl[15].code=16; opchTbl[15].grade='d';
  136.         opchTbl[16].op="("; opchTbl[16].code=17; opchTbl[16].grade='d';
  137.         opchTbl[17].op=")"; opchTbl[17].code=18; opchTbl[17].grade='d';
  138.         opchTbl[18].op=" "; opchTbl[18].code=19; opchTbl[18].grade='d';
  139.         startString=expressBox.Text;
  140.     }
  141.     public void CreterionFaction()
  142.     {
  143.         //以下代碼消去待掃描字符串中的所有空格字符
  144.         for(int i=0;i<startString.Length;i++)
  145.             if(startString[i].Equals(' '))
  146.             {
  147.                 startString=startString.Remove(i,1);
  148.                 i--;
  149.             }
  150.         //以下代碼使待掃描字符串的單目('+'和'-')變爲雙目
  151.         if(startString.Length!=0)
  152.             if(startString[0]=='+'||startString[0]=='-')
  153.             {
  154.                 startString=startString.Insert(0,"0");
  155.             }
  156.         for(int i=0;i<startString.Length-1;i++)
  157.         {
  158.             if((startString[i]=='(')&&(startString[i+1]=='-'))
  159.                 startString=startString.Insert(i+1,"0");
  160.         }
  161.         startString=startString.Insert(startString.Length,")");
  162.         //將待掃描字符串轉化爲小寫字母
  163.         startString=startString.ToLower();
  164.     }
  165.     public bool CheckParentthese() //檢查括號是否匹配
  166.     {
  167.         int number=0;
  168.         for(int i=0;i<startString.Length-1;i++)
  169.         {
  170.             if(i=='(') number++;
  171.             if(i==')') number--;
  172.             if(number<0) return false;
  173.         }
  174.         if(number!=0)  
  175.         {
  176.             return false;
  177.         }
  178.         return true;
  179.     }
  180.     //給運算表達式分塊(三角函數、算術運算符等),再根據其返回值來檢驗其屬於哪類錯誤
  181.     public int CheckFollowCorrect()    
  182.     {
  183.         string str,oldString="",newString="";
  184.         int dataCount=0,characterCount=0;
  185.         if(startString.Equals(")"))       
  186.             return 0;         //輸入字符串爲空返回值
  187. if((startString[0]=='*')||(startString[0]=='/')||(startString[0]=='^')||
  188.     (startString[0]==')'))
  189.             return 11;        //首字符輸入錯誤返回值
  190. for(int i=0;i<startString.Length;i++)
  191. {
  192.     if((oldString.Equals("三角函數"))&&(newString.Equals("右括號")))
  193.         return 2;     //三角函數直接接右括號錯誤返回值
  194.     if((oldString.Equals("左括號"))&&(newString.Equals("算術運算符")))
  195.         return 3;     //左括號直接接算術運算符錯誤返回值
  196.     if((oldString.Equals("數字序列"))&&(newString.Equals("三角函數")))
  197.         return 4;     //數字序列後直接接三角函數錯誤返回值
  198.     if((oldString.Equals("數字序列"))&&(newString.Equals("左括號")))
  199.         return 5;     //數字序列後直接接左括號錯誤返回值
  200.     if((oldString.Equals("算術運算符"))&&(newString.Equals("右括號")))
  201.         return 6;     //算術運算符後直接接右括號錯誤返回值
  202.     if((oldString.Equals("右括號"))&&(newString.Equals("左括號")))
  203.         return 7;     //右括號直接接左括號錯誤返回值
  204.     if((oldString.Equals("右括號"))&&(newString.Equals("三角函數")))
  205.         return 8;     //右括號直接接三角函數錯誤返回值
  206.     if((oldString.Equals("數字序列"))&&(newString.Equals("數字序列")))
  207.         return 9;     //數字序列後直接接'pi'/'e'或'pi'/'e'直接接數字序列錯誤返回值
  208.     if((oldString.Equals("算術運算符"))&&(newString.Equals("算術運算符")))
  209.         return 10;    //算術運算符後直接接算術運算符錯誤返回值
  210.     oldString=newString;
  211.     if(i<startString.Length-5&&startString.Length>=6)
  212.     {
  213.         str=startString.Substring(i,6); 
  214. if((str.CompareTo("arcsin")==0)||(str.CompareTo("arccos")==0)||(str.Compar
  215.     eTo("arctan")==0)||(str.CompareTo("arccot")==0))
  216.     {
  217.         newString="三角函數";
  218.         i+=5; characterCount++;
  219.         continue;
  220.     }
  221. }
  222. if(i<startString.Length-2&&startString.Length>=3)
  223. {
  224.     str=startString.Substring(i,3);
  225. if((str.CompareTo("sin")==0)||(str.CompareTo("cos")==0)||(str.CompareTo("tan")==0)||(str.CompareTo("cot")==0)||(str.CompareTo("sec")==0)||(str.CompareTo("csc")==0))
  226.     {
  227.         newString="三角函數";
  228.         i+=2; characterCount++;
  229.         continue;
  230.     }
  231. }
  232. if(i<(startString.Length-1)&&(startString.Length)>=2)
  233. {
  234.     str=startString.Substring(i,2);
  235.     if(str.CompareTo("ln")==0)
  236.     {
  237.         newString="三角函數";
  238.         i+=1; characterCount++;
  239.         continue;
  240.     }
  241.     if(str.CompareTo("pi")==0)
  242.     {
  243.         newString="數字序列";
  244.         i+=1;dataCount++;
  245.         continue;
  246.     }
  247.     }
  248.     str=startString.Substring(i,1);
  249. if(str.Equals("^")||str.Equals("*")||str.Equals("/")||str.Equals("+")||str.Equals("-"))
  250.     {
  251.         newString="算術運算符";
  252.         characterCount++;
  253.         continue;
  254.     }
  255.     if(str.Equals("e"))
  256.     {
  257.         newString="數字序列";
  258.         dataCount++;
  259.         continue;
  260.     }
  261.     if(str.Equals("("))
  262.     {
  263.         newString="左括號";
  264.         characterCount++;
  265.         continue;
  266.     }
  267.     if(str.Equals(")"))
  268.     {
  269.         newString="右括號";
  270.         characterCount++;
  271.         continue;
  272.     }
  273.     if(Char.IsDigit(startString[i]))
  274.     {
  275.         while(Char.IsDigit(startString[i]))
  276.     {
  277.         i++;                                          
  278.     }
  279. if(startString[i]=='.'&&(!Char.IsDigit(startString[i+1]))&&(i+1)!=startString.Length)
  280.         return 13;
  281.     if(startString[i]=='.')
  282.     {
  283.         i++;
  284.     }                    
  285.     while(Char.IsDigit(startString[i]))
  286.     {
  287.         i++;  
  288.     }
  289.     newString="數字序列";
  290.     i--; dataCount++;
  291.     continue;
  292.     }
  293.     return 1;         //非法字符
  294. if((dataCount==0&&characterCount!=0)||(startString[0]=='0'&&dataCount==1&
  295.    characterCount>1&&startString.Length!=2))
  296.             return 12;
  297.         return 100;
  298.     }
  299.     public int IsCharacterOrData(ref double num)
  300.     {
  301.         string str="";
  302.         startTop+=startTopMoveCount; startTopMoveCount=0;
  303.         int i=startTop;
  304.         if(i<startString.Length-5&&startString.Length>=6)
  305.         {
  306.             str=startString.Substring(i,6); 
  307.             for(int j=4;j<=7;j++)
  308.                 if(str.Equals(opchTbl[j].op))
  309.                 {
  310.                     startTopMoveCount=6;
  311.                     return opchTbl[j].code;
  312.                 }
  313.         }
  314.         if(i<startString.Length-2&&startString.Length>=3)
  315.         {                 
  316.             str=startString.Substring(i,3);                 
  317.             for(int j=0;j<10;j++)
  318.                 if(str.CompareTo(opchTbl[j].op)==0)
  319.                 {
  320.                     startTopMoveCount=3;
  321.                     return opchTbl[j].code;
  322.                 }
  323.         }
  324.         if(i<(startString.Length-1)&&(startString.Length)>=2)
  325.         {
  326.             str=startString.Substring(i,2);
  327.             if(str.CompareTo("ln")==0)
  328.             {
  329.                 startTopMoveCount=2;
  330.                 return 11;
  331.             }
  332.             if(str.CompareTo("pi")==0)
  333.             {
  334.                 startTopMoveCount=2;
  335.                 num=Math.PI;
  336.                 return 100;
  337.             }
  338.         }
  339.         //以下開始確認一個字符是屬於什麼值類型
  340.         if(i<startString.Length)
  341.         {
  342.             str=startString.Substring(i,1);
  343.             for(int j=11;j<19;j++)
  344.             {
  345.                 if(str.Equals(opchTbl[j].op))
  346.                 {startTopMoveCount=1;return opchTbl[j].code;}                
  347.             }
  348.             if(str.CompareTo("e")==0)
  349.             {
  350.                 startTopMoveCount=1; num=Math.E;
  351.                 return 100;
  352.             }  
  353.             if(Char.IsDigit(startString[i]))
  354.             {
  355.                 double temp=0,M=10; int j=i;
  356.                 while(Char.IsDigit(startString[j]))
  357.                 {
  358.                     temp=M*temp+Char.GetNumericValue(startString[j]);
  359.                     startTop++;
  360.                     j++;                      
  361.                 }
  362.                 if(startString[j]=='.')
  363.                 {
  364.                     j++;startTop++;                       
  365.                 }
  366.                 while(Char.IsDigit(startString[j]))
  367.                 {
  368.                     temp+=1.0/M*Char.GetNumericValue(startString[j]);
  369.                     M/=10;j++;    
  370.                     startTop++;
  371.                 }
  372.                 startTopMoveCount=0;
  373.                 num=temp;
  374.                 return 100;
  375.             }
  376.         }
  377.         return -1;
  378.     }
  379.     public double DoubleCount(string opString,double data1,double data2)
  380.     {   //雙目運算
  381.         if(opString.CompareTo("+")==0) return (data1+data2);
  382.         if(opString.CompareTo("-")==0) return (data1-data2);
  383.         if(opString.CompareTo("*")==0) return (data1*data2);
  384.         if(opString.CompareTo("/")==0) return (data1/data2);
  385.         if(opString.CompareTo("^")==0) 
  386.         {
  387.             double end=data1;
  388.             for(int i=0;i<data2-1;i++)
  389.                 end*=data1;
  390.             return (end);
  391.         }            
  392.         return Double.MaxValue;    //定義域不對,返回
  393.     }
  394.     public double DoubleCount(string opString,double data1)
  395.     {   //單目運算
  396.         if(opString.CompareTo("sin")==0) return Math.Sin(data1);
  397.         if(opString.CompareTo("cos")==0) return Math.Cos(data1);
  398.         if(opString.CompareTo("tan")==0) return Math.Tan(data1);
  399.         if(opString.CompareTo("cot")==0) return (1/(Math.Tan(data1)));
  400.         if(opString.CompareTo("arcsin")==0)
  401.         if(-1<=data1&&data1<=1)    return Math.Asin(data1);
  402.     
  403.         if(opString.CompareTo("arccos")==0) 
  404.             if(-1<=data1&&data1<=1)      return Math.Acos(data1);
  405.         
  406.         if(opString.CompareTo("arctan")==0)
  407.             if(-Math.PI/2<=data1&&data1<=Math.PI/2)return Math.Atan(data1);
  408.         if(opString.CompareTo("arccot")==0)
  409.             if(-Math.PI/2<=data1&&data1<=Math.PI/2)return (-Math.Atan(data1));
  410.         if(opString.CompareTo("sec")==0) return (1/(Math.Cos(data1)));
  411.         if(opString.CompareTo("csc")==0) return (1/(Math.Sin(data1)));
  412.         if(data1>0) if(opString.CompareTo("ln")==0) return  Math.Log(data1);
  413.         return Double.MaxValue;   //定義域不對
  414.     }
  415.     public bool CountValueY(ref double tempY)  //此方法功能爲求解
  416.     {
  417.         int type=-1;       //存放正在掃描的字符串是爲數字類型還是單雙目運算符
  418.         double num=0;      //如果是數據,則返回數據的值
  419.         //進棧底結束符"end"
  420.         opTop++;
  421.         operateStack[opTop].op="end"; operateStack[opTop].code=18; 
  422.         operateStack[opTop].grade=' ';
  423.         while(startTop<=startString.Length-1)
  424.         {
  425.         start:
  426.             type=IsCharacterOrData(ref num);  //調用判斷返回值類型函數
  427.             if(type==-1){return false;}                
  428.             if(type==100) 
  429.             {                
  430.                 dataTop=dataTop+1;
  431.                 dataStack[dataTop]=num;                                                            
  432.             }    
  433.             else
  434.             {   
  435.                 if(osp[type-1]>isp[operateStack[opTop].code-1])   //操作符進棧
  436.                 {
  437.                     opTop++;
  438.                     operateStack[opTop].op=opchTbl[type-1].op; 
  439.                     operateStack[opTop].code=opchTbl[type-1].code; 
  440.                     operateStack[opTop].grade=opchTbl[type-1].grade; 
  441.                 }
  442.                 else
  443.     {
  444.         //彈出操作符跟數據計算,並存入數據
  445.         while(osp[type-1]<=isp[operateStack[opTop].code-1])  
  446.         {    
  447.             //當遇到"end"結束符表示已經獲得結果
  448.             if(operateStack[opTop].op.CompareTo("end")==0) 
  449.             {
  450.                 if(dataTop==0)
  451.                 {
  452.                     tempY=dataStack[dataTop];  startTop=0; startTopMoveCount=0; 
  453.                     opTop=-1; dataTop=-1;
  454.                     return true;
  455.                 }
  456.                 else return false;//運算符和數據的個數不匹配造成的錯誤
  457.             }
  458.             if(operateStack[opTop].op.CompareTo("(")==0)  //如果要彈出操作數爲
  459.                                                                    //'( ',則消去左括號
  460.             {
  461.                 opTop--; goto start;
  462.             }  
  463.             //彈出操作碼和一個或兩個數據計算,並將計算結果存入數據棧
  464.             double data1,data2; opTable operate;
  465.             if(dataTop>=0)    data2=dataStack[dataTop];
  466.             else return false;
  467.             operate.op=operateStack[opTop].op; operate.code=operateStack
  468.             [opTop].code; operate.grade=operateStack[opTop].grade;
  469.             opTop--;  //處理一次,指針必須僅且只能下移一個單位
  470.             if(operate.grade=='d')
  471.                 {
  472.                 if(dataTop-1>=0)    data1=dataStack[dataTop-1];
  473.                 else return false;
  474.                 double tempValue=DoubleCount(operate.op,data1,data2);
  475.                 if(tempValue!=Double.MaxValue)dataStack[--dataTop]=tempValue;
  476.                 else return false;
  477.             }
  478.             if(operate.grade=='s')
  479.             {
  480.                 double tempValue=DoubleCount(operate.op,data2);
  481.                 if(tempValue!=Double.MaxValue)
  482.                     dataStack[dataTop]=tempValue;
  483.                     else return false;
  484.                 }
  485.             }                                        
  486.             //如果當前棧外操作符比棧頂的操作符優先級別高,則棧外操作符進棧
  487.                 opTop++;
  488.                 operateStack[opTop].op=opchTbl[type-1].op; 
  489.                 operateStack[opTop].code=opchTbl[type-1].code; 
  490.                 operateStack[opTop].grade=opchTbl[type-1].grade;
  491.             }
  492.         }
  493.     }
  494.     return false
  495. }
  496. public void StartExcute()
  497. {
  498.     InitializeOpchTblStack();
  499.     CreterionFaction();
  500.     if(CheckParentthese()==false)
  501.     {
  502.         MessageBox.Show("括號不匹配,請重新輸入!!!","錯誤",MessageBoxButtons.OK, 
  503.              MessageBoxIcon.Error);
  504.         return
  505.     }
  506.     switch(CheckFollowCorrect())
  507.     {
  508.         case 0: MessageBox.Show("表達式爲空,請先輸入表達式!!!","錯誤"
  509.                   MessageBoxButtons.OK,MessageBoxIcon.Warning); 
  510.         return;
  511.         case 1:  MessageBox.Show("表達式中有非法字符!!!","錯誤"
  512.                   MessageBoxButtons.OK,MessageBoxIcon.Error); 
  513.         return;
  514.         case 2:  MessageBox.Show("三角函數運算符與) 之間應輸入數據或其他表達式!!!","
  515.                   錯誤",MessageBoxButtons.OK,MessageBoxIcon.Error);
  516.          return;
  517.         case 3:  MessageBox.Show("' (  ' 與算術運算符之間應輸入數據或其他表達
  518.                   式!!!","錯誤",MessageBoxButtons.OK,MessageBoxIcon.Error);
  519.         return;
  520.         case 4:  MessageBox.Show("數字數列與三角函數之間應輸入算術運算符或其他表達
  521.                   式!!!","錯誤",MessageBoxButtons.OK,MessageBoxIcon.Error);
  522.         return;
  523.         case 5:  MessageBox.Show("數字數列與 ' (  '  之間應輸入算術運算符或其他表達
  524.                   式!!!","錯誤",MessageBoxButtons.OK,MessageBoxIcon.Error);
  525.     return;
  526.         case 6:  MessageBox.Show("算術運算符與右括號之間應輸入數據或其他表達式!!!","
  527.                   錯誤",MessageBoxButtons.OK,MessageBoxIcon.Error);
  528.         return;
  529.         case 7:  MessageBox.Show("'  )  ' 與'  (  ' 之間應輸入算術運算符或其他表達
  530.                   式!!!","錯誤",MessageBoxButtons.OK,MessageBoxIcon.Error);
  531.         return;
  532.         case 8:  MessageBox.Show("'   )   ' 與三角函數之間應輸入算術運算符或其他表達
  533.              式!!!","錯誤",MessageBoxButtons.OK,MessageBoxIcon.Error);
  534.         return;
  535.         case 9:  MessageBox.Show("常量'  PI  '  或 '  E  '  或 '  X  '  與數字數據
  536.                   之間應輸入算術運算符或其他表達式!!!","錯誤", 
  537.                   MessageBoxButtons.OK,MessageBoxIcon.Error);
  538.         return;
  539.         case 10: MessageBox.Show("算術運算符與算術運算符之間應輸入數據或其他表達
  540.                   式!!!","錯誤",MessageBoxButtons.OK,MessageBoxIcon.Error); 
  541.         return;
  542.         case 11: MessageBox.Show("表達式頭部不能爲' + ',' - ',' * ',' / '' ^ ',' )'
  543.                  !!!","錯誤",MessageBoxButtons.OK,MessageBoxIcon.Error);
  544.         return;    
  545.         case 12: MessageBox.Show("僅有運算符號沒有數字數據或數據缺少而無法計算,請輸入數
  546.                  字數據!!!","錯誤",MessageBoxButtons.OK,MessageBoxIcon.Error);
  547.         return;
  548.         case 13: MessageBox.Show("小數點後面缺少小數部分,請輸入小數部分!!!","錯誤",
  549.                   MessageBoxButtons.OK,MessageBoxIcon.Error);
  550.         return;
  551.     }
  552.     double tempY=0;
  553.     switch(CountValueY(ref tempY))
  554.     {                
  555.         case false:MessageBox.Show("輸入的表達式不正確或反三角函數定義域在其定義域範圍
  556.                     之外!!!","錯誤",MessageBoxButtons.OK,MessageBoxIcon.Error);
  557.         return;
  558.     }            
  559.     endbox.Text=tempY.ToString();//依次存檔計算結果
  560.     number++;            
  561. }
  562. private void button30_Click(object sender, System.EventArgs e)
  563. {
  564.     StartExcute();
  565. }
  566. private void button10_Click(object sender, System.EventArgs e)
  567. {
  568.     expressBox.SelectedText=null;
  569.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button10.Text);            
  570.     expressBox.SelectionStart=expressBox.TextLength;
  571. }
  572. private void button11_Click(object sender, System.EventArgs e)
  573. {
  574.     expressBox.SelectedText=null;
  575.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart,".");            
  576.     expressBox.SelectionStart=expressBox.TextLength;
  577. }
  578. private void button27_Click(object sender, System.EventArgs e)
  579. {
  580.     expressBox.SelectedText=null;
  581.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart,"^");            
  582.     expressBox.SelectionStart=expressBox.TextLength;
  583. }
  584. private void button1_Click_1(object sender, System.EventArgs e)
  585. {        
  586.     expressBox.SelectedText=null;
  587.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button1.Text);
  588.     expressBox.SelectionStart=expressBox.TextLength;
  589. }
  590. private void button4_Click(object sender, System.EventArgs e)
  591. {
  592.     expressBox.SelectedText=null;
  593.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button4.Text);
  594.     expressBox.SelectionStart=expressBox.TextLength;
  595. }
  596. private void button3_Click(object sender, System.EventArgs e)
  597. {
  598.     expressBox.SelectedText=null;
  599.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button3.Text);
  600.     expressBox.SelectionStart=expressBox.TextLength;
  601. }
  602. private void button2_Click_1(object sender, System.EventArgs e)
  603. {
  604.     expressBox.SelectedText=null;
  605.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button2.Text);
  606.     expressBox.SelectionStart=expressBox.TextLength;
  607. }
  608. private void button14_Click(object sender, System.EventArgs e)
  609. {
  610.     expressBox.SelectedText=null;
  611.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button14.Text);
  612.     expressBox.SelectionStart=expressBox.TextLength;
  613. }
  614. private void button15_Click(object sender, System.EventArgs e)
  615. {
  616.     expressBox.SelectedText=null;
  617.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button15.Text);
  618.     expressBox.SelectionStart=expressBox.TextLength;
  619. }
  620. private void button5_Click(object sender, System.EventArgs e)
  621. {            
  622.     expressBox.SelectedText=null;
  623.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button5.Text);
  624.     expressBox.SelectionStart=expressBox.TextLength;
  625. }
  626. private void button6_Click(object sender, System.EventArgs e)
  627. {
  628.     expressBox.SelectedText=null;
  629.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button6.Text);
  630.     expressBox.SelectionStart=expressBox.TextLength;
  631. }
  632. private void button9_Click(object sender, System.EventArgs e)
  633. {
  634.     expressBox.SelectedText=null;
  635.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button9.Text);
  636.     expressBox.SelectionStart=expressBox.TextLength;
  637. }
  638. private void button8_Click(object sender, System.EventArgs e)
  639. {
  640.     expressBox.SelectedText=null;
  641.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button8.Text);
  642.     expressBox.SelectionStart=expressBox.TextLength;
  643. }
  644. private void button7_Click(object sender, System.EventArgs e)
  645. {
  646.     expressBox.SelectedText=null;
  647.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button7.Text);
  648.     expressBox.SelectionStart=expressBox.TextLength;
  649. }
  650. private void button12_Click(object sender, System.EventArgs e)
  651. {
  652.     expressBox.SelectedText=null;
  653.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button12.Text);
  654.     expressBox.SelectionStart=expressBox.TextLength;
  655. }
  656. private void button13_Click(object sender, System.EventArgs e)
  657. {
  658.     expressBox.SelectedText=null;
  659.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button13.Text);
  660.     expressBox.SelectionStart=expressBox.TextLength;
  661. }
  662. private void button29_Click(object sender, System.EventArgs e)
  663. {            
  664.     expressBox.SelectedText=null;
  665.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button29.Text);
  666.     expressBox.SelectionStart=expressBox.TextLength;
  667. }
  668. private void button28_Click(object sender, System.EventArgs e)
  669. {
  670.     expressBox.SelectedText=null;
  671.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button28.Text);
  672.     expressBox.SelectionStart=expressBox.TextLength;
  673. }
  674. private void button16_Click(object sender, System.EventArgs e)
  675. {
  676.     expressBox.SelectedText=null;
  677.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button16.Text);
  678.     expressBox.SelectionStart=expressBox.TextLength;
  679. }
  680. private void button20_Click(object sender, System.EventArgs e)
  681. {
  682.     expressBox.SelectedText=null;
  683.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button20.Text);            
  684.     expressBox.SelectionStart=expressBox.TextLength;
  685. }
  686. private void button17_Click(object sender, System.EventArgs e)
  687. {
  688.     expressBox.SelectedText=null;
  689.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button17.Text);
  690.     expressBox.SelectionStart=expressBox.TextLength;
  691. }
  692. private void button21_Click(object sender, System.EventArgs e)
  693. {
  694.     expressBox.SelectedText=null;
  695.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button21.Text);            
  696.     expressBox.SelectionStart=expressBox.TextLength;
  697. }
  698. private void button24_Click(object sender, System.EventArgs e)
  699. {
  700.     expressBox.SelectedText=null;
  701.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button24.Text);
  702.     expressBox.SelectionStart=expressBox.TextLength;
  703. }
  704. private void button18_Click(object sender, System.EventArgs e)
  705. {
  706.     expressBox.SelectedText=null;
  707.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button18.Text);            
  708.     expressBox.SelectionStart=expressBox.TextLength;
  709. }
  710. private void button22_Click(object sender, System.EventArgs e)
  711. {
  712.     expressBox.SelectedText=null;
  713.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button22.Text);
  714.     expressBox.SelectionStart=expressBox.TextLength;
  715. }
  716. private void button25_Click(object sender, System.EventArgs e)
  717. {
  718.     expressBox.SelectedText=null;
  719.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button25.Text);
  720.     expressBox.SelectionStart=expressBox.TextLength;
  721. }
  722. private void button19_Click(object sender, System.EventArgs e)
  723. {
  724.     expressBox.SelectedText=null;
  725.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button19.Text);            
  726.     expressBox.SelectionStart=expressBox.TextLength;
  727. }
  728. private void button23_Click(object sender, System.EventArgs e)
  729. {
  730.     expressBox.SelectedText=null;
  731.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button23.Text);
  732.     expressBox.SelectionStart=expressBox.TextLength;
  733. }
  734. private void button26_Click(object sender, System.EventArgs e)
  735. {
  736.     expressBox.SelectedText=null;
  737.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button26.Text);
  738.     expressBox.SelectionStart=expressBox.TextLength;
  739. }
  740. private void button31_Click(object sender, System.EventArgs e)
  741. {
  742.     if(expressBox.Text.Length>0)
  743.         expressBox.Text=expressBox.Text.Remove(expressBox.Text.Length-1,1);
  744. }
  745. private void button32_Click(object sender, System.EventArgs e)
  746. {
  747.     expressBox.Text="";
  748.     endbox.Text="0.000000";
  749. }
  750. private void button33_Click(object sender, System.EventArgs e)
  751. {
  752.     expressBox.SelectedText=null;
  753.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart,"PI");
  754.     expressBox.SelectionStart=expressBox.TextLength;
  755. }
  756. private void button35_Click(object sender, System.EventArgs e)
  757. {            
  758.     strReturnValue = endbox.Text.Trim();
  759. }
  760. private void btnClear_Click(object sender, EventArgs e)
  761. {
  762.     expressBox.Text = "";
  763. }
  764. private void button34_Click(object sender, EventArgs e)
  765. {
  766.     StartExcute();
  767. }
  768. }

上面代碼爲一個

WinForm窗體主要實現展示一個計算器功能此計算器可以一次性計算多個操作項的值例如:y = 3 + 64 * (2 + 3^5) + sinPI的值,這是它與其他計算器的不同之處比如Windows自帶的計算器一次只能計算兩個操作數這裏可以支持您任意輸入多個操作數的表達式最後一塊計算出結果支持:
sincostancotarcsinarccosarctanseccscln^*/+-()運算符並用括號區分優先級另外此計算器的實現算法也不錯採用經典的Stack編譯算法感興趣的讀者可以研究一下。

然後定義一個編輯器,其實所有這些的編輯器功能相當於一個“橋接器”,使屬性與自定義Form窗體關聯起來。代碼如下:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. public class CalculatorSelectEditor : System.Drawing.Design.UITypeEditor
  6. {
  7.     public CalculatorSelectEditor()
  8.     {
  9.     }
  10.     public override System.Drawing.Design.UITypeEditorEditStyle GetEditStyle 
  11.          (System.ComponentModel.ITypeDescriptorContext context)
  12.     {            
  13.         return UITypeEditorEditStyle.Modal;
  14.     }
  15.     public override object EditValue(System.ComponentModel.ItypeDescriptor 
  16.         Context context, System.IServiceProvider provider, object value)
  17.     {
  18.         IWindowsFormsEditorService service = (IWindowsFormsEditorService) 
  19.       provider.GetService(typeof(IWindowsFormsEditorService));
  20.         if (service == null)
  21.         {
  22.             return null;
  23.         }
  24.         
  25.         FormKeyBoard form = new FormKeyBoard();
  26.         
  27.         if (service.ShowDialog(form) == DialogResult.OK)
  28.         {
  29.             object strReturn = form.strReturnValue;
  30.             return strReturn;
  31.         }
  32.         
  33.         return value;
  34.     }
  35. }

此類的功能是彈出一個模式的計算器

Form窗體,與4.5.2.2節定義的編輯器功能幾乎一樣,這裏就不作多講,如果還有不明白的地方請回顧一下前面章節的內容。

最後定義主控件代碼類,如下所示:

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

 

[DefaultProperty("Money")]

[ToolboxData("<{0}:CalculatorSelectControl

    runat=server></{0}:CalculatorSelectControl>")]

public class CalculatorSelectControl : WebControl

{

    [Bindable(true)]

    [Category("自定義計算機屬性")]

    [DefaultValue("")]

    [Localizable(true)]

    [Editor(typeof(CalculatorSelectEditor), typeof(System.Drawing.Design. UITypeEditor))]

    [Description("請輸入金額")]

    public string Money

    {

        get

        {

            string s = (string)ViewState["Money"];

            return ((s == null) ? "" : s);

        }

 

        set

        {

            ViewState["Money"] = value;

        }

    }

    //… …

}

 

u  主控件中定義了一個表示金額的Money屬性,並指定設計時的編輯器爲我們上面定義的計算器編輯器類:

[Editor(typeof(CalculatorSelectEditor), typeof(System.Drawing.Design.UITypeEditor))]

編譯此控件,在屬性窗口中單擊屬性旁邊的“”按鈕即可以看到我們定義的計算器編輯器,如圖4-27所示。

 

4-27  屬性窗口

 

本節內容已經講解完。本節主要實現了幾個自定義功能的編輯器:多類型子項集合編輯器、彈出式模態數據選擇編輯器、下拉式數據選擇編輯器,最後綜合運用前面的知識實現了一個計算器編輯器。限於篇幅,示例都比較簡單,但已經足以說明其用法了。另外,在定義自己的編輯器時,除了使用編輯器基類外,還可以把4.5.1節列出的那些系統編輯器作爲基類使用。

4.6  類型轉換器

類型轉換器是什麼?它主要完成什麼樣的功能呢?類型轉換器可用於在數據類型之間轉換值,並通過提供文本到值的轉換或待選值的下拉列表來幫助在設計時配置屬性。如果配置正確,通過使用InstanceDescriptorSystem.Reflection對象來給設計器序列化系統提供生成在運行時初始化屬性的代碼所需的信息,類型轉換器可以生成屬性配置代碼。

類型轉換器可用於字符串到值的轉換,或用於在設計時和運行時數據類型之間的雙向翻譯。在宿主(如窗體設計器中的屬性瀏覽器)中,類型轉換器允許以文本形式向用戶表示屬性值,並且可以將用戶輸入的文本轉換爲相應數據類型的值。

大多數本機數據類型(Int32String、枚舉類型和其他類型)具有默認的類型轉換器,提供從字符串到值的轉換並執行驗證檢查。默認的類型轉換器位於System.ComponentModel命名空間中,名爲TypeConverterNameConverter。當默認功能無法滿足需要時,可以擴展類型轉換器;當定義的自定義類型沒有關聯的類型轉換器時,可以實現自定義類型轉換器。

4.6.1  系統類型轉換器

系統默認提供了許多常用的類型轉換器,其中有不少我們在使用控件時已經用到了。本節主要列舉一些常用的轉換器,並以其中幾個經典的轉換器爲例說明其使用方式。

4.6.1.1  整型類型轉換器

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

 

[TypeConverter(typeof(Int32Converter))]

public int IntConverter

{

    //… …

}

轉換器類名爲Int32Converter,用於32位有符號整數對象與其他類型之相互轉換的類型轉換。它的展示樣式與一般字符串屬性完全一樣,只是假如我們輸入非整型的值,它會提示格式不正確,要求重新輸入,例如輸入一個字符“a”,會彈出如圖4-28所示的提示輸入格式錯誤的窗口。

 

 

4-28  類型不匹配提示窗口

 

在實際應用中,像stringint等類型的屬性不需要我們指定轉換器,它會自動關聯繫統默認的轉換器,即上面的[TypeConverter(…)]語句可以去掉。

這裏要說明的重點是,爲屬性增加轉換器的方法:

[TypeConverter(typeof(Int32Converter))]

該方法在屬性上方增加TypeConverter設計時屬性,參數爲轉換器的類型(系統提供的或自定義的),後面小節會介紹怎樣爲特性的屬性類型定製自定義轉換器。

4.6.1.2  WebColor類型轉換器

[TypeConverter(typeof(WebColorConverter))]

public Color WebColorConverter

{

    //… …

}

 

屬性窗口中顯示效果如圖4-29所示。

 

 

圖4-29 顏色類型轉換器

 

這也是系統提供的一個常用轉換器,轉換器類爲WebColor Converter,注意單擊下拉的顏色選擇面板不是WebColorConverter提供的,是由默認的顏色編輯器提供(在4.5.1.2節有講解),WebColorConverter主要用於設計或運行時從WebColor類型到字符串類型的轉換。WebColorColor相比,提供了更多表示顏色格式的類型。

4.6.1.3  控件ID列表類型轉換器

如果在設計器中的某幾個控件具有關聯關係,比如一個控件在運行時要獲取另一個控件的一些屬性值,則可以用控件列表轉換器ControlIDConverter來建立兩個控件之間的關聯關系。

[TypeConverter(typeof(ControlIDConverter))]             

public string TargetControl

{

    //… …

}

轉換器類型爲System.Web.UI.WebControls.ControlIDConverter,指定此類型轉換器類型的屬性展示效果如圖4-30所示。

 

4-30  屬性展示效果

 

在屬性列表中,已經列出了設計器中其他幾個控件的ID。在實際應用中,知道了控件的ID,就可以通過FindControl方法獲取到整個控件了,FindControl使用方法在第3章有講解。

除了上面介紹的三個外,系統還提供了好多轉換器,限於篇幅就不一一作講解,請看錶4-1

在實際開發時,可以參考上面列表選擇適合的類型轉換器。提醒一點,上面的轉換器除了可以用作開發控件設計時的屬性類型轉換器;也可以在其他地方直接使用類轉換器中的功能方法,即把類型轉換器作爲普通類使用,這種方式也用得比較廣泛,在後面講解視圖狀態機制時就利用了自定義類型轉換器(接下來要講的SolidCoordinateConverter類型轉換器)對視圖對象進行正反序列化。

4-1  系統轉換器

系統轉換器類型

   

Int32Converter

32位有符號整數對象與其他表示形式相互轉換

Int64Converter

64位有符號整數對象與各種其他表示形式相互轉換

Int16Converter

16位有符號整數對象與各種其他表示形式相互轉換

續表 

系統轉換器類型

   

ByteConverter

字節類型與其他類型相互轉換

BooleanConverter

Boolean對象與其他各種表示形式相互轉換

CharConverter

Unicode字符對象與各種其他表示形式相互轉換

UnitConverter

Unit對象轉換爲其他數據類型的對象,或從其他類型轉換爲 UNIT對象

EnumConverter

Enum對象與其他各種表示形式相互轉換

DateTimeConverter

將日期類型與其他類型相互轉換

DecimalConverter

Decimal對象與其他各種表示形式相互轉換

StringConverter

在字符串對象與其他表示形式之間實現相互轉換

DoubleConverter

將雙精度對象與其他各種表示形式相互轉換

SingleConverter

將單精度浮點數字對象與各種其他表示形式相互轉換

TimeSpanConverter

TimeSpan對象與其他表示形式相互轉換

WebColorConverter

在預定義的顏色名稱或RGB顏色值與System.Drawing.Color 對象之間相互轉換

ArrayConverter

Array對象與其他各種表示形式相互轉換

CollectionConverter

將集合對象與各種其他表示形式相互轉換

ExpandableObjectConverter

在可擴展對象與其他各種表示形式之間實現轉換

GuidConverter

Guid對象與其他各種表示形式相互轉換

BaseNumberConverter

爲非浮點數字類型提供基類型轉換器,上面幾個整型轉換器就是從此類派生的

ReferenceConverter

將對象引用與其他表示形式相互轉換

TypeListConverter

以可用類型填充列表框的類型轉換器

ObjectConverter

Object類型與其他類型相互轉換

PropertyConverter

用於在屬性值和字符串之間進行轉換的轉換器

DataBindingCollectionConverter

DataBindingCollection 對象的類型轉換器

DataFieldConverter

可從當前組件的選定數據源中檢索數據字段的列表

DataMemberConverter

可從當前組件選定的數據源中檢索數據成員的列表

DataSourceConverter

數據源類型轉換器

CursorConverter

Cursor對象與其他各種表示形式相互轉換

FontNamesConverter

包含字體名稱列表的字符串轉換爲包含個別名稱的字符串數組,它還執行反轉功能

FontUnitConverter

轉換字體單位類型

StringArrayConverter

在以由逗號分隔的值組成的字符串與字符串數組之間進行轉換

ControlIDConverter

控件ID列表轉換器,4.6.1.3小節已經作過示例

TargetConverter

將從Web導航產生的內容的位置(目標)的值轉換爲字符串。該類還將字符串轉換爲目標值

ValidatedControlConverter

初始化ValidatedControlConverter類的新實例

4.6.2  定製自己的類型轉換器

系統已經提供了很多的類型轉換器,能夠滿足一般情況下開發的需要。但開發控件時,並不是所有的屬性類型都是那些簡單的且系統已知的int, string等類型,即控件的屬性類型可以是我們定義的任意類型,因此係統不能夠自動檢測到該使用哪個類型轉換器,這種情況就需要我們爲自己的屬性定製專門的類型轉換器。

實現自己的類型轉換器,一般需要以下5個步驟:

 定義一個從TypeConverter派生的類,TypeConverter類提供了將值的類型轉換爲其他類型,以及訪問標準值和子屬性的統一方法。其主要是重載類的一些正反向轉換方法。

 重寫CanConvertFrom方法,在方法中指定是否可以從字符串轉換爲指定的類型。

 重寫ConvertFrom方法,實現從字符串到指定類型的轉換。

 重寫CanConvertTo方法,指定是否能從SolidCoordinate類轉換爲stringInstanceDescriptor類型InstanceDescriptor是提供創建對象實例所需信息的類。轉換爲字符串類型不需要重寫此方法。

 重寫ConvertTo方法,實現轉換。

其中上面2345都是重載方法。下面就以兩個例子說明類型轉換器的創建過程。

4.6.2.1  三維座標類型轉換器

家都知道在.NET Framework中有Point類,如果把該類作爲屬性的類型,則系統會自動調用它的類型轉換器進行類型轉換。比如在屬性窗口中設置屬性值,切換到源代碼視圖時即調用類型轉換器進行轉換;或在運行時控件狀態或視圖狀態對存儲的對象進行序列化和反序列化。

 

這裏我們定義一種新的座標類型SolidCoordinate類,併爲其定義匹配的類型轉換器,以此說明如何自定義和使用類型轉換器。

來看一下實現後的效果,在屬性窗口中設置SolidCoordinate類型的屬性,如圖4-31所示。

 

圖4-31 三維座標類型轉換器

 

然後,切換到源代碼視圖,則會看到如下代碼:

<cc1:CustomeTypeConverterControl ID="CustomeTypeConverterControl1" runat= "server" SolidCoordinate="3, 5, 8" />

在切換到源代碼視圖時,轉換器類就起作用了,它會把 SolidCoordinate轉換成字符串類型,因爲在源代碼模式下所有代碼類型只能以字符串格式存在,所以要求轉換爲字符串格式;反之,會把字符串逆向轉換爲SolidCoordinate類。這就是類型轉換器的功能。

格式SolidCoordinate="3,5,8"是可以自定義的,比如可以定義成SolidCoordinate="3-5-8"格式,規則可以在轉換器類中任意指定,只要是字符串格式且保證正反向轉換規則一致即可。

接下來開始講解代碼部分。SolidCoordinate類共有三個屬性(XYZ),前兩個值(XY)與Point類型的(XY)屬性一致,表示平面上的橫座標和縱座標;(Z)屬性表示平面之外的第三維座標,類代碼如下:

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

 

[TypeConverter(typeof(SolidCoordinateConverter))]

public class SolidCoordinate

{

    private int x;

    private int y;

    private int z;

 

    public SolidCoordinate()

    {       

    }

 

    public SolidCoordinate(int x, int y, int z)

    {

        this.x = x;

        this.y = y;

        this.z = z;

    }

 

    [NotifyParentProperty(true)]

    public int X

    {

        get

        {

            return this.x;

        }

        set

        {

            this.x = value;

        }

    }

 

    [NotifyParentProperty(true)]

    public int Y

    {

        get

        {

            return this.y;

        }

        set

        {

            this.y = value;

        }

    }

 

    [NotifyParentProperty(true)]

    public int Z

    {

        get

        {

            return this.z;

        }

        set

        {

            this.z = value;

        }

    }

}

u  類代碼就包括三個座標屬性,沒有任何方法。需要注意的是上面有句:

[TypeConverter(typeof(SolidCoordinateConverter))]

其作用是指定該類的轉換器爲SolidCoordinateConverter,即凡是SolidCoordinate類型的控件屬性都會使用此類型轉換器。

SolidCoordinateConverter類的源代碼如下:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. public class SolidCoordinateConverter : TypeConverter
  6. {
  7.     public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
  8.     {
  9.         return ((sourceType == typeof(string)) || base.CanConvertFrom(context, sourceType));
  10.     }
  11.     public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
  12.     {            
  13.         return ((destinationType == typeof(InstanceDescriptor)) ||                            base.CanConvertTo(context, destinationType));
  14.     }
  15.     public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
  16.     {
  17.         string str = value as string;
  18.         if (str == null)
  19.         {
  20.             return base.ConvertFrom(context, culture, value);
  21.         }
  22.         string str2 = str.Trim();
  23.         if (str2.Length == 0)
  24.         {
  25.             return null;
  26.         }
  27.         if (culture == null)
  28.         {
  29.             culture = CultureInfo.CurrentCulture;
  30.         }
  31.         char ch = culture.TextInfo.ListSeparator[0];
  32.         string[] strArray = str2.Split(new char[] { ch });
  33.         int[] numArray = new int[strArray.Length];
  34.         TypeConverter converter = TypeDescriptor.GetConverter(typeof(int));
  35.         for (int i = 0; i < numArray.Length; i++)
  36.         {
  37.             numArray[i] = (int)converter.ConvertFromString(context, culture,                  strArray[i]);
  38.         }
  39.         if (numArray.Length != 3)
  40.         {
  41.             throw new Exception("格式不正確!");
  42.         }
  43.         return new SolidCoordinate(numArray[0], numArray[1], numArray[2]);
  44.     }
  45.     public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
  46.     {
  47.         if (destinationType == null)
  48.         {
  49.             throw new Exception("目標類型不能爲空!");
  50.         }
  51.         if (value is SolidCoordinate)
  52.         {
  53.             if (destinationType == typeof(string))
  54.             {
  55.                 SolidCoordinate solidCoordinate = (SolidCoordinate)value;
  56.                 if (culture == null)
  57.                 {
  58.                     culture = CultureInfo.CurrentCulture;
  59.                 }
  60.                 string separator = culture.TextInfo.ListSeparator + " ";
  61.                 TypeConverter converter=TypeDescriptor.GetConverter(typeof(int));
  62.                 string[] strArray = new string[3];
  63.                 int num = 0;
  64.                 strArray[num++] = converter.ConvertToString(context, culture,                    solidCoordinate.X);
  65.                 strArray[num++] = converter.ConvertToString(context, culture,                    solidCoordinate.Y);
  66.                 strArray[num++] = converter.ConvertToString(context, culture, solidCoordinate.Z);
  67.                 return string.Join(separator, strArray);
  68.             }
  69.             if (destinationType == typeof(InstanceDescriptor))
  70.             {
  71.                 SolidCoordinate solidCoordinate2 = (SolidCoordinate)value;
  72.                 ConstructorInfo constructor = typeof(SolidCoordinate). GetConstructor(new Type[] { typeof(int), typeof(int), typeof(int) });
  73.                 if (constructor != null)
  74.                 {
  75.                     return new InstanceDescriptor(constructor, new object[] { solidCoordinate2.X, solidCoordinate2.Y, solidCoordinate2.Z });
  76.                 }
  77.             }
  78.         }
  79.         return base.ConvertTo(context, culture, value, destinationType);
  80.     }
  81.     public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
  82.     {
  83.         return true;
  84.     }        
  85.     public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
  86.     {
  87.         if (propertyValues == null)
  88.         {
  89.             throw new Exception("屬性值不能爲空!");
  90.         }
  91.         object obj2 = propertyValues["X"];
  92.         object obj3 = propertyValues["Y"];
  93.         object obj4 = propertyValues["Z"];
  94.         if (((obj2 == null) || (obj3 == null) || (obj4 == null)) || (!(obj2 is 
  95.             int) || !(obj3 is int) || !(obj4 is int)))
  96.         {
  97.             throw new Exception("格式不正確!");
  98.         }
  99.         return new SolidCoordinate((int)obj2, (int)obj3, (int)obj4);
  100.     }
  101.     public override bool GetPropertiesSupported(ITypeDescriptorContext context)
  102.     {
  103.         return true;
  104.     }
  105.     public override PropertyDescriptorCollection GetProperties (ITypeDescriptor Context context, object value, Attribute[] attributes)
  106.     {
  107.         return TypeDescriptor.GetProperties(typeof(SolidCoordinate), attributes). Sort(new string[] { "X""Y""Z" });
  108.     }    
  109. }

SolidCoordinateConverter

類繼承於類型轉換器基類TypeConverter,主要重寫TypeConverter類的一些可重寫方法,從而實現自定義的類型轉換器。

方法CanConvertFrom具有兩個參數,第一個參數表示當前上下文變量,通過此參數可以獲取當前容器、當前類實例和屬性描述等信息;第二個參數sourceType表示當前要轉換的類型。在這裏,此方法主要判斷是否能從第二個參數的類型(源類型)轉換爲SolidCoordinate類型,如果源類型是字符串,則返回true,否則調用基方法,由基方法來決定返回值。

方法CanConvertTo同樣也具有兩個參數,第一個參數與上面CanConvertTo相同意義;第二個參數destinationType表示要轉化到的目標類型。該方法在這裏表示是否能夠把SolidCoordinate轉換爲destinationType類型。方法體中的InstanceDescriptor類表示實例描述類,提供創建對象所需的信息,在後面ConvertTo方法中會詳細介紹。切換到源代碼視圖時所有代碼都是以string類型標記的。

方法ConvertFrom具有三個參數,第一個參數爲ITypeDescriptorContext類型的context與前面CanConverFrom中參數具有相同意義;第二個參數爲CultureInfo類型的culture,提供有關特定區域性的信息(如區域性的名稱、書寫系統和使用的日曆),以及設置日期和字符串排序的格式。CultureInfo類保存區域性特定的信息,如關聯的語言、子語言、國家/地區、日曆和區域性約定。此類還提供對DateTimeFormatInfoNumberFormatInfoCompareInfoTextInfo的區域性特定實例的訪問,在方法ConverFrom中使用到了訪問它的TextInfo信息;第三個參數爲objectvalue,表示要進行轉換的類型,這裏是要把value轉換爲SolidCoordinate類型。

ConverFrom方法體中代碼部分主要實現從源類型(第三個參數value)到類型SolidCoordinate的轉換,最後返回一個SolidCoordinate類型的實例。value在這裏其實是一個“3,5,8”格式的字符串。首先根據區域文化對象獲取到分隔符:

char ch = culture.TextInfo.ListSeparator[0];

分隔符也可以直接固定爲逗號,如char ch =",";既然在curtureTextInfo對象中提供了分隔符,我們就直接使用它定義的。在TextInfo下的ListSeparator是一個分隔符列表數組,第一項爲",",所以我們就取它的ListSeparator[0] 作爲分隔符。

u  然後把value值通過分隔符拆分到一個字符串數組中:

string[] strArray = str2.Split(new char[] { ch });

u  由於SolidCoordinate類的三個屬性(X,Y,Z)都是整型的,因此我們再定義一個整型數組numArray,並使用系統的int類型轉換器的方法把字符串數組中的每項都轉換爲整型存儲到整型數組中:

int[] numArray = new int[strArray.Length];

TypeConverter converter = TypeDescriptor.GetConverter(typeof(int));

for (int i = 0; i < numArray.Length; i++)

{

    numArray[i]=(int)converter.ConvertFromString(context,culture,strArray[i]);

}

注意上面TypeDescriptor的靜態方法GetConverter是怎樣使用的,系統轉換器和我們定義的任何轉換器都是可以這麼使用。

最後,返回一個SolidCoordinate類型的實例:

return new SolidCoordinate(numArray[0], numArray[1], numArray[2]);

這就是ConvertFrom方法的實現過程。在這裏面的轉換規則是任意定義的,只要與ConvertTo規則一致即可。

下面我們來講解一下ConvertTo方法的實現邏輯。ConverTo方法的前三個參數與ConvertFrom方法中的前三個參數表示的意義相同;第四個參數表示要轉換到的目標類型:stringInstanceDescriptorConverTo代碼中的邏輯與ConvertFrom中的是一個相反轉換的過程,即把SolidCoordinate轉換成stringInstanceDescriptor,代碼邏輯部分就不再詳細闡述。

這裏僅說一下目標類型爲類InstanceDescriptor的情況,什麼時候需要轉換爲InstanceDescriptor.NET框架中提供了在設計時生成可動態配置的屬性初始化代碼的功能。開發人員可以構建一個產生基於構造函數的初始化代碼的類型轉換器。爲了在運行時配置類型屬性,這些類型轉換器可以使用在設計時設置的值來動態生成構造函數代碼。當某個屬性是可讀寫時,就需要將屬性的類型轉換成InstanceDescriptor類型,並且解析器也需要產生創建該類型實例的代碼,InstanceDescriptor對象提供了用來創建以參數類型傳遞給ConvertTo方法的SolidCoordinate實例的構造器的有關信息。這些信息是解析器產生創建SolidCoordinate類型的一個實例的代碼時所使用的。在構造函數中的使用示例:

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

 

public class InstanceDescriptorControl : Control

{

    SolidCoordinate solidCoordinate;

    public InstanceDescriptorControl() {

        solidCoordinate = new SolidCoordinate(1,2,3);

    }

    public SolidCoordinate SolidCoordinate {

        get

        {

            return solidCoordinate;

        }

        set

        {

            solidCoordinate = value;

        }

    }

}

u  上面代碼中SolidCoordinate類型的屬性SolidCoordinate是可讀寫的(同時具有getset語句),這就需要生成InstanceDescriptor實例的代碼。也就是說,如果使用了set語句,但這句:

if (destinationType == typeof(InstanceDescriptor))

{

    //… …

}

沒有實現,這樣編譯後的控件所在的頁面編譯時不會通過。讀者可以打開本書隨書光盤中對應的源代碼,把上段代碼進行註釋後,測試一下,理解起來會更加深刻。

方法GetCreateInstanceSupported返回bool類型。如果更改此對象的屬性需要調用CreateInstance 來創建新值,則返回true;否則返回false

方法CreateInstance根據上下文和指定的屬性字典創建實例。IDictionary類型的參數propertyValues是一個屬性字典集合,存儲了創建對象所需的值。例如:

object obj2 = propertyValues["X"];

object obj3 = propertyValues["Y"];

object obj4 = propertyValues["Z"];

u  就是從字典集合中取得三個座標值。最後根據三個座標值返回SolidCoordinate類型的對象,如下所示:

return new SolidCoordinate((int)obj2, (int)obj3, (int)obj4);

CreateInstance一般用於不可變但希望爲其提供可變屬性的對象。

接下來方法GetPropertiesSupported表示使用指定的上下文返回該對象是否支持單獨設置屬性功能。如果返回true,則屬性窗口中屬性佈局如圖4-32所示。如果返回false,則屬性佈局如圖4-33所示。

            

 

4-32  返回true時的屬性佈局                   

 

4-33  返回false時的屬性佈局

 

GetProperties是對GetPropertiesSupported=true時的屬性列表的實現邏輯。代碼就一句:

return TypeDescriptor.GetProperties(typeof(SolidCoordinate), attributes). Sort(new string[] { "X", "Y", "Z" });

取得SolidCoordinate的屬性,並以attributes數組爲篩選條件,取得屬性後依次以XYZ的順序進行排序,最後返回排序後的PropertyDescriptorCollection類型的對象。

類型轉換器類SolidCoordinateConverter已經講解完畢,可能一些重載方法理解起來比較費勁,在開始學習只要理解CanConvertFromCanConvertToConvertFrom,ConvertTo就可以了,最常用的也是這幾個方法;其他的幾個重載方法GetCreateInstanceSupportedCreateInstanceGetPropertiesSupportedGetProprties可以在開發過程中逐步理解,這裏只是說明它們的功能,事實上對SolidCoordinate轉換器示例只需要重寫CanConverterFrom, ConvertFrom等前面幾個方法就夠了。

最後實現主控件,代碼如下:

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

 

[DefaultProperty("SolidCoordinate")]

[ToolboxData("<{0}:SolidCoordinateTypeConverterControl runat=server></{0}:SolidCoordinateTypeConverterControl>")]

public class SolidCoordinateTypeConverterControl : WebControl

{

    SolidCoordinate solidCoordinate;

    public SolidCoordinate SolidCoordinate

    {

        get

        {

            if (solidCoordinate == null)

            {

                solidCoordinate = new SolidCoordinate();

            }

            return solidCoordinate;

        }

        set

        {

            solidCoordinate = value;

        }

    }

    //… …

}

u  主控件僅包含一個SolidCoordinate類型的屬性。在屬性上面並沒有如下代碼:

[TypeConverter (typeof(SolidCoordinateConverter))]

關聯類型轉換器的語句,因爲在SolidCoordinate類中指定了,這裏就不必再指定。在屬性前面和屬性類型前面指定轉換器的作用是相同的,但它們有兩點區別:

1)在類中指定類型轉換器一般用於一對一的時候,即SolidCoordinateConverter就是隻用於SolidCoordinate類型的轉換器。

2)在屬性前面指定轉換器一般用於此屬性可能(不是必須)使用某個轉換器的時候,換句話說就是此屬性或許還使用其他的轉換器,或不需要轉換器;或者此屬性的類型爲系統類型,不是我們定義的類型,這時候我們也沒有機會爲類指定轉換器,只能在屬性上指定轉換器,下節講的集合列表類型轉換器就是如此。另外,這樣指定的好處是更加靈活。

 

 

編譯主控件,並將控件放置到頁面中,在頁面設計器屬性窗口就可以看到我們已經實現的轉換器屬性,如圖4-33所示。

 

 

4-33  轉換器屬性

 

然後,切換到源代碼視圖,則會看到如下代碼:

<cc1:CustomeTypeConverterControl ID="CustomeTypeConverterControl1" runat= "server" SolidCoordinate="3, 5, 8" />

在源代碼碼下修改屬性值,再切換到設計器屬性窗口中,也會看到值已經被修改了。

4.6.2.2  集合列表類型轉換器

一般常用的自定義類型轉換器有以下兩類:

1)值翻譯的類型轉換器。

2)提供集合列表展示形式的類型轉換器。

 

 

上一節我們實現的SolidCoordinate類的類型轉換器就屬於值翻譯類型轉換器,這一節我們來實現第二種類型的轉換器。還是以顯示食品列表爲例,利用自定義轉換器製作一個顯示集合列表的屬性,圖4-34是實現後的效果。

 

 

4-34  集合類型轉換器

 

下面講解一下集合轉換器的實現,還是以代碼爲主展開講解。請見如下代碼:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. public class CustomCollectionPropertyConverter : StringConverter
  6. {
  7.         public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
  8.         {
  9.             return true;
  10.         }
  11.         
  12.         public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
  13.         {
  14.             return false;
  15.         }        
  16.         
  17.         public override StandardValuesCollection GetStandardValues(ITypeDescriptor Context context)
  18.         {
  19.             string[] strArray = new string[]{"水果","蔬菜","肉食","麪食","蛋類"};
  20.             StandardValuesCollection returnStandardValuesCollection = new                    StandardValuesCollection(strArray);
  21.             return returnStandardValuesCollection;
  22.         }
  23. }

上面代碼這次沒有使用

TypeConverter基類,而是使用的StringConverter類,把此類作爲轉換器基類進行擴展,實現我們需要的功能。StringConverter提供三個可重寫方法,可以在這三個方法中定義自己的代碼邏輯,實現集合列表。三個方法都只有一個ITypeDescriptorContext類型的參數,通過此參數可以獲取當前容器、當前類實例和屬性描述等信息。

方法GetStandardValuesSupported根據上下文參數對象返回是否支持從列表中選取標準值集,顯然這裏我們要設置返回true

方法GetStandardValuesExclusive指定返回標準值的集合是否爲獨佔列表。即如果設置爲獨佔列表(返回true),屬性值只能從集合下拉列表中選擇;反之,如果設置爲非獨佔列表(返回false),則屬性值既可以從下拉列表中選擇,也可以手動輸入(即與默認string類型屬性輸入方式相同)。讀者可以打開隨書光盤中對應的源代碼把此屬性設置爲獨佔列表模式,看一下效果,可以加深對該方法的理解。

方法GetStandardValues是設置集合列表的主要方法,先定義了一個數組:

string[] strArray = new string[] { "水果", "蔬菜", "肉食", "麪食", "蛋類" };

u  然後,以當前數組對象作爲參數定義返回值類型集合,並把此集合作爲方法返回值。代碼如下:

StandardValuesCollection returnStandardValuesCollection = new

StandardValuesCollection(strArray);

return returnStandardValuesCollection;

這樣系統就會把此返回值集合作爲屬性下拉集合列表的填充數據。注意這裏的返回值集合類型爲System.ComponentModel.TypeConverter.StandardValuesCollection,是類型轉換器專門提供的一種集合類型。

接下來講解主控件的實現,其核心源代碼如下:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. [DefaultProperty("SelectFood")]
  6. [ToolboxData("<{0}:CustomCollectionPropertyConverterControl runat=server></{0}:CustomCollectionPropertyConverterControl>")]
  7. public class CustomCollectionPropertyConverterControl : WebControl
  8. {
  9.     private string strSelectFood;
  10.     [TypeConverter(typeof(CustomCollectionPropertyConverter))]
  11.     public string SelectFood
  12.     {
  13.         get
  14.         {
  15.             return strSelectFood;
  16.         }
  17.         set
  18.         {
  19.             strSelectFood = value;
  20.         }
  21.     }
  22.     //… …
  23. }

主控件包含一個

string類型的屬性,並指定了此屬性的類型轉換器爲我們剛剛已經實現的轉換器CustomCollectionPropertyConverter

 

這裏我們指定類型轉換器的位置與上節有所不同:在這裏我們只能在屬性上指定屬性的類型轉換器,因爲string類是系統基本類型,我們沒有機會在System.StringString.String的別名即是string類上指定轉換器,除非改寫string類。另外,這種方式的靈活性在於我們可以爲此屬性指定其他任意類型的轉換器,僅需修改一下 [TypeConverter(typeof(C))] C位置的類型即可。

編譯主控件,並將該控件放置到頁面中,在屬性窗口中就會看到我們實現的集合列表如圖4-35所示。

 

 

4-35  集合列表

 

本節內容到此結束。本節以兩個例子(值翻譯的類型轉換器和提供集合列表形式的類型轉換器)說明了自定義類型轉換器的實現方案。

4.7  實現自定義屬性

在本章前面的章節中我們實現的許多功能都是藉助於系統提供的設計時元數據屬性支持實現的,如:

Ø 顯示屬性描述信息的Description屬性

[Description("描述文本")]

Ø 指定屬性在屬性窗口中類別的Category設計屬性

[Category("屬性窗口中的類別名稱")]

Ø 指定屬性編輯器的Editor設計屬性

[Editor(typeof(FileNameEditor), typeof(UITypeEditor))]

Ø 指定屬性類型轉換器的TypeConverter設計屬性

[TypeConverter(typeof(SolidCoordinateConverter))]

這些都是系統提供的,每個屬性都有它自己的功能。那麼如果我們想自定義一個設計屬性,實現自定義的功能,該怎麼實現呢?要設計自定義屬性,僅需要直接或間接地從System.Attribute派生即可,與傳統類功能完全一樣。我們既可以使用System.Attribute來定義控件設計期控制,也可以用System.Attribute指定運行時的控制。

本節就以一個示例演示控件中的設計屬性是怎麼實現的。首先還是先說明一下本節控件屬性要實現的功能,如圖4-35所示。

此控件具有一個Num屬性,允許開發人員指定一個值。在控件的屬性上指定了一個NumValidate類型的驗證屬性,該屬性需要傳入兩個參數,供開發人員指定區間,如果在屬性窗口中指定的屬性不在區間內,則會有警告提示(如圖4-36中紅色警告提示)。

 

4-36  控件設計屬性示例

或許讀者會想這不就是一個驗證輸入的簡單功能嗎?這裏與我們之前做的驗證的情況是不一樣的。之前我們經常用的驗證方式是頁面在瀏覽器中運行時的驗證,即對用戶輸入的驗證;而這裏是在IDE設計器界面驗證,是在控件設計模式下的驗證,是對使用控件的開發人員進行輸入驗證,而不是對最終用戶輸入驗證。

本示例就是用來講解怎樣在設計模式下設置和獲取我們自己定義的設計屬性。還是從代碼開始,NumValidate屬性的源代碼如下:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. /// <summary>
  6. ///  自定義屬性類(實現驗證功能)
  7. /// </summary>
  8. [AttributeUsage(AttributeTargets.Property,AllowMultiple=true,Inherited=true)]
  9. public class NumValidateAttribute : Attribute
  10. {
  11.     /// <summary>
  12.     ///  構造方法
  13.     /// </summary>
  14.     /// <param name="intMinValue">最小值</param>
  15.     /// <param name="intMaxValue">最大值</param>
  16.     public NumValidateAttribute(int intMinValue, int intMaxValue)
  17.     {
  18.         this.intMinValue = intMinValue;
  19.         this.intMaxValue = intMaxValue;
  20.     }
  21.     private int intMinValue;
  22.     /// <summary>
  23.     /// 最大值
  24.     /// </summary>
  25.     public int MinValue
  26.     {
  27.         get 
  28.         { 
  29.             return intMinValue; 
  30.         }            
  31.     }
  32.     private int intMaxValue;
  33.     /// <summary>
  34.     /// 最小值
  35.     /// </summary>
  36.     public int MaxValue
  37.     {
  38.         get 
  39.         { 
  40.             return intMaxValue; 
  41.         }            
  42.     }
  43.     /// <summary>
  44.     /// 執行驗證
  45.     /// </summary>
  46.     /// <param name="value"></param>
  47.     /// <returns></returns>
  48.     public bool ValidateResult(int value)        
  49.     {
  50.         if (this.intMinValue <= value && value <= this.intMaxValue)
  51.         {
  52.             return true;
  53.         }
  54.         else
  55.         {
  56.             return false;
  57.         }
  58.     }
  59. }

代碼中驗證類名爲

NumValidateAttribute,從System.Attribute派生而來,有些時候根據需要間接繼承System.Attribute也是可以的。

類中必須有一個帶兩個參數的構造方法,分別表示驗證範圍的最大值和最小值,然後定義兩個屬性:MinValueMaxValue,分別存儲驗證範圍的最小值和最大值。最後面的方法ValidateResult是主要的驗證方法,它有一個參數,表示要驗證的值,如果此值在最大值和最小值區間,則返回true,表示是合法輸入;否則,返回false,表示驗證失敗。

驗證類NumValidateAttribute上方有一個很重要的設計時屬性:

[AttributeUsage(AttributeTargets.Property,AllowMultiple=true,Inherited=true)]

AttributeUsage完整命名爲System.AttributeUsageAttribute,它包含三個對自定義屬性的創建具有重要意義的成員:AttributeTargetsAllowMultipleInherited。該類主要說明NumValidateAttribute的用法(作用於類還是屬性,是否允許被繼承,等等)。我們只需要指定各個參數即可,如果使用位置不正確,系統會自動提示警告信息。

AttributeTargets指定可以對應用程序的哪些元素應用此屬性。在前面的示例中指定了AttributeTargets.Property,指示該屬性只可以應用到類中的屬性,還可以指定AttributeTargets. Class,表示屬性只可以應用於類元素;或指定AttributeTargets.Method,表示屬性只可以應用於某個方法。還可通過 "|" 設置AttributeTargets的多個實例。下列代碼段指定自定義屬性可應用到任何類或方法:

[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]

u  如果某個屬性可以應用到 AttributeTargets下的所有類型,則需要作如下設置即可:

[AttributeUsage (AttributeTargets.All)]

u  AttributeUsage枚舉類應用的範圍不僅僅包括類和屬性,共有15個可應用元素,下面是它的源代碼:

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

 

// 摘要:

//     指定哪些應用程序元素可以對它們應用屬性

[Serializable]

[ComVisible(true)]

[Flags]

public enum AttributeTargets

{

    // 摘要:

    //     可以對程序集應用屬性

    Assembly = 1,

    //

    // 摘要:

    //     可以對模塊應用屬性

    Module = 2,

    //

    // 摘要:

    //     可以對類應用屬性

    Class = 4,

    //

    // 摘要:

    //     可以對結構應用屬性,即值類型

    Struct = 8,

    //

    // 摘要:

    //     可以對枚舉應用屬性

    Enum = 16,

    //

    // 摘要:

    //     可以對構造函數應用屬性

    Constructor = 32,

    //

    // 摘要:

    //     可以對方法應用屬性

    Method = 64,

    //

    // 摘要:

    //     可以對屬性 (Property) 應用屬性 (Attribute)

    Property = 128,

    //

    // 摘要:

    //     可以對字段應用屬性

    Field = 256,

    //

    // 摘要:

    //     可以對事件應用屬性

    Event = 512,

    //

    // 摘要:

    //     可以對接口應用屬性

    Interface = 1024,

    //

    // 摘要:

    //     可以對參數應用屬性

    Parameter = 2048,

    //

    // 摘要:

    //     可以對委託應用屬性

    Delegate = 4096,

    //

    // 摘要:

    //     可以對返回值應用屬性

    ReturnValue = 8192,

    //

    // 摘要:

    //     可以對泛型參數應用屬性

    GenericParameter = 16384,

    //

    // 摘要:

    //     可以對任何應用程序元素應用屬性

    All = 32767,

}

 

u  AllowMultiple屬性指示元素中是否可存在屬性的多個實例。該屬性爲bool類型,默認值爲false,標識我們自定義的Attribte是否能在同一元素上使用多次;如果設置爲false,則同一語言元素上只能使用一次。如果設置爲AllowMultiple=true,則屬性可以這麼使用:

 

  1. /// <summary>
  2. /// 獲得本書更多內容,請看:
  3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
  4. /// </summary> 
  5. [CustomAttribute]
  6. [CustomAttribute]
  7. public void Method()
  8. {
  9.     //……
  10. }

最後一個參數

Inherited,也是bool類型的。我們可以使用該屬性來控制我們的自定義attribute類的繼承規則,該屬性標識我們的自定義Attribute在應用到元素(別名A)時,是否可以由派生類(繼承於A所屬的類)繼承。如果設置爲true,則可以被繼承;反之,不可以被繼承。

更深一點理解,AllowMultiple Inherited還可以組合使用,完成我們需要的設置功能。如果AllowMultiple=true,且Inherited=true,且基類AB類(繼承A類)中都有相同名稱的一個屬性,則實際上B類中有兩個相同名稱的屬性,即兩個不同的實例,自己定義的一個和從基類繼承來的一個;如果設置AllowMultiple=false, Inherited=true,且基類AB(繼承A類)中都有相同名稱的一個屬性,則這時B類中只有一個屬性,即自己定義的實例,因爲AllowMultiple=false指定不允許多實例並存,系統只能用B類定義的屬性重寫A類的屬性定義。如果不按正確方式使用,編譯程序會提示警告信息。

到此自定義屬性類就已經講解完了,下面講解一下此屬性類是怎麼使用的。筆者專門做了個控件來講解屬性類在控件中是怎麼使用的,主控件源代碼如下:

/// <summary>

/// 獲得本書更多內容,請看:

/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

/// </summary>

 

[DefaultProperty("Num")]

[ToolboxData("<{0}:CustomPropertyControl runat=server></{0}:CustomPropertyControl>")]

public class CustomPropertyControl : WebControl

{

    TextBox tb;

    int intNum = 0;

    [Category("Appearance")]  

    [NumValidate(0, 10)]

    [Description("輸入值範圍(0~10)")]

    public int Num

    {

        get

        {                

            return intNum;

        }

 

        set

        {

            intNum = value;

        }

    }

   

    protected override void Render(HtmlTextWriter writer)

    {

        Table t = new Table();

        t.CellPadding = 0;

        t.CellSpacing = 0;

        TableRow tr = new TableRow();

 

        TableCell td_left = new TableCell();

        tb = new TextBox();

        tb.Text = this.intNum.ToString();

        td_left.Controls.Add(tb);

        tr.Controls.Add(td_left);

 

        NumValidateAttribute numValidateAttribute = this.GetNumValidate Arribute();

        if (numValidateAttribute.ValidateResult(this.Num) == false)           

        {

            TableCell td_right = new TableCell();

            Label lb = new Label();

            lb.ForeColor = System.Drawing.Color.Red;

            lb.Text = "值輸入範圍必須在:" + numValidateAttribute.MinValue. ToString

            () + "~" + numValidateAttribute.MaxValue.ToString() + "之間!";

            td_right.Controls.Add(lb);

            tr.Controls.Add(td_right);

        }

        t.Controls.Add(tr);

 

        t.RenderControl(writer);

    }

 

    private NumValidateAttribute GetNumValidateArribute()

    {

        System.Type type = this.GetType();

        PropertyInfo property = type.GetProperty("Num");

        object[] attrs = (object[])property.GetCustomAttributes(true);     

        foreach (Attribute attr in attrs)

        {

            if (attr is NumValidateAttribute)

            {

                return attr as NumValidateAttribute;

            }

        }

        return null;

    }

}

 

u  主控件輸出一個文本框,開發人員在設計器屬性窗口中可以修改文本框的值。控件中的屬性Num,就是用來存儲文本框的值的。Num屬性使用了我們剛剛定義的自定義屬性驗證證類NumValidateAttribute

[NumValidate(0, 10)]

注意在使用時,關鍵字使用的不是我們定義的完整類名。實際上這裏使用 [NumValidate(0, 10)] [NumValidateAttribute(0, 10)] 都是可以的,系統都會把後綴Attribute省略,而且智能提示也會省略Attribute,因此我們在使用時也把Attribute省略了。

我們設置NumValidate屬性並傳入兩個參數:010,以及Num的類型爲int類型,表示要求這個Num屬性必須是0 ~ 10之間的整型數值。在設計模式和運行模式執行時,系統會把010作爲NumValidateAttribute類的構造參數創建實例。

接下來是Render方法,在Render方法中主要執行兩個功能:一是輸出TextBox內容,並嵌套到Table對象中;二是實現對設計期間的輸入驗證。驗證過程代碼如下:

 

  1. NumValidateAttribute numValidateAttribute = this.GetNumValidateArribute();
  2. if (numValidateAttribute.ValidateResult(this.Num) == false)            
  3. {
  4.     TableCell td_right = new TableCell();
  5.     Label lb = new Label();
  6.     lb.ForeColor = System.Drawing.Color.Red;
  7.     lb.Text = "值輸入範圍必須在:" + numValidateAttribute.MinValue.ToString() + "~" + numValidateAttribute.MaxValue.ToString() + "之間!";
  8.     td_right.Controls.Add(lb);
  9.     tr.Controls.Add(td_right);
  10. }

以上代碼首先獲取我們在屬性上指定的自定義驗證屬性實例對象,然後以主控件的

Num值作爲參數,調用驗證屬性對象的ValidateResult方法進行輸入合法驗證,如果當前輸入值驗證失敗(Num在我們設定的0~10之間,這裏僅模擬驗證邏輯),則會在當前行中增加一個單元格,並在單元格中增加一個顯示出錯警告的Label控件,告知開發人員合法輸入值正確範圍;反之,驗證成功,則不會呈現出單元格和提示警告信息Label控件。

接下來說明自定義屬性對象實例是怎樣獲取到的,方法GetNumValidateArribute獲取屬性對象的代碼,其使用反射機制取得。代碼如下:

System.Type type = this.GetType();

PropertyInfo property = type.GetProperty("Num");

u  先取得當前控件類的類型,再根據類型調用以Num屬性爲參數,調用GetProperty方法取得屬性的PropertyInfo對象propertyPropertyInfo可以提供對屬性元數據的訪問。代碼如下:

 

  1. object[] attrs = (object[])property.GetCustomAttributes(true);      
  2. foreach (Attribute attr in attrs)
  3. {
  4.     if (attr is NumValidateAttribute)
  5.     {
  6.         return attr as NumValidateAttribute;
  7.     }
  8. }
  9. return null;

通過

PropertyInfo對象實例的GetCustomAttributes方法獲取Num屬性的所有自定義屬性類實例,放到attrs數組中,再循環遍歷數組中每個實例的類型,如果找到有NumValidateAttribute類型的對象實例,則返回此實例;否則,返回null

GetCustomAttributes方法主要是展示怎樣獲取屬性元素的設計屬性實例,如果自定義屬性不是應用到屬性,而是類或接口或方法等,則獲取屬性實例方式原理類似,都是通過反射機制實現。這裏不作多講,具體使用時可以查看官方文檔。

編譯主控件,並放置一個CustomPropertyControl控件到頁面中,則可以在屬性窗口中通過修改控件Num屬性的值來看一下效果,如圖4-37所示。

自定義屬性就講到這裏,在主控件類中可以通過this.GetType()方法獲取當前控件的類型,再根據類型取得屬性,進而取得屬性的設計屬性Attribute,那麼在控件構造器類、控件編輯器類或控件類型轉換器類等這樣的一些類中是怎樣獲取到當前主控件類型呢?其實讀者可能會想到了,一般在這些特殊類中都會有相應的上下文對象,比如在4.6節講解的自定義類型轉換器中,像ConvertToCanConvertFrom等方法都會有個ITypeDescriptorContext類型的上下文參數,基本上我們可能需要的所有信息都能夠通過此對象得到,如圖4-38所示。

           

 

4-37  屬性窗口效果                             

 

4-38  智能感知列表

 

如上圖所示的智能感知列表,可以看到它下面有容器對象、實例、屬性描述集合,還有屬性描述類等。

4.8  本章總結

本章內容比較多,對控件的屬性作了詳細的介紹。本章按照不同的分類方法將控件屬性分爲系統屬性和自定義屬性;簡單屬性和複雜屬性;並且分別闡述了這些屬性類別的定義和屬性的設計時特徵。其中重點講解了複雜屬性,包括各種複雜屬性標記的實現方式,及通過高級功能AddParsedSubObjectControlBuilder實現自定義的複雜屬性。一個使用方便的控件不僅要具備豐富的屬性以實現靈活的控制,還應具備友好的屬性編輯器。因此本章深入講解屬性編輯器,包括系統常用編輯器和自定義編輯器。在配置屬性時常常需要用到類型轉換,本章特意用一節講解系統常用類型轉換器和自定義的兩個類型轉換器:三維座標類型轉換器和集合列表類型轉換器。本章堪稱本書的精華章節之一,請讀者務必仔細閱讀,細細體會。

 

獲得本書更多內容,請看:

    http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

 

 

 

 

 

如果您需要發問,  非常歡迎您在下面留言,  Thanks/King.

 

 

.

.

.

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章