2009年7月30日 星期四

探討.NET Configuration Files 1-3

RunPC 112、113期:在.NET上保存應用程式設定(四)

ConfigurationSettings類別
ConfigurationSettings是一個類別提供在指定配置區段中對配置設定的存取,它擁有一個靜態方法(Static Method)GetConfig與一個靜態屬性(Static Property) AppSettings,是故可在不建立類別實體(Instance)的情況下即可使用該靜態方法與屬性,如上範例我們並未建立ConfigurationSettings實體而直接使用;AppSettings屬性會取得一個唯讀的NameValueCollection,含有配置區段中設定的名稱/值對;若在配置檔案中並未指定則會傳回empty;而GetConfig靜態方法的簽名碼(Signature)中指定傳回的是Object,但一般會是NameValueCollection,而實際上在區段中取得的會是ReadOnlyNameValueCollection,詳細的ConfigurationSettings類別請見configurationsettings.cs附檔。
為自訂標記(Tag)自訂一個處理器類別(Handler)
上面我們各舉了一個利用電腦配置檔系統事先已宣告的appSettings區段,和一個利用系統提供的處理器類別宣告一個自訂的區段,而這都不外乎是一種簡單的Key/Value配對機制,對於在共通上保留應用程式的設定值已經非常完美,但若遇到複雜的儲存值時卻也有點顯的不符使用,是故我們在這系列文章中的第二篇為各位介紹怎麼自訂一個XML的配置檔案,當然這是一個解決方式,但若可以利用.NET所提供的機制來解決這方面的問題那就更加的完美。接下來我們將為各位介紹怎麼為自訂標記自訂一個處理器類別,我們舉個簡單的例子,若有某甲公司想將有關產品分類、產品分類網址與產品項目的列表要保留於配置檔案中,其步驟如下:
1、 定義配置檔案內的XML結構描述
依上述需求我們會規劃其XML格式片段如下,顯然地我們沒法使用系統提供的處理器類別來加以剖析處理,接著我們將下面XML片斷加入Web.Config的根項目下,接著執行會發生如(圖一)的錯誤。

2、 建立自訂處理器
建立一個自訂處理器並不困難,但它必需是一個獨立的專案。
A、 在ProWebConfig專案內選擇功能表上的[專案]-->[新增]-->[專案],在新增專案對話盒中選[Visual Basic專案]-->[類別庫],名稱改為[ProProductHandler],記得點選[加入至方案]後按確定。
B、 在專案總管中刪除Class1.vb,後再專案上按右鍵選[加入新項目],選[類別]更名為[ProductTagHandler.vb]。
C、 自訂處理器類別必須實作IConfigurationSectionHandler介面,該介面只有一個Create的方法,由所有配置區段處理器實作以剖析配置區段的 XML。傳回的物件會加入至置配集合並且由 GetConfig進行存取。它帶有三個參數:parent、configContext、section,當然系統提供的處理器類別依舊是必需實作該介面[註3],請參考下面程式碼:

當應用程式剖析時,它會建立ProductTagHandler實體和呼叫Create方法,而通常我們只會用到section這個XmlNode參數,它包含了整個您要處理的配置檔區段內容,在這個例子上是指的區段內容;而configContext(HttpConfigurationContext)是提供目前的內容資訊給 ASP.NET 應用程式中的配置區段處理器,最主要是用來取得Web.config的虛擬路徑;parent則是在對應的父代配置區段中的配置設定。
[註3]:請參考附件系統提供處理器的原始碼檔案:dictionarysectionhandler.cs、ignoresectionhandler.cs、namevaluefilesectionhandler.cs、namevaluesectionhandler.cs、singletagsectionhandler.cs皆實作了IConfigurationSectionHandler介面。
D、 撰寫相關剖析程式碼,如下:

Imports System.Configuration
Imports System.Collections
Imports System.Xml
Public Class ProductTagHandler
Implements IConfigurationSectionHandler

Public Function Create(ByVal parent As Object, ByVal configContext As Object, ByVal section As System.Xml.XmlNode) As Object Implements System.Configuration.IConfigurationSectionHandler.Create
Dim xnProduct, xnRelProduct As XmlNode
Dim ProductNodes, RelProductNodes As XmlNodeList
Dim RelProducts, Prices As ArrayList
Dim PTitle, PDescUrl As String
Dim Products As New ArrayList()
'取得分類產品節點列表
ProductNodes = section.SelectNodes("Product")
'取得每個分類產品節點
For Each xnProduct In ProductNodes
'取得分類產品節點的Title、DescUrl屬性值
PTitle = xnProduct.Attributes.GetNamedItem("Title").Value
PDescUrl = xnProduct.Attributes.GetNamedItem("DescUrl").Value
RelProducts = New ArrayList()
Prices = New ArrayList()
'取出產品節點下的相關產品列表
RelProductNodes = xnProduct.SelectNodes("RelProducts//RelProduct")
If Not RelProductNodes Is Nothing Then
'取出相關產品的名稱與價格
For Each xnRelProduct In RelProductNodes
RelProducts.Add(xnRelProduct.InnerText)
Prices.Add(xnRelProduct.Attributes.GetNamedItem("Price").Value)
Next
End If
'將Product實體放入Products ArrayList內
Products.Add(New Product(PTitle, PDescUrl, RelProducts, Prices))
Next
'傳回Products ArrayList
Return Products
End Function
End Class

