為ASP.NET控件添加設計時支持
|
簡介
Microsoft ASP.NET 為開發人員提供了一種適用于 Web 開發的、功能最為強大的新工具:服務器控件。服務器控件使開發人員能夠在短時間內開發出響應速度快而且功能強大的 Web 應用程序,所需的時間與在典型的 ASP 中創建類似應用程序的時間差不多。
ASP.NET 服務器控件之所以能夠提供生產效率,關鍵原因之一在于它為 Microsoft Visual Studio .NET 開發環境中的服務器控件提供了豐富的設計時支持。開發人員可以將服務器控件從 Visual Studio .NET 工具箱拖放到頁面上,通過 Properties(屬性)窗口訪問它們的屬性,然后在 Visual Studio HTML 編輯器以及 ASP.NET 頁面的內含代碼的類中利用 Microsoft IntelliSense? 語句完成功能。這些設計時功能為 Web 開發帶來了快速應用程序開發 (RAD) 工具,而這些工具已被 Microsoft Visual Basic? 開發人員使用了多年。
ASP.NET 還使開發人員能夠通過創建自定義服務器控件以封裝大量可重復使用的用戶界面特定的代碼(例如登錄或注冊表單),來進一步提高生產效率。盡管開發人員已經開始意識到開發自定義控件的重要性,但許多人可能還沒有意識到還能在控件中利用 Visual Studio 設計時支持的強大功能,使這些控件能夠像 ASP.NET 中的內置控件那樣易于使用。本文將介紹 Microsoft .NET Framework 和 Visual Studio .NET 提供的設計時支持的類型,并向開發人員介紹如何構建利用這種支持的控件。
下載本文示例代碼
閱讀目錄:
設計時支持的類型
Blog 控件示例
添加設計時支持
設計視圖和元數據特性
添加工具箱支持
添加設計器
HTML視圖支持:自定義架構和 Visual Studio 注釋
小結
附錄:
列表1:Blog.vb代碼
列表 2:BlogClient.aspx代碼
列表 3:BlogClient.aspx.vb代碼
列表 4:Blog_DT.vb
列表 5:Blog.xsd
本文章的留言內容: |
設計時支持的類型
針對 Visual Studio .NET 中的服務器控件,有五種不同的設計時支持。它們是:
1、內含代碼的類中的 IntelliSense
2、設計視圖中的屬性瀏覽器支持
3、工具箱支持
4、HTML 視圖中的屬性瀏覽器支持
5、HTML 編輯器中的 IntelliSense
這些設計時支持類型是由幾個不同的機制提供的。內含代碼的類中的 IntelliSense 由 IDE 啟用,IDE 為您的控件讀取元數據以確定控件所提供的屬性和方法及其類型和參數。要啟用內含代碼的類中的 IntelliSense,只需對您的控件進行編寫和編譯,然后將其程序集放到使用該控件的應用程序的 bin 子目錄中。
Visual Studio .NET 編輯器設計視圖中的屬性瀏覽器支持通過以下兩個途徑提供:將該類型與某個屬性相關聯和/或將元數據特性與該屬性相關聯。將元數據特性(下文簡稱為特性)添加到您的代碼中,用于標識屬性的類別、提供屬性說明以及在需要時指定首選編輯器。有些類型的屬性(如 System.Drawing.Color)會自動映射到 Visual Studio .NET 中的相應編輯器中。
Visual Studio .NET 的 HTML 視圖中的 IntelliSense 和屬性瀏覽器支持通過使用一種 XSD 架構進行提供,該架構用于描述與控件相關聯的類型,它使用稱為 Visual Studio 注釋的文本修飾指定控件的首選編輯器和其他首選項。
最后,您可以通過結合特性和帶有特定屬性的自定義位圖來支持從 Visual Studio .NET 工具箱拖放控件。
Blog 控件示例
用于說明 Visual Studio .NET 中的設計時功能的控件稱作“Blog 控件”,如本文末尾的列表 1 所示。該控件提供利用 XML 作為存儲介質的簡單 Web 日志功能。Web 日志通常稱為 Blog,它實際上是一個 Web 頁面,供人們在上面張貼有關日常生活、世態百象、時事政治或人們所關心的其他問題的定期觀察報告或評論。Blog 條目是通過 Web 瀏覽器添加的。
Blog 控件非常簡單明了,它利用控件組合向瀏覽器提供輸出。在組合控件中,CreateChildControls 方法(由 ASP.NET 運行時自動調用)會被重寫,利用此方法,我們可以創建構成自定義控件 UI 的控件,并將它們添加到控件的“控件”集合中。此外,該控件還包含用于顯示和添加 Blog 以及當 XML Blog 存儲文件不存在時創建一個這樣的文件的邏輯。該控件的幾個公共屬性需要開發人員在設計時進行設置,其中包括在添加新 Blog 時該控件將重定向到的頁面的 URL、與新 Blog 關聯的電子郵件地址、控件模式(顯示或添加)以及各 Blog 條目之間的分隔線的顏色。圖 1 所示為正在運行的 Blog 控件。Add Blog(添加 Blog)超鏈接由 ASP.NET 超鏈接控件提供,獨立于 Blog 控件。BlogClient.aspx 的代碼如列表 2 所示。BlogClient.aspx 的 codebehind 類如列表 3 所示,它提供單擊 Add Blog(添加 Blog)鏈接時更改 Blog 模式的邏輯。

