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