Public Class Product
Private m_Title, m_DescUrl As String
Private m_RelProducts, m_Prices As ArrayList
Public Sub New(ByVal PTitle As String, ByVal PDescUrl As String, ByVal RelProducts As ArrayList, ByVal Prices As ArrayList)
m_Title = PTitle
m_DescUrl = PDescUrl
m_RelProducts = RelProducts
m_Prices = Prices
End Sub

Public ReadOnly Property Title() As String
Get
Return m_Title
End Get
End Property
Public ReadOnly Property DescURL() As String
Get
Return m_DescUrl
End Get
End Property
Public ReadOnly Property RelProducts() As ArrayList
Get
Return m_RelProducts
End Get
End Property
Public ReadOnly Property Prices() As ArrayList
Get
Return m_Prices
End Get
End Property
End Class

在Create方法內撰寫對section剖析的程式碼,section的內容是來自配置檔案內的標記,剖析後將值用來建立Product的實體,再將Products ArrayList一一放入對Product實體的參照,最後將該ArrayList傳回,但在IConfigurationSectionHandler介面內的Create方法的簽名碼傳回的資料型別是Object,是故在呼叫者必需轉換型態為ArrayList。
3、 參考自訂處理器類別
在ASP.NET Web應用程式專案(ProWebConfig)中加入參考,在加入參考對話盒中選擇[專案]頁籤,加入參考[ProProductHandler]專案。
4、 宣告配置區段(Products)

項目下宣告,其section屬性名稱設為Products,而type屬性指定為處理器類別的組件(Assembly)和類別名稱。
5、 取得配置內容與資料呈現
在RootForm.aspx上增加一按鈕[Button1],在屬性視窗中改Text為[GetProductsList],在該Button的Click事件中撰寫下面程式碼:

Dim Products As ArrayList
Dim anProduct As ProProductHandler.Product
Dim i As Integer
Products = CType(System.Configuration.ConfigurationSettings.GetConfig("Products"), ArrayList)
If Not Products Is Nothing Then
For Each anProduct In Products
Response.Output.WriteLine("< a href=""" & anProduct.DescURL & """" & ">" & anProduct.Title & "< /a>")
If Not anProduct.RelProducts Is Nothing Then
For i = 0 To anProduct.RelProducts.Count - 1
Response.Output.WriteLine(anProduct.RelProducts.Item(i) & ":$" & anProduct.Prices.Item(i))
Next
End If
Next
End If

在System.Configuration.ConfigurationSettings.GetConfig("Products")會呼叫處理器類別的Create方法,因簽名碼的關係取回的是Object型態,此處借由型別轉換回真正傳回的ArrayList型態;而後續再取出ArrayList內的Product一一組成我們所要呈現的介面,執行結果呈現如下(圖三)。

(圖三) 取得配置內容的Web資料呈現

結論:
這篇文章是這系列文章中的最後一篇,我們對電腦(machine)、應用程式(application)和安全性(security) 各配置檔案做了簡單的介紹;當然我們特別著重在應用程式配置檔案中怎麼自訂配置區段的部份,一般有二個步驟:先宣告配置區段指定區段名稱和處理器類別,再依宣告名稱設定配置區段的內容。若無特殊需求一般可利用已在電腦配置檔案宣告的appSettings來做區段配置;當然您若要自訂區段名稱則可視自己所需在項目下宣告區段名稱,指定.NET Framework所提供的處理器類別,讓我們的區段結構描述是要以XML Node還提XML Attribute鍵/值成對為主;最後若鍵/值成對無法滿足您配置需求時,我們為各位解說怎麼利用實作IConfigurationSectionHandler介面來撰寫自訂的處理器類別,來剖析處理特殊的配置區段內容。
若各位讀者希望能夠撰寫較佳的處理器類別,對於進一步研究.NET上的XML操作是不可或缺的,畢竟配置檔案是XML Base的,當然各位亦可參考附件的dictionarysectionhandler.cs、ignoresectionhandler.cs、namevaluefilesectionhandler.cs、namevaluesectionhandler.cs、singletagsectionhandler.cs系統提供的處理器類別原始碼亦是以操作XML為主。
參考內容:
MSDN線上說明文件
Configuring Applications in .NET(http://www.c-sharpcorner.com/Code/2002/June/ConfigApp.asp)
Creating Custom Configuration Section Handlers(http://www.dotnetbips.com/displayarticle.aspx?id=127)
Specifying Configuration Settings in Web.config(http://aspnet.4guysfromrolla.com/articles/053102-1.aspx)

2009年7月22日 星期三

探討.NET Configuration Files 1-2

RunPC 112、113期:在.NET上保存應用程式設定(四)

應用程式配置檔案(Application Configuration Files)
應用程式配置檔案顧名思義它是用來配置應用程式特有的配置檔案,它的名稱和位置取決於應用程式的主應用程式 (Host),可以是下列其中一個:可執行檔裝載的應用程式、ASP.NET 裝載的應用程式、Internet Explorer 裝載的應用程式。
1、可執行檔裝載的應用程式
可執行檔裝載的應用程式它包含了視窗表單(Windows Froms)和主控台(Console-Base)的應用程式,可執行的主應用程式裝載的應用程式配置檔案位在與應用程式相同的目錄。配置檔案的副檔名為 .config,而其名稱為應用程式名稱(含.exe)加上.config。例如,稱為MyApp.exe 的應用程式可以和稱為 MyApp.exe.config 的配置檔案相關聯。
2、ASP.NET 裝載的應用程式
ASP.NET 裝載的應用程式它包含了ASP.NET Web 應用程式和ASP.NET Web服務,ASP.NET 配置檔案稱為 Web.config它位於虛擬目錄下的根節點或其子目錄下,在一個ASP.NET Web 應用程式中可以有多個 Web.config檔案,若您要求執行子目錄下的aspx頁面,它會先參考較靠近該aspx頁面下的web.config檔案內的設定值,若找不到相關設定值才會往上一層目錄參考其Web.Config檔案內的設定值直至虛擬目錄下的Web.Config檔案,若依舊沒有相關的設定值則會尋找電腦配置檔案內是否有提供其配置值,若皆沒有則出現圖一的錯誤。換言之,ASP.NET應用程式中頁面所參考的配置檔案是結合電腦配置檔案和各層目錄下的Web.Config檔案,若設定值有衝突時則以較靠近頁面的Web.Config設定值為主,請參考範例。
3、Internet Explorer 裝載的應用程式
如果裝載於 Internet Explorer 的應用程式具有配置檔案,這個檔案的位置會以下列語法在 < link> 標記中指定: < link rel="ConfigurationFileName" href="location"> 這個標記中,location 為配置檔案的 URL。這會設定應用程式基底。配置檔案必須位在與應用程式相同的 Web 網站。
範例:
1、 建立ASP.NET Web應用程式專案[ProWebConfig]。
2、 將專案中WebForm1.aspx更名為RootForm.aspx。
3、 在專案中建立Sub1與Sub2子資料夾,又在Sub1中建立Sub1_1子資料夾。
4、 在各子資料夾中加入新項目Web配置檔案(Web.config),在各配置檔中配置appSettings區段,而區段內加入一項目key為dsn,其值為Root_DSN、Sub1_DSN、Sub2_DSN、Sub1_1_DSN。
5、 在各子資料夾中加入Web Form,其名稱各為frmSub1.aspx、frmSub2.aspx、frmSub1_1.aspx。
6、 在各表單的Form_Load事件中加入Response.Output.WriteLine(ConfigurationSettings.AppSettings("dsn"))這行程式碼。
7、 在RootForm.aspx中加入三個HyperLink,其NavigateUrl屬性各指定為Sub1/frmSub1.aspx、Sub2/frmSub2.aspx、Sub1_1/frmSub1_1.aspx。
8、 執行測試,各表單取得的dsn鍵其值皆為最靠近表單的Web.config設定值。
9、 您可試將Sub1資料夾下的appSettings區段拿掉,再看frmSub1.aspx中取得該dsn鍵的值會是如何呢?
使用AppSettings來自訂配置檔案設定
當對各配置檔案做簡單說明後,接下接我們要為各位介紹配置檔案內的配置區段宣告與自訂的應用程式設定的關係,對它們的結構描述與各屬性所代表的意義來作簡單的介紹。一般我們會將對應用程式的喜愛設定和相關的狀態資訊存放在配置檔案的項目區段內,如:資料庫的連結設定等。其結構描述如下:

如前所述在所有配置檔案中皆是以< configuration>為根節點,是故< appSettings>項目區段亦是包含在其內。< add>項目的加入是表加入一個key/value成對的自訂的應用程式設定;< remove>項目是表來從您的應用程式中移除已在配置檔案階層架構較高層級中定義的設定,如:在應用程式配置檔案中移除您於電腦配置檔案中的設定;< clear>項目是表從您的應用程式中移除所有已在配置檔案階層架構較高層級中定義的設定。而我們可以利用< appSettings>項目來自訂的應用程式設定,是因為現存的電腦配置檔案中已對< appSettings>項目作宣告,若宣告的配置區段與配置區段內容沒有對應上則會發生如(圖一)的錯誤。您可參考下面電腦配置檔(machine.config)的部份片斷如下:

電腦配置檔案依舊是以< configuration>為根節點,而< configSections>項目是用來對配置區段和命名空間宣告,要特別注意的是當這個項目位於配置檔案中,則它必須是位於 < configuration> 項目的第一個子項目,其子項目請參考(表三)。

表三:< configSections>下的子項目

在程式中我們可利用ConfigurationSettings.AppSettings(key)[註1]來取得該key所對應的value,若您要將在該區段中的Key/Value值列出,請參考下面的程式碼:

利用For Each取得區段中的每一個鍵的名稱,再利用ConfigurationSettings.AppSettings帶入鍵的名稱取得對應的配對值,字串組合後用Response.Output.WriteLine[註2]列印於網頁上,當完成使用AppSettings來自訂配置檔案設定後,接下來讓我們在應用程式配置檔案中自訂一個配置區段。

註1:請參考下面ConfigurationSettings類別的說明。
註2:此處您亦可利用Respose.Write將結果印於網頁上,但若要分行則必需於每個字串結尾中串接< Br>字串以利分行,實為繁複,是故我們可利用在Global.asax的BeginRequest中加入Response.Output.NewLine = "< br>"該行程式碼,以後在該應用系統中即可使用Response.Output.WriteLine來輸出俱有分行效果的字串喔!
應用程式配置檔案中自訂一個配置區段
我們以ASP.NET Web應用程式為例,在Web.config配置檔案中使用非事先定義的AppSettings區段:
在Web.config中指定新定義的區段MySingleSection

區段宣告中的type可以有NameValueSectionHandler、NameValueFileSectionHandler、SingleTagSectionHandler、DictionarySectionHandler、IgnoreSectionHandler…等系統中預先定義好的區段處理類別,來針對所對應的區段配置內容來做剖析,其相關的說明請見表四。此處我們定義一個區段名稱為MySingleSection,利用SingleTagSectionHandler區段處理類別來做剖析區段內容,而MySingleSection區段內容帶Key/Value成對屬性三對,是因為要配合選用SingleTagSectionHandler的處理器類別,您可以利用下面的程式碼取出Key/Value列表值:

ConfigurationSettings.GetConfig("MySingleSection")傳回一雜湊表,而Hashtable 類別以 IDictionary 介面為基礎,當然MySS的宣告亦可改為Dim MySS As HashTable,詳細的程式碼請見附件範例。

表四:預先定義好的區段處理類別

配置檔案群組化
當在多人團隊開發的環境中或整合系統專案時,我們在配置檔案的應用上一般會將其配置區段群組化,以確保不會與其他人團隊成員所定義的配置區段發生命名衝突,其定義說明您可以參考表三。而下面為群組化的配置檔案部份內容,首先宣告一區段群組MyGroup後在其子項目中宣告一區段MySection其type改為NameValueSectionHandler處理器類別,注意其區段內容結構描述因處理器類別不同而有所差異,必須採用XML節點(node) 結構描述而非原XML屬性(Attribute) 結構描述。

而在群組區段中我們可以使用下面的程式碼取得您所要的Key/Value列表值:

此處因為我們宣告區段MySection其type已改為NameValueSectionHandler處理器類別,是故它的回傳值為NameValueCollection物件,請見表四說明,它是取回MyGroup群組MySection區段配置下集合,詳細的程式碼請見附件範例。

2009年7月15日 星期三

探討.NET Configuration Files 1-1

RunPC 112、113期:在.NET上保存應用程式設定(四)

在過去這一系列的文章中我們為各位介紹了,怎麼利用DynamicProperties快速設定來保留應用程式;怎麼規劃、自訂自己所屬的XML應用程式設定檔案;以及怎麼使用PropertyGrid控制項物件來讓應用程式的設定更加的方便,將設計時期的屬性視窗延伸到執行時期來對整個應用程式的環境來作配置;而本期我們將帶領各位讀者深入探討.NET Framework所提供的配置檔案(machine、security、application),當然為配合主題,我們將會把焦點放在Application配置檔案上,我們會為各位讀者說明利用DynamicProperties設定後產生的app.config內的appSettings配置區段(Configuration Section)是怎麼來的,以及key和value的配對關係;如何利用.NET Framework所提供的事先定義好的區段處理器(Section Handlers)來建立一個新的配置區段;如何將配置檔案內的Section群組化以利團隊模式開發;和要如何實作IConfigurationSectionHandler介面來自訂自己的區段處理器…等。首先讓我們對.NET Framework上的配置檔案做個概念性的解說。
配置檔案(Configuration Files)
在.NET Framework提供了三種基本型態的配置檔案資訊:電腦(machine)、應用程式(application)和安全性(security)。它們各自是為了不同目的而產生的,但它們皆是XML-Base具有相同的結構描述(Schema),例如:它們皆以這個項目為配置檔案的最上層項目,在配置檔案內根項目內包含了一些子項目(表1),而這些項目是使用標記(Tag)來標記項目的開頭和結尾,而項目的XML標記(Tag)和屬性是有大小寫區分的。當然若您使用事先定義好的區段來定義一個新的配置區段,則您必需要使用預先定義的屬性(項目之開始標記內的key/value配對)來指定配置設定。

表1:項目下的子項目

我們會將重心放置在二個子項目來探討,在開始這二個子項目介紹之前讓我們先對電腦(machine)和安全性(security)的配置檔案做個基本性介紹。
電腦配置檔案(Machine Configuration Files)
電腦配置檔案它的名稱為machine.config,位在.NET Framework安裝路徑的Config子目錄下,一般會在C:\WINDOWS\Microsoft.NET\Framework\v1.0.3705\CONFIG目錄下,它包含套用於整個電腦的設定值,如:電腦範圍組件繫結、內建遠端通道和 ASP.NET 的配置設定。配置系統首先會在電腦配置檔案中尋找 項目和開發人員可能定義的其他配置區段。它接著查看應用程式配置檔案(app.config、AppName.exe.config或web.config)。在電腦配置檔所定義的配置區段可在該部電腦的任一配置檔案上使用,除非該配置區段被應用程式配置檔案所覆寫,是故我們可以想像所有應用程式的配置檔案是繼承電腦配置檔案,而應用程式配置檔案可以覆寫電腦配置檔案的配置區段。若要讓電腦配置檔案易於管理,最好將這些設定值放在應用程式配置檔案中,以免喪失應用程式配置檔案的獨立性。然而,若該部電腦共通的設定值會置於電腦配置檔案中,如此做可讓我們的系統更容易維護。例如,如果我們在一台電腦上佈署多個應用程式,而各應用程式皆會連結到同一個單一登錄(Single Sign On)的資料庫伺服器以達到單一登錄,那我們就可將該連結參數設定值放置於電腦配置檔案上是比較適當的位置,而不用在各個應用程式配置檔案上設定,所以您在兩個不同檔案中沒有相同的設定值。但在佈署上若使用 XCOPY 部署應用程式將不會複製電腦配置檔案中的設定值此點要請讀者注意。下面是machine.config的部份內容:

而在先前文章中為各位介紹的『DynamicProperties』是利用DynamicProperties設定後產生的app.config內的appSettings配置區段(Configuration Section)是從電腦配置檔案(Machine.config)內的區段指定配對而來:

您可以將其appSettings區段名稱更名為appSettings1執行,則系統會發生如(圖一)的錯誤,請選擇[否],若要觀察錯誤所在位置,請按『Ctrl+F5』或點選功能表中的[偵錯]-->[啟動但不偵錯],即可見如圖二的配置錯誤畫面,告訴您在配置檔案中發生錯誤的位置,和錯誤的原因,顯而易見的它是告訴我們無法辨認配置區段appSettings,由此可知appSettings配置區段是必預由machine.config電腦配置檔案中事先宣告過。

(圖一)系統配置檔錯誤對話盒


(圖二)配置錯誤畫面

安全性配置檔案(Security Configuration Files)
安全配置檔案包含程式碼群組階層架構和與原則層級相關聯的使用權限集合的資訊。安全性配置檔案包含有三種原則配置檔案:企業(Enterprise)原則配置檔案、電腦(Machine)原則配置檔案、使用者(User)原則配置檔案,各所存在的路徑與檔名如(表2),我們強烈建議讀者使用 .NET Framework 配置工具 (Mscorcfg.msc位在%Runtime install path%下) 或程式碼存取安全性原則工具 (Caspol.exe位在%Runtime install path%下) 來修改安全性原則,確保原則變更不會損毀安全配置檔案。企業原則配置檔案是設定全部企業的安全性配置;電腦原則配置檔案只定義本機的安全性配置,此二者只可被具有administrator權限的使用者來設定配置,而使用者原則配置檔案是用來設定配置目前登錄使用者的相關安全性配置。

表2安全性配置檔案的位置

註:%Runtime install path%為.NET Framework安裝路徑;
vxx.xx.NET Framework的版本,目前一般為v1.0.3705。

2009年7月10日 星期五

PropertyGrid控制項1-2

RunPC 111期:在.NET上保存應用程式設定(三)

利用PropertyGrid呈現自訂類別屬性內容
假設我們自訂的類別為呈現系統相關的環境設定,其自訂屬性和分類規劃如下:

外觀分類屬性{表單顏色(AppColor)、應用程式游標樣式(AppCursor)、表單字型(AppFont)、標頭(Title)};
行為分類屬性{可見與否(Show)};
其他分類屬性{語言(AppLangugage)};
配置分類屬性{表單位置(AppLocation)、表單大小(AppSize)};
視窗樣式分類屬性{表單縮圖(AppIcon)}。



(圖九)自訂類別屬性內容

希望方格依照我們規劃的分類來呈現類別的屬性(圖九),我們運用.NET CategoryAttribute成員提供預設有十三種分類屬性(Action:動作;Appearance:外觀;Behavior:行為;Data:資料;Default:其他(即不設時);Design:設計;DragDrop:拖放;Focus:焦點;Format:格式;Key:索引鍵;Layout:配置;Mouse:滑鼠;WindowStyle:視窗樣式)來做自訂類別的屬性分類,當然規劃時若此十三種預設的屬性分類不能滿足您需求時,您亦可自訂分類;規劃完成後接下來讓我們開始建立自訂類別,其步驟如下:

1、 建立自訂類別:
在原專案[PropertyGrid_WinAp]的方案總管中選右鍵,於快顯功能表中選[加入][加入新項目],在範本中選擇[類別],更名為[AppSettings],按[確定]建立新類別,鍵入下面程式碼。

Imports System.ComponentModel
< DefaultPropertyAttribute("Title")> _
Public Class AppSettings
Private _Title As String = "自訂類別"
Private _Show As Boolean=True
Private _AppCursor As Cursor
Private _AppFont As Font
Private _AppIcon As Icon
Private _AppLangugae As System.Globalization.CultureInfo
Private _AppSize As Size
Private _AppColor As Color
Private _AppLocation As Point
< Category("Appearance"), _
BrowsableAttribute(True), _
ReadOnlyAttribute(False), _
BindableAttribute(False), _
DefaultValue("自訂類別"), _
DesignOnlyAttribute(False), _
DescriptionAttribute("【標頭】:請輸入應用程式的標頭")> _
Public Property Title() As String
Get
Return _Title
End Get
Set(ByVal Value As String)
_Title = Value
End Set
End Property
< CategoryAttribute("Behavior"), _
Browsable(True), _
[ReadOnly](False), _
BindableAttribute(False), _
DefaultValueAttribute("True"), _
DesignOnly(False), _
DescriptionAttribute("【可見與否】:設定應用程式介面可見與否")> _
Public Property Show() As Boolean
Get
Return _Show
End Get
Set(ByVal Value As Boolean)
_Show = Value
End Set
End Property
< CategoryAttribute("Appearance"), DefaultValueAttribute(""), _
DescriptionAttribute("【游標】:選擇應用程式介面上要呈現的游標樣式")> _
Public Property AppCursor() As System.Windows.Forms.Cursor
Get
Return _AppCursor
End Get
Set(ByVal Value As System.Windows.Forms.Cursor)
_AppCursor = Value
End Set
End Property
< CategoryAttribute("Appearance"), DefaultValueAttribute(""), _
DescriptionAttribute("【字型】:設定應用程式介面上的字型")> _
Public Property AppFont() As System.Drawing.Font
Get
Return _AppFont
End Get
Set(ByVal Value As System.Drawing.Font)
_AppFont = Value
End Set
End Property
< CategoryAttribute("WindowStyle"), DefaultValueAttribute(""), _
DescriptionAttribute("【圖示】:設定應用程式介面上呈現的小圖示")> _
Public Property AppIcon() As System.Drawing.Icon
Get
Return _AppIcon
End Get
Set(ByVal Value As System.Drawing.Icon)
_AppIcon = Value
End Set
End Property
< DefaultValueAttribute(""), _
DescriptionAttribute("【語言】:設定應用程式的語言")> _
Public Property AppLangugae() As System.Globalization.CultureInfo
Get
Return _AppLangugae
End Get
Set(ByVal Value As System.Globalization.CultureInfo)
_AppLangugae = Value
End Set
End Property
< CategoryAttribute("Layout"), DefaultValueAttribute(""), _
DescriptionAttribute("【大小】:設定應用程式介面的大小")> _
Public Property AppSize() As Size
Get
Return _AppSize
End Get
Set(ByVal Value As Size)
_AppSize = Value
End Set
End Property
< Category("Appearance"), DefaultValueAttribute(""), _
DescriptionAttribute("【字型顏色】:設定應用程式的字型顏色")> _
Public Property AppColor() As System.Drawing.Color
Get
Return _AppColor
End Get
Set(ByVal Value As System.Drawing.Color)
_AppColor = Value
End Set
End Property
< Category("Layout"), DefaultValueAttribute(""), _
DescriptionAttribute("【位置】:設定應用程式介面呈現的位置")> _
Public Property AppLocation() As Point
Get
Return AppLocation
End Get
Set(ByVal Value As Point)
_AppLocation = Value
End Set
End Property

程式碼說明:
Imports System.ComponentModel
因為要變更該自訂類別某些屬性的顯示方式,可以套用不同的特性(Attribute) 至這些屬性。所以需匯入 System.ComponentModel該命名空間,特性(Attribute) 是用於標記附註程式的設計項目,在執行時期時使用反映擷取類別、類型、欄位、方法及屬性的宣告式標記,此處我們只用到類別和屬性的宣告式標記,其下列是部分清單:
類別宣告式標記:
< DefaultPropertyAttribute("Title")> _
Public Class AppSettings
AppSettings類別的宣告式標記[DefaultPropertyAttribute]:表該類別預設的屬性。此例”Title”屬性為該類別之預設屬性,即該類別被載入方格時最先取得焦點位置的屬性。
屬性宣告式標記:

< Category("Appearance"), _
BrowsableAttribute(True), _
ReadOnlyAttribute(False), _
BindableAttribute(False), _
DefaultValue("自訂類別"), _
DesignOnlyAttribute(False), _
DescriptionAttribute("【標頭】:請輸入應用程式的標頭")> _
Public Property Title() As String
Get
Return _Title
End Get
Set(ByVal Value As String)
_Title = Value
End Set
End Property

 Category或CategoryAttribute:該屬性宣告式標記是用來設定屬性在方格中所屬的分類,其分類可參考上所述的.NET CategoryAttribute成員預設的十三種分類屬性,其分類亦可自訂,只要將其內含值改為要分類的名稱即可,當然若屬性未設定該Category屬性宣告式標記則該屬性會歸納於Default(其他)分類中。
 Browsable或BrowsableAttribute:該屬性宣告式標記是用來設定屬性是否在方格中呈現,若未設定該屬性宣告式標記預設為True,一律會將公用屬性呈現於方格中。
 ReadOnly或ReadOnlyAttribute(註2):該屬性宣告式標記是用來設定屬性是否唯讀,若未設定該屬性宣告式標記預設為False,一律會將俱有Get和Set子函式的公用屬性可於方格中編輯修改;若為True則該屬性於方格中以灰白呈現。
註2:一般宣告式標記的Attribute可省略,而此處省略後ReadOnly為關鍵字,則需將其以中括號含括。
 Bindable或BindableAttribute:該屬性宣告式標記是用來設定屬性可以被Bind到資料來源,若未設定該屬性宣告式標記預設為False。
 DefaultValue或DefaultValueAttribute:該屬性宣告式標記是用來指定屬性的預設值,當屬性值不等於屬性的預設值時,方格內的該屬性值將以粗體來表示。
 DesignOnly或DesignOnlyAttribute:該屬性宣告式標記是用來設定屬性在執行時期是否為唯讀,若為True則該屬性只可在設計時期被設定。
 Description或DescriptionAttribute:該屬性宣告式標記是用來設定屬性方格下描述說明窗格中所顯示之屬性的文字,即在方格中取得焦點的屬性相關描述說明。
類別AppSettings內除了Title和Show為簡單型別的屬性,其餘皆為複雜屬性,由.NET Framework提供一些具有特殊顯示功能的資料型別,讓類別中的屬性更容易在方格中被使用與設定,如:表單顏色(AppColor)、應用程式游標樣式(AppCursor)、表單字型(AppFont)、語言(AppLangugage)表單位置(AppLocation)、表單大小(AppSize)、表單縮圖(AppIcon)…等皆為複雜屬性在方格中各有特殊顯示功能。


2、 建立呈現視窗:
在原專案[PropertyGrid_WinAp]的方案總管中選右鍵,於快顯功能表中選[加入][加入視窗表單],名稱為[SelfProperty],按[確定]建立新表單後,拖曳PropertyGrid控制項物件至SelfProperty表單上名稱為PropertyGrid1。
3、 撰寫相關程式碼:

Private Sub SelfProperty_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim SelfCls As New AppSettings()
PropertyGrid1.SelectedObject = SelfCls
PropertyGrid1.Refresh()
End Sub

上面的程式碼是於SelfProperty_Load事件中(即表單載入時),建立一AppSettings的類別物件的執行個體SelfCls,而PropertyGrid1方格物件的SelectedObject指定為SelfCls物件,如此即可呈現如(圖九)的樣式,而在末行PropertyGrid1.Refresh()方法是用來重新整理屬性。
4、 設定啟始物件
在執行之前我們需改變它的啟始物件,如(圖七)選擇啟始物件為[SelfProperty],後按確定,執行即可見著如(圖九)之畫面。
到目前為止我們還停留在怎麼樣利用PropertyGrid控制項物件方格來呈現自訂或系統類別屬性,和怎麼利用宣告式標記來套用不同的特性(Attribute) 至這些屬性上與怎麼在方格中對屬性來加分類等議題,接下來我們將探討如何結合這系列文章第一篇(Run PC 109期)所提到的DynamicProperties之Config檔來保存PropertyGrid控制項物件的設定值。

利用DynamicProperties保存PropertyGrid控制項物件的設定值
依附上例,我們各舉二個簡單型別{(標頭(Title)、可見與否(Show) }與二個複雜誌型別{表單位置(AppLocation)、表單大小(AppSize)}來加以保存。

1、 建立Config檔
選擇表單SelfProperty後於DynamicProperties內任一設定一對應屬性,使其由系統快速自動建立app.config檔,再將其檔案內容修改如下:



Height和Width是用來保存表單大小(AppSize);而X和Y是用來保存表單位置(AppLocation);Title和Show即是用來保存其餘二個簡單型別。
2、 讀取Config檔與初始設定
在表單SelfProperty的New建構式中呼叫SetApp()

Public Sub New()
MyBase.New()
InitializeComponent()
SetApp()
End Sub
Sub SetApp()
Dim MyConfigReader As System.Configuration.AppSettingsReader = New System.Configuration.AppSettingsReader()
Dim x, y, width, height As Integer, title As String, show As Boolean
x = CType(MyConfigReader.GetValue("X", GetType(System.Int32)), Integer)
y = CType(MyConfigReader.GetValue("Y", GetType(System.Int32)), Integer)
width = CType(MyConfigReader.GetValue("Width", GetType(System.Int32)), Integer)
height = CType(MyConfigReader.GetValue("Height", GetType(System.Int32)), Integer)
title = MyConfigReader.GetValue("Title", GetType(System.String))
show = CType(MyConfigReader.GetValue("Show", GetType(System.Boolean)), Boolean)
Me.Location = New System.Drawing.Point(x, y)
Me.Size = New Size(width, height)
Me.Text = title
Me.Visible = show
SelfCls.AppLocation = Me.Location
SelfCls.AppSize = Me.Size
SelfCls.Title = Me.Text
SelfCls.Show = Me.Visible
End Sub

這段程式碼是利用AppSettingsReader類別物件來讀取Config檔內的設定值,再一一將讀取出來的值設定給應用程式來反應舊有保存值和自訂類別的執行個體(SelfCls)來立即呈現相關設定於PropertyGrid類別物件方格內。
再來需將SelfProperty_Load事件中建立自訂類別物件的執行個體,由事件程序提昇至類別層級,宣告如下:Dim WithEvents SelfCls As New AppSettings()
3、 修改自訂類別來保存設定值
因為我們希望在PropertyGrid類別物件方格內修改屬性值的同時能即時將設定值反應至應用系統環境中,是故我們在自訂類別AppSettings內加入四個事件宣告。

Public Event SetAppSize(ByVal SetValue As Size)
Public Event SetAppLocation(ByVal SetValue As Point)
Public Event SetTitle(ByVal SetValue As String)
Public Event SetShow(ByVal SetValue As Boolean)

當相關的屬性值修改時立即觸發該事件,是故我們選擇在Title、Show、AppLocation、AppSize四個屬性的Set子函式內分別加入RaiseEvent SetTitle(Value)、RaiseEvent SetShow(Value)、RaiseEvent SetAppLocation(Value)、RaiseEvent SetAppSize(Value)敘述。
接下來於自訂類別中撰寫相關儲存設定程式碼,於設定屬性值的同時亦將屬性值直接存入Config檔內,程式碼參考如下:

Private Sub SaveValue()
Dim RA As System.Reflection.Assembly = System.Reflection.Assembly.GetExecutingAssembly
Dim StrCnfgPath As String = RA.Location & ".config"
Dim XmlDoc As New System.Xml.XmlDocument()
XmlDoc.Load(StrCnfgPath)
Dim AddNode As System.Xml.XmlNode
For Each AddNode In XmlDoc.Item("configuration").Item("appSettings")
If AddNode.Name = "add" Then
Select Case AddNode.Attributes.GetNamedItem("key").Value
Case "X"
AddNode.Attributes.GetNamedItem("value").Value = CType(_AppLocation.X, System.String)
Case "Y"
AddNode.Attributes.GetNamedItem("value").Value = CType(_AppLocation.Y, System.String)
Case "Width"
AddNode.Attributes.GetNamedItem("value").Value = CType(_AppSize.Width, System.String)
Case "Height"
AddNode.Attributes.GetNamedItem("value").Value = CType(_AppSize.Height, System.String)
Case "Title"
AddNode.Attributes.GetNamedItem("value").Value = CType(_Title, System.String)
Case "Show"
AddNode.Attributes.GetNamedItem("value").Value = CType(_Show, System.String)
End Select
End If
Next
XmlDoc.Save(StrCnfgPath)
End Sub

是利用XML相關的類別物件將值存入Config檔內,相關細節說明請見(Run PC 109期DynamicProperties),而我們必須在RaiseEvent之前來呼叫SaveValue副程式來做儲存設定值的動作。
4、 反應設定值於應用系統中
當我們有RaiseEvent且在建立自訂的類別執行實體時的WithEvents時,即可在SelfProperty中找到SelfCls類別物件的SelfCls_SetAppSize、SelfCls_SetAppSize、SelfCls_SetAppSize、SelfCls_SetAppSize四個事件,程式碼撰寫如下來反應設定值於應用系統中:

Private Sub SelfCls_SetAppSize(ByVal SetValue As System.Drawing.Size) Handles SelfCls.SetAppSize
Me.Size = New Size(SetValue.Width, SetValue.Height)
End Sub
Private Sub SelfCls_SetAppLocation(ByVal SetValue As System.Drawing.Point) Handles SelfCls.SetAppLocation
Me.Location = New Point(SetValue.X, SetValue.Y)
End Sub
Private Sub SelfCls_SetShow(ByVal SetValue As Boolean) Handles SelfCls.SetShow
Me.Visible = SetValue
End Sub
Private Sub SelfCls_SetTitle(ByVal SetValue As String) Handles SelfCls.SetTitle
Me.Text = SetValue
End Sub


5、 執行測試:
在VS.Net IDE中按F5執行,修改屬性值儲存,停下重新執行是否發現並未如預期的將設定值儲存下來呢?那是因為在開發環境中每次執行會將app.config內容蓋回我們儲存的設定檔中,在佈署時就不會有此問題。請找到Bin資料夾下的PropertyGrid_WinAp.exe執行即可正常操作。


結論:

這篇文章我們大致上為各位介紹怎麼樣將設計時期的IDE中屬性視窗延伸到我們程式執行時期(Run-Time),讓我們更有彈性的設計我們的系統,使其有更豐富的變化;當然接著我們也為各位介紹怎麼將自訂類別物件呈現在PropertyGrid類別物件方格內,而不單單只可呈現系統的類別物件,進而將建立一個自訂獨立的類別,而非使用各別控制項,使其設定較簡單且容易管理維護,將不必要或較細節的部分隱藏起來,而後半部為各位解說怎麼利用DynamicProperties的Config檔來保存PropertyGrid類別物件方格屬性值,細節部份各位可參考(Run PC 109 DynamicProperties)。
.NET內的PropertyGrid類別物件是個非常豐富的控制項,當然還有許多議題可以加以探討,如怎麼樣在.NET Framework型別使用TypeConverter和UITypeEditor類別,將自訂型別於呈現在PropertyGrid類別物件方格內,怎樣利用繼承ExpandableObjectConverter來提供可擴充物件的支援,怎樣提供下拉屬性的支授…等,若各位讀者有興趣可參考Shawn Burke 的文章《Make Your Components Really RAD with Visual Studio .NET Property Browser》;若您對利用PropertyGrid類別物件方格保存設定值想進一步研究,您可參考Resource Editor .NET (http://www.codeproject.org/cs/miscctrl/resource_editor.asp),它說明怎麼利用資源檔來保存設定值,包含圖檔等資料。
加入書籤: MyShare HemiDemi Baidu Google Bookmarks Yahoo! My Web PChome Del.icio.us Digg technorati furludn bookmark 其他更多書籤

BOOKS:New and Upcoming