圖 1:運行時的 Blog 控件
圖2所示為設計時基本 Blog 控件的外觀。請注意,雖然列出了屬性,但并未分類。

圖 2:設計時的 Blog 控件
添加設計時支持
雖然在 Web 窗體頁上使用 Blog 控件非常簡單,但并不是很直觀。例如,如果沒有相關文檔,使用 Blog 控件的人就無法知道 Mode 屬性的有效值只能是 Display 或 Add。如果未將 Add 模式的相關信息明確地告訴使用該控件的開發人員,他們就很難自己發現并使用這種模式。
對于使用 Visual Studio .NET(或支持 IntelliSense 的其他 IDE)的開發人員而言,可以通過為控件添加設計時支持來解決這一問題。這可以通過綜合利用本文前面所介紹的方法來實現。在為自定義服務器控件提供設計時支持所面臨的挑戰中,部分原因來自于在自定義控件中全面支持設計時功能所需的方法的多樣性。最簡單的、不需要任何附加編碼的是內含代碼的類中的 IntelliSense 語句完成方法,如圖 3 所示,此方法適用于 BlogClient.aspx.vb。

圖 3:內含代碼的類中的 IntelliSense
遺憾的是,語句完成功能的自動支持并沒有擴展到編輯 Web 窗體頁時的設計視圖或 HTML 視圖,而且 Visual Studio 也沒有提供不需要額外的控件工作就能在屬性瀏覽器中查看和編輯屬性的內置支持。更復雜的是,要在 Web 窗體編輯器的屬性瀏覽器和設計視圖中支持 IntelliSense,需要采用一種方法,要在該編輯器的 HTML 視圖中支持 IntelliSense,則需要采用另一種方法。
要在設計視圖中支持屬性瀏覽,所需的方法是通過特性告訴 Visual Studio .NET 如何處理屬性。要在 HTML 視圖中支持語句完成和屬性瀏覽,需要生成一個自定義 XSD 架構以描述控件中的類型。我們將在下文討論這兩種方法。
設計視圖和元數據特性
Visual Studio .NET 為使用拖放技術的動態控件設計和修改提供了豐富的支持,同時還提供了屬性瀏覽器之類的工具以及相關的設計器(例如顏色選擇器)。對這些工具的支持是通過一系列特性提供的,您可以將這些特性添加到您的控件中。這些特性用于告訴 Visual Studio IDE 是否在屬性瀏覽器中顯示控件的屬性、屬性所屬的類型以及應使用哪個設計器設置屬性的值。
對于將要提供設計時支持的控件版本,我們將制作一份控件文件 Blog.vb 的副本,并將其命名為 Blog_DT.vb,然后在副本文件上進行修改。這樣可以生成該控件的設計時版本,并保留原始控件以便進行比較。
要支持在屬性瀏覽器中編輯 AddRedirect 屬性,應在屬性進程之前添加以下特性,如以下代碼片段所示:
<Browsable(True), _ Category("行為"), _ Description("成功提交新的 Blog 條目后, " & _ "應重定向到的 " & _ "頁面的 URL。"), _ Editor("System.Web.UI.Design.UrlEditor", _ GetType(UITypeEditor))> _ Public Property AddRedirect() As String '屬性進程代碼 End Property |
這些特性聲明允許在屬性瀏覽器中顯示屬性、為屬性設置所需的類別(當屬性按類別排序時)、提供屬性說明并告訴 Visual Studio .NET 使用 UrlEditor 類編輯屬性的值,如圖 4 所示。

圖 4:設計視圖中的屬性支持
此處所述的特性語法適用于 Visual Basic .NET。在 Visual Basic .NET 中,特性通過以下語法進行聲明:
<AttributeName(AttributeParams)> |
在 C# 中,特性采用如下形式:
[AttributeName(AttributeParams)]
Visual Basic .NET 要求特性聲明與其修改的成員位于同一行中,因此通常最好在特性后面跟一個 Visual Basic 行接續字符以提高可讀性:
<AttributeName(AttributeParams)> _
Public Membername()
在 C# 和 Visual Basic 中,您可以在一對 [ ] 或 <> 括號中聲明多個特性,特性之間用逗號分隔。而在 Visual Basic .NET 中,如果它們出現在不同的行中,則必須使用 Visual Basic 行接續符銜接特性,使其位于同一個語句中。
添加工具箱支持
除了設置屬性級別的特性外,還可設置某些類和程序集級別的特性。例如,您可以使用程序集級別的特性 TagPrefix 來指定標記前綴,供程序集中包含的任何控件使用。之后,當您從 Visual Studio 工具箱中向某個 Web 窗體頁上添加該控件的實例時,Visual Studio .NET 將自動插入這個標記前綴。以下代碼片段顯示了 TagPrefix 特性的語法。該特性應放置在定義該控件的類模塊內,但應在類和命名空間聲明之外(請注意,在 Visual Basic .NET 項目中,命名空間是在項目級別定義的,因此您不用擔心如何將程序集特性放置到命名空間聲明之外)。在以下特性中,TagPrefix 特性的第一個參數是控件的命名空間,第二個參數是您希望為標記前綴使用的文本。
<Assembly: TagPrefix("BlogControl", "BlogControl")> |
要將控件集成到 Visual Studio .NET 環境中,應將 ToolBoxData 特性(該特性用于告訴 Visual Studio .NET 從工具箱中為控件插入的首選標記名)添加到實現該控件的類中:
<ToolboxData("<{0}:Blog_DT runat=server></{0}:Blog_DT>")> _ Public Class Blog_DT Inherits Panel Implements INamingContainer '控件實現 End Class |
將控件從工具箱中插入到頁面上時,由 TagPrefix 特性指定的標記前綴將插入 {0} 占位符,而其他文本將按原樣插入。
您還可以為控件提供自己的自定義圖標,以顯示在工具箱中。為此,需要創建一個 16 x 16 像素大小的位圖(左下方的像素采用透明色),其名稱與包含該控件的類相同(即 classname.bmp)。使用 Add Existing Item(添加現有項)命令將該位圖添加到項目中,然后使用屬性瀏覽器將其 Build Action(創建操作)設置為 Embedded Resource(內置資源),如圖 5 所示。

圖 5:設置 Build Action(創建操作)
編譯完成后,該控件將支持從工具箱中將控件添加到某個頁面中時為 Blog 控件自動插入 @Register 指令、標記前綴和標記名,并在工具箱中顯示自定義圖標,如圖 6 所示。要將控件添加到 Visual Studio .NET 工具箱中,應完成以下簡單步驟:
1、在設計視圖中,選擇 Visual Studio .NET 工具箱的 Web forms(Web 窗體)選項卡。
在該選項卡上的任意位置單擊鼠標右鍵,然后選擇 Add/Remove Items(添加項目/刪除項目)(Visual Studio .NET 2002 中為 Customize Toolbox [自定義工具箱])。
2、選擇 .NET Framework Components(.NET Framework 組件)選項卡,然后單擊 Browse(瀏覽)。
瀏覽到編譯后的控件程序集所在的位置,選中它并單擊 Open(打開)。
3、單擊 OK(確定)。

圖 6:工具箱中的自定義控件
將控件添加到工具箱中后,可以通過雙擊該控件或將其從工具箱中拖放到 Web 窗體頁上,將其添加到 Web 窗體頁中。無論何種情況,Visual Studio .NET 都會自動插入正確的 @Register 指令(包括基于程序集級別的特性設置 TagPrefix),還將使用 ToolBoxData 屬性中指定的標記名為該控件生成一組標記。
添加設計器
正如前文所述,Blog 控件在 Web 窗體編輯器的設計視圖中沒有任何可視界面。這使得選擇頁面上的控件很困難,更難以理解控件在運行時的外觀。為了解決這個問題,我們可以添加設計器支持,使設計時的 HTML 在外觀上接近于運行時的 Blog 控件。請注意,您還可以生成可以完整再現控件運行時輸出的設計器,但此操作相當復雜,而且超出了本文的討論范圍。
所有服務器控件設計器都是從類 System.Web.UI.Design.ControlDesigner 派生而來,該類提供了大量方法,您可以重寫這些方法為您的控件提供設計時渲染。以下代碼簡單重寫了 GetDesignTimeHtml 方法,返回設計時顯示的簡單 HTML。請注意,該示例顯示了 Blog 控件的整個設計器類,您可以簡單地將其添加到現有的 Blog_DT.vb 類文件中。
Public Class BlogDesigner Inherits ControlDesigner Public Overrides Function GetDesignTimeHtml() As String Return "<h1>Blog</h1><hr/><hr/>" End Function End Class |
要將該設計器綁定到 Blog_DT 類中,我們使用了 Designer 特性,如以下片段所示。請注意,此段代碼還添加了一個描述控件功能的 Description 特性。
<Description("簡單 Blog 控件。支持顯示 " & _ "Web 日志/來自 XML 文件的新條目。"), _ Designer("BlogControl.BlogDesigner"), _ ToolboxData("<{0}:Blog_DT runat=server></{0}:Blog_DT>")> _ Public Class Blog_DT Inherits Panel Implements INamingContainer |
如您所見,BlogDesigner 類非常簡單,但它為控件在 Web 窗體頁上的設計時外觀添加了大量內容,如圖 7 所示。

圖 7:添加設計時渲染
列表 4 顯示了 Blog 控件的代碼,它已經使用特性進行了更新,以啟用設計視圖和屬性瀏覽器中的控件設計時支持。請注意,該示例添加了多條 using 指令,以導入支持我們使用的特性和設計器類所需要的命名空間。這個新列表還添加了一個用于 Mode 屬性值的枚舉。
HTML視圖支持:自定義架構和 Visual Studio 注釋
盡管前文所述的特性幫助我們為 Blog 控件提供了設計時支持,但這里遺漏了一個重要的問題:在 Web 窗體編輯器的 HTML 視圖中添加標記和特性的 IntelliSense 支持。對于那些認為在 HTML 環境中工作比在“所見即所得”風格的環境中工作更舒適的開發人員來說,這是一個極大的疏忽。
因為 Web 窗體編輯器的 HTML 視圖使用 XSD 架構決定在 Web 窗體頁上提供哪些元素和特性,所以為了糾正這一問題,我們需要提供一個描述 Blog 控件及其所支持的特性的 XSD 架構。也可以在該架構中添加注釋,告訴 Visual Studio .NET 各種元素的有關信息以及我們所希望的元素行為。
列表 5 包含 Blog 控件特定的 XSD 架構的部分內容。實際的架構文件(可從本文的示例代碼中獲得)還包含面板控件(Blog_DT 控件就是由它派生的)的類型定義以及其他必需的特性和類型定義。這些定義是從為內置 ASP.NET 服務器控件創建的 asp.xsd 架構文件中復制的。
請注意,任何時候都不應直接修改 asp.xsd 架構文件,而只應將必需的類型和特性定義復制到您的自定義架構文件中。盡管這看起來是多余的,但如果直接編輯 asp.xsd,以后安裝 .NET Framework 或服務包時該文件將被覆蓋,您的自定義輸入項將因此而丟失。
在列表 5 中,請注意根架構元素上的 targetNamespace 和 xmlns 特性,這兩個特性用于為控件的架構定義 XML 命名空間。targetNamespace 和 xmlns 特性的值還將用于 Web 窗體頁中的特性,以“綁定”該架構。<xsd:element> 標記定義根 Blog_DT 元素。<xsd:complexType> 標記定義 Blog_DT 元素的特性,包括 <xsd:attributeGroup> 標記引用的 Web 控件特性。最后,<xsd:simpleType> 標記定義 BlogMode 類型的枚舉,該類型被用作 Blog_DT 元素的一個特性。
請注意,列表 5 使用 vs:builder 注釋來告訴 Visual Studio .NET 對 AddRedirect 特性使用 URL 生成器,而對 SeparatorColor 特性使用顏色生成器。vs:builder 注釋是可用于修改架構的注釋之一。表 1 列出了最常用的注釋。
表 1:常用的 Visual Studio .NET 注釋
注釋 | 用途 | 有效值 |
vs:absolutepositioning | 在根 <schema> 元素上使用,用于確定 Visual Studio 是否可以插入用于定位的樣式特性。 | true 或 false |
vs:blockformatted | 表明是否可以在自動格式化期間為元素添加前導空格。 | true 或 false |
vs:builder | 指定用于編輯相關屬性值的生成器。 | 顏色、樣式或 URL |
vs:deprecated | 允許將某個相關屬性標記為“已否決”,以防止其在屬性瀏覽器和語句完成中出現。 | true 或 false |
vs:empty | 在元素級別使用,用于指示 Visual Studio .NET 應對相關標記(無結束標記)使用一個標記語法。 | true 或 false |
vs:friendlyname | 在根級別使用,用于為架構提供顯示名。 | |
vs:iscasesensitive | 在根級別使用,說明 Visual Studio .NET 是否以區分大小寫的方式處理相關標記。 | true 或 false |
vs:ishtmlschema | 在根級別使用,說明架構是否是一個 HTML 文檔架構。 | true 或 false |
vs:nonbrowseable | 在特性級別使用,說明該特性不應出現在語句完成中。 | true 或 false |
vs:readonly | 在特性級別使用,說明不能在屬性窗口中修改該特性。 | true 或 false |
vs:requireattributequotes | 在根級別使用,說明特性值必須用引號括起。 | true 或 false |
創建自己的 XSD 架構后,可以將其與 asp.xsd 文件保存到同一位置(在 Visual Studio .NET 2003 中,默認為 C:Program FilesMicrosoft Visual Studio .NET 2003Common7Packagesschemasxml)。
要允許 Visual Studio .NET 讀取您的自定義架構,需要將一個 xmlns 特性添加到要使用該架構的頁面的 <body> 標記中,如以下代碼片段所示:
<body xmlns:BlogControl="urn:http://www.aspnetian.com/schemas"> |
請注意,此段代碼使用具有 xmlns 特性的 BlogControl 前綴來說明該架構適用于帶有 BlogControl 標記前綴的控件,這個可以再次調用的前綴是使用 TagPrefix 特性進行設置的(有關該特性的說明,請參見上文中的“元數據特性”一節)。xmlns 特性的值應與架構根元素中定義的 targetNamespace 特性的值相同。
通過 xmlns 特性綁定架構之后,即可鍵入一個開放的“<”字符,并使 Blog 控件顯示為語句完成的一個選項,如圖 8 所示。此時,還應獲取已定義屬性的語句完成,包括 Mode 屬性的有效值,以及由 XSD 文件中的注釋指定的生成器。

圖 8:HTML 視圖中的語句完成
小結
本文介紹了 Visual Studio .NET 中適用于 ASP.NET 服務器控件的設計時支持,還說明了開發人員如何在自己的自定義控件中利用這一支持功能。雖然在控件中添加設計時支持相對簡明,但要充分利用這些功能卻需要掌握多種不同的技巧。特別欠缺的知識領域就是如何將自定義 XSD 架構綁定到頁面上。在撰寫本文時,還不具備將頁面與控件 XSD 架構連接起來所需的 xmlns 特性的內置支持。所以,還需要手動添加這個特性。希望以后的 Visual Studio .NET 版本能夠自動完成這一過程。
本文中的示例代碼包含一個適用于 Blog 控件基礎版和設計時支持版的 Visual Studio .NET 項目,還包含一個說明如何使用每個控件的客戶端項目。要運行 BlogControlClient 項目,您需要在 IIS 中創建一個虛擬目錄 BlogControlClient,然后將其映射到硬盤驅動器上用于保存 BlogControlClient 項目文件夾的位置。
真誠地感謝 Microsoft Visual Studio .NET 團隊的 Rob Caron,他在我編寫自定義 XSD 架構的創建和綁定過程中給予了極大的幫助。
代碼列表
列表 1:Blog.vb
'supports Color structure Imports System.Drawing '支持 StreamWriter 類型 Imports System.IO Imports System.Web.UI '支持使用 HTML 控件 Imports System.Web.UI.HtmlControls '支持使用 Web 控件 Imports System.Web.UI.WebControls Public Class Blog Inherits Panel Implements INamingContainer Protected BlogDS As DataSet Protected TitleTB As TextBox Protected BlogText As TextBox Private _addRedirect As String Private _email As String Private _mode As String Private _separatorColor As Color = Color.Black Public Property AddRedirect() As String Get Return Me._addRedirect End Get Set(ByVal Value As String) Me._addRedirect = Value End Set End Property Public Property Email() As String Get Return Me._email End Get Set(ByVal Value As String) Me._email = Value End Set End Property Public Property Mode() As String Get Return Me._mode End Get Set(ByVal Value As String) Me._mode = Value End Set End Property Public Property SeparatorColor() As Color Get Return Me._separatorColor End Get Set(ByVal Value As Color) Me._separatorColor = Value End Set End Property Protected Overrides Sub OnInit(ByVal e As EventArgs) LoadData() MyBase.OnInit(e) End Sub Protected Overrides Sub CreateChildControls() If Not Me._mode = "Add" Then DisplayBlogs() Else NewBlog() End If End Sub Protected Sub LoadData() BlogDS = New DataSet() Try BlogDS.ReadXml(Page.Server.MapPath("Blog.xml")) Catch fnfEx As FileNotFoundException CreateBlankFile() LoadData() End Try End Sub Protected Sub DisplayBlogs() Dim BlogDate As DateTime Dim CurrentDate As DateTime = New DateTime() Dim BlogRows As DataRowCollection = _ BlogDS.Tables(0).Rows Dim BlogDR As DataRow For Each BlogDR In BlogRows Dim BDate As String = BlogDR("date").ToString() BlogDate = New DateTime _ (Convert.ToInt32(BDate.Substring(4, 4)), _ Convert.ToInt32(BDate.Substring(0, 2)), _ Convert.ToInt32(BDate.Substring(2, 2))) If Not CurrentDate = BlogDate Then Dim TempDate As Label = New Label() TempDate.Text = BlogDate.ToLongDateString() TempDate.Font.Size = FontUnit.Large TempDate.Font.Bold = True Me.Controls.Add(TempDate) Me.Controls.Add _ (New LiteralControl("<br/><br/>")) CurrentDate = BlogDate End If Dim Anchor As HtmlAnchor = New HtmlAnchor() Anchor.Name = "#" & BlogDR("anchorID").ToString() Me.Controls.Add(Anchor) Dim Title As Label = New Label() Title.Text = BlogDR("title").ToString() Title.Font.Size = FontUnit.Larger Title.Font.Bold = True Me.Controls.Add(Title) Me.Controls.Add(New LiteralControl("<p>")) Dim BlogText As LiteralControl = _ New LiteralControl("<div>" & _ BlogDR("text").ToString() & "</div>") Me.Controls.Add(BlogText) Me.Controls.Add(New LiteralControl("</p>")) Dim Email As HyperLink = New HyperLink() Email.NavigateUrl = "mailto:" & _ BlogDR("email").ToString() Email.Text = "E-mail me" Me.Controls.Add(Email) Me.Controls.Add(New LiteralControl(" | ")) Dim AnchorLink As HyperLink = New HyperLink() AnchorLink.NavigateUrl = _ Page.Request.Url.ToString() & "#" & _ BlogDR("anchorID").ToString() AnchorLink.Text = "Link" Me.Controls.Add(AnchorLink) Me.Controls.Add(New _ LiteralControl("<hr color='" & _ ColorTranslator.ToHtml(_separatorColor) & _ "' width='100%'/><br/>")) Next End Sub Protected Sub NewBlog() Dim Title As Label = New Label() Title.Text = "Create New Blog" Title.Font.Size = FontUnit.Larger Title.Font.Bold = True Me.Controls.Add(Title) Me.Controls.Add(New LiteralControl("<br/><br/>")) Dim TitleLabel As Label = New Label() TitleLabel.Text = "Title: " TitleLabel.Font.Bold = True Me.Controls.Add(TitleLabel) TitleTB = New TextBox() Me.Controls.Add(TitleTB) Me.Controls.Add(New LiteralControl("<br/>")) Dim BlogTextLabel As Label = New Label() BlogTextLabel.Text = "Text: " BlogTextLabel.Font.Bold = True Me.Controls.Add(BlogTextLabel) BlogText = New TextBox() BlogText.TextMode = TextBoxMode.MultiLine BlogText.Rows = 10 BlogText.Columns = 40 Me.Controls.Add(BlogText) Me.Controls.Add(New LiteralControl("<br/>")) Dim Submit As Button = New Button() Submit.Text = "Submit" AddHandler Submit.Click, AddressOf Me.Submit_Click Me.Controls.Add(Submit) End Sub Protected Sub Submit_Click(ByVal Sender As Object, _ ByVal e As EventArgs) EnsureChildControls() AddBlog() End Sub Protected Sub AddBlog() Dim NewBlogDR As DataRow NewBlogDR = BlogDS.Tables(0).NewRow() NewBlogDR("date") = FormatDate(DateTime.Today) NewBlogDR("title") = TitleTB.Text NewBlogDR("text") = BlogText.Text NewBlogDR("anchorID") = Guid.NewGuid().ToString() NewBlogDR("email") = _email BlogDS.Tables(0).Rows.InsertAt(NewBlogDR, 0) BlogDS.WriteXml(Page.Server.MapPath("Blog.xml")) Page.Response.Redirect(_addRedirect) End Sub Protected Function FormatDate(ByVal dt As DateTime) _ As String Dim retString As String retString = String.Format("{0:D2}", dt.Month) retString &= String.Format("{0:D2}", dt.Day) retString &= String.Format("{0:D2}", dt.Year) Return retString End Function Public Sub CreateBlankFile() Dim NewXml As StreamWriter = _ File.CreateText(Page.Server.MapPath("Blog.xml")) NewXml.WriteLine("<blogs>") NewXml.WriteLine _ (" <!-- blog field describes a single blog -->") NewXml.WriteLine(" <blog>") NewXml.WriteLine(" <!-- date field contains" & _ " the creation date of the blog -->") NewXml.WriteLine(" <date>" & _ FormatDate(DateTime.Today) & "</date>") NewXml.WriteLine _ (" <title>Temporary Blog</title>") NewXml.WriteLine(" <!-- text field " & _ "should contain the blog text, including any " & _ "desired HTML tags -->") NewXml.WriteLine(" <text>This entry " & _ "indicates that the file blog.xml was not " & _ "found.A default version of this file has " & _ "been created for you.You can modify the " & _ "fields in this file as desired.If you set " & _ "the Blog control to add mode (add the " & _ "attribute mode='add' to the control's " & _ "declaration), the control will " & _ "automatically populate the XML file when " & _ "you submit the form.</text>") NewXml.WriteLine(" <!-- anchorID field " & _ "will be autopopulated by the control -->") NewXml.WriteLine(" <anchorID></anchorID>") NewXml.WriteLine(" <!-- email field should" & _ " contain the email address for feedback -->") NewXml.WriteLine(" <email>change this to a " & _ "valid email address</email>") NewXml.WriteLine(" </blog>") NewXml.WriteLine("</blogs>") NewXml.Close() End Sub End Class |
列表 2:BlogClient.aspx
<%@ Register TagPrefix="cc1" Namespace="BlogControl" Assembly="BlogControl" %> <%@ Page Language="vb" AutoEventWireup="false" Codebehind="BlogClient.aspx.vb" Inherits="BlogControlClient.WebForm1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Blog Client</title> </head> <body> <form id=Form1 method=post runat="server"> <p><asp:hyperlink id=Link1 navigateurl="BlogClient.aspx?mode=add" runat="server">Add Blog</asp:hyperlink></p> <cc1:blog id=Blog1 Email="andrew@graymad.com" AddRedirect="BlogClient.aspx" SeparatorColor="LawnGreen" runat="server"></cc1:blog> <p><asp:hyperlink id=Link2 navigateurl="BlogClient.aspx?mode=add" runat="server">Add Blog</asp:hyperlink></p> </form> </body> </html> |
列表 3:BlogClient.aspx.vb
Imports BlogControl Public Class WebForm1 Inherits System.Web.UI.Page Protected WithEvents Link1 As _ System.Web.UI.WebControls.HyperLink Protected WithEvents Link2 As _ System.Web.UI.WebControls.HyperLink Protected WithEvents Blog1 As BlogControl.Blog Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load If Request.QueryString("mode") = "add" Then Blog1.Mode = "Add" Link1.Visible = False Link2.Visible = False Else Blog1.Mode = "Display" Link1.Visible = True Link2.Visible = True End If End Sub End Class |
列表 4:Blog_DT.vb
'支持設計時特性 Imports System.ComponentModel '支持顏色結構 Imports System.Drawing '支持 UITypeEditor 類型 Imports System.Drawing.Design '支持 StreamWriter 類型 Imports System.IO Imports System.Web.UI '支持 ControlDesigner 類型 ' 請注意,必須添加程序集 ' System.Design 的引用,才能導入此命名空間 Imports System.Web.UI.Design '支持使用 HTML 控件 Imports System.Web.UI.HtmlControls '支持使用 Web 控件 Imports System.Web.UI.WebControls <Assembly: TagPrefix("BlogControl", "BlogControl")> Public Enum BlogMode Add Display End Enum <Description("Simple Blog control.Supports display " & _ "of Web log / news items from an XML file."), _ Designer("BlogControl.BlogDesigner"), _ ToolboxData("<{0}:Blog_DT runat=server></{0}:Blog_DT>")> _ Public Class Blog_DT Inherits Panel Implements INamingContainer Protected BlogDS As DataSet Protected TitleTB As TextBox Protected BlogText As TextBox Private _addRedirect As String Private _email As String Private _mode As BlogMode Private _separatorColor As Color = Color.Black <Browsable(True), _ Category("Behavior"), _ Description("URL to which the page should redirect after successful submission of a new Blog entry."), _ Editor("System.Web.UI.Design.UrlEditor", _ GetType(UITypeEditor))> _ Public Property AddRedirect() As String Get Return Me._addRedirect End Get Set(ByVal Value As String) Me._addRedirect = Value End Set End Property <Browsable(True), _ Category("Behavior"), _ Description("Email address the control will use for listing in new Blog entries.")> _ Public Property Email() As String Get Return Me._email End Get Set(ByVal Value As String) Me._email = Value End Set End Property <Browsable(True), _ Category("Behavior"), _ Description("Controls whether existing Blogs are displayed, or fields for creating a new Blog entry.")> _ Public Property Mode() As BlogMode Get Return Me._mode End Get Set(ByVal Value As BlogMode) Me._mode = Value End Set End Property <Browsable(True), _ Category("Appearance"), _ Description("Controls the color of the line that separates Blog entries when in display mode.")> _ Public Property SeparatorColor() As Color Get Return Me._separatorColor End Get Set(ByVal Value As Color) Me._separatorColor = Value End Set End Property Protected Overrides Sub OnInit(ByVal e As EventArgs) LoadData() MyBase.OnInit(e) End Sub Protected Overrides Sub CreateChildControls() If Not Me._mode = BlogMode.Add Then DisplayBlogs() Else NewBlog() End If End Sub Protected Sub LoadData() BlogDS = New DataSet() Try BlogDS.ReadXml(Page.Server.MapPath("Blog.xml")) Catch fnfEx As FileNotFoundException CreateBlankFile() LoadData() End Try End Sub Protected Sub DisplayBlogs() Dim BlogDate As DateTime Dim CurrentDate As DateTime = New DateTime() Dim BlogRows As DataRowCollection = _ BlogDS.Tables(0).Rows Dim BlogDR As DataRow For Each BlogDR In BlogRows Dim BDate As String = BlogDR("date").ToString() BlogDate = New DateTime _ (Convert.ToInt32(BDate.Substring(4, 4)), _ Convert.ToInt32(BDate.Substring(0, 2)), _ Convert.ToInt32(BDate.Substring(2, 2))) If Not CurrentDate = BlogDate Then Dim TempDate As Label = New Label() TempDate.Text = BlogDate.ToLongDateString() TempDate.Font.Size = FontUnit.Large TempDate.Font.Bold = True Me.Controls.Add(TempDate) Me.Controls.Add _ (New LiteralControl("<br/><br/>")) CurrentDate = BlogDate End If Dim Anchor As HtmlAnchor = New HtmlAnchor() Anchor.Name = "#" + BlogDR("anchorID").ToString() Me.Controls.Add(Anchor) Dim Title As Label = New Label() Title.Text = BlogDR("title").ToString() Title.Font.Size = FontUnit.Larger Title.Font.Bold = True Me.Controls.Add(Title) Me.Controls.Add(New LiteralControl("<p>")) Dim BlogText As LiteralControl = _ New LiteralControl("<div>" & _ BlogDR("text").ToString() & "</div>") Me.Controls.Add(BlogText) Me.Controls.Add(New LiteralControl("</p>")) Dim Email As HyperLink = New HyperLink() Email.NavigateUrl = "mailto:" & _ BlogDR("email").ToString() Email.Text = "E-mail me" Me.Controls.Add(Email) Me.Controls.Add(New LiteralControl(" | ")) Dim AnchorLink As HyperLink = New HyperLink() AnchorLink.NavigateUrl = _ Page.Request.Url.ToString() & "#" & _ BlogDR("anchorID").ToString() AnchorLink.Text = "Link" Me.Controls.Add(AnchorLink) Me.Controls.Add _ (New LiteralControl("<hr color='" & _ ColorTranslator.ToHtml(_separatorColor) & _ "' width='100%'/><br/>")) Next End Sub Protected Sub NewBlog() Dim Title As Label = New Label() Title.Text = "Create New Blog" Title.Font.Size = FontUnit.Larger Title.Font.Bold = True Me.Controls.Add(Title) Me.Controls.Add(New LiteralControl("<br/><br/>")) Dim TitleLabel As Label = New Label() TitleLabel.Text = "Title: " TitleLabel.Font.Bold = True Me.Controls.Add(TitleLabel) TitleTB = New TextBox() Me.Controls.Add(TitleTB) Me.Controls.Add(New LiteralControl("<br/>")) Dim BlogTextLabel As Label = New Label() BlogTextLabel.Text = "Text: " BlogTextLabel.Font.Bold = True Me.Controls.Add(BlogTextLabel) BlogText = New TextBox() BlogText.TextMode = TextBoxMode.MultiLine BlogText.Rows = 10 BlogText.Columns = 40 Me.Controls.Add(BlogText) Me.Controls.Add(New LiteralControl("<br/>")) Dim Submit As Button = New Button() Submit.Text = "Submit" AddHandler Submit.Click, AddressOf Me.Submit_Click Me.Controls.Add(Submit) End Sub Protected Sub Submit_Click(ByVal Sender As Object, _ ByVal e As EventArgs) EnsureChildControls() AddBlog() End Sub Protected Sub AddBlog() Dim NewBlogDR As DataRow NewBlogDR = BlogDS.Tables(0).NewRow() NewBlogDR("date") = FormatDate(DateTime.Today) NewBlogDR("title") = TitleTB.Text NewBlogDR("text") = BlogText.Text NewBlogDR("anchorID") = Guid.NewGuid().ToString() NewBlogDR("email") = _email BlogDS.Tables(0).Rows.InsertAt(NewBlogDR, 0) BlogDS.WriteXml(Page.Server.MapPath("Blog.xml")) Page.Response.Redirect(_addRedirect) End Sub Protected Function FormatDate(ByVal dt As DateTime) As String Dim retString As String retString = String.Format("{0:D2}", dt.Month) retString &= String.Format("{0:D2}", dt.Day) retString &= String.Format("{0:D2}", dt.Year) Return retString End Function Public Sub CreateBlankFile() Dim NewXml As StreamWriter = _ File.CreateText(Page.Server.MapPath("Blog.xml")) NewXml.WriteLine("<blogs>") NewXml.WriteLine _ (" <!-- blog field describes a single blog -->") NewXml.WriteLine(" <blog>") NewXml.WriteLine(" <!-- date field contains" & _ " the creation date of the blog -->") NewXml.WriteLine(" <date>" & _ FormatDate(DateTime.Today) & "</date>") NewXml.WriteLine _ (" <title>Temporary Blog</title>") NewXml.WriteLine(" <!-- text field " & _ "should contain the blog text, including any " & _ "desired HTML tags -->") NewXml.WriteLine(" <text>This entry " & _ "indicates that the file blog.xml was not " & _ "found.A default version of this file has " & _ "been created for you.You can modify the " & _ "fields in this file as desired.If you set " & _ "the Blog control to add mode (add the " & _ "attribute mode='add' to the control's " & _ "declaration), the control will " & _ "automatically populate the XML file when " & _ "you submit the form.</text>") NewXml.WriteLine(" <!-- anchorID field " & _ "will be autopopulated by the control -->") NewXml.WriteLine(" <anchorID></anchorID>") NewXml.WriteLine(" <!-- email field should" & _ " contain the email address for feedback -->") NewXml.WriteLine(" <email>change this to a " & _ "valid email address</email>") NewXml.WriteLine(" </blog>") NewXml.WriteLine("</blogs>") NewXml.Close() End Sub End Class Public Class BlogDesigner Inherits ControlDesigner Public Overrides Function GetDesignTimeHtml() As String Return "<h1>Blog</h1><hr/><hr/>" End Function End Class |
列表 5:Blog.xsd
<?xml version="1.0" encoding="utf-8" ?> <xsd:schema targetNamespace="urn:http://www.aspnetian.com/schemas" elementFormDefault="qualified" xmlns="urn:http://www.aspnetian.com/schemas" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:vs="http://schemas.microsoft.com/Visual-Studio-Intellisense" vs:friendlyname="Blog Control Schema" vs:ishtmlschema="false" vs:iscasesensitive="false" vs:requireattributequotes="true" > <xsd:annotation> <xsd:documentation> Blog Control schema. </xsd:documentation> </xsd:annotation> <xsd:element name="Blog_DT" type="BlogDef" /> <!-- <aspnetian:Blog> --> <xsd:complexType name="BlogDef"> <!-- <aspnetian:Blog>-specific attributes --> <xsd:attribute name="AddRedirect" type="xsd:string" vs:builder="url"/> <xsd:attribute name="Email" type="xsd:string"/> <xsd:attribute name="Mode" type="BlogMode"/> <xsd:attribute name="SeparatorColor" type="xsd:string" vs:builder="color"/> <!-- <asp:Panel>-specific attributes --> <xsd:attribute name="BackImageUrl" type="xsd:anyURI" /> <xsd:attribute name="HorizontalAlign" type="HorizontalAlign" /> <xsd:attribute name="Wrap" type="xsd:boolean" /> <xsd:attribute name="Enabled" type="xsd:boolean" /> <xsd:attribute name="BorderWidth" type="ui4" /> <xsd:attribute name="BorderColor" type="xsd:string" vs:builder="color" /> <xsd:attribute name="BorderStyle" type="BorderStyle" /> <xsd:attributeGroup ref="WebControlAttributes" /> </xsd:complexType> <!-- DataTypes --> <xsd:simpleType name="BlogMode"> <xsd:restriction base="xsd:string"> <xsd:enumeration value="Add" /> <xsd:enumeration value="Display" /> </xsd:restriction> </xsd:simpleType> </xsd:schema> |