顯示具有 DynamicProperties 標籤的文章。 顯示所有文章
顯示具有 DynamicProperties 標籤的文章。 顯示所有文章

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),它說明怎麼利用資源檔來保存設定值,包含圖檔等資料。

2009年1月2日 星期五

PropertyGrid控制項1-1

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

什麼PropertyGrid控制項元件呢?如果讀者有使用過早期的Visual Basic或現今的Visual Studio.NET IDE(整合開發環境)開發環境,一定對『屬性視窗』的簡易設定、快速反映和良好的互動有著深刻的體會,顯然地『屬性視窗』已成為微軟快速應用程式開發(Rapid Application Development;RAD)不可獲缺的重要元素之一,這也是初學者為什麼這麼快就愛上VB的其中一項原因了。看到這兒讀者是否會想到,如果可以將IDE中的屬性視窗延伸到我們程式執行時期(Run-Time),那可不妙哉!沒錯這正式我們接下來所要聊的.NET Framework所提供的這項類別物件『PropertyGrid控制項元件』,它是Visual Studio.NET中屬性視窗的核心。
在開始PropertyGrid控制項元件的介紹之前,讓我們先回顧早期Visual Basic和今時Visual Studio.NET IDE內的屬性視窗有著何許差異,若單純由外觀呈現的方式來區分:Visual Studio.NET IDE內的屬性視窗除具有早期VB屬性視窗的[物件方塊]、[屬性清單]的[字母順序]和[性質分類]頁籤和[說明窗格]外,還加了一些特點,我們以字型(Font)為例,早期只可借由開啟[屬性頁]來做相關字型大小、字型樣式、效果…等字型細節設定,而今可多一項選擇可直接由屬性視窗直接展開Font階層來做相關細節設定,請見(圖一),當然如果您有設定相關圖示選項屬性時,屬性視窗亦會提供圖示預視縮圖的功能,除此之外還加上屬性預設值異動標明功能,如將Font由[新細明體]改為[標楷體],於Visual Studio.NET IDE的屬性視窗內可見Font內的值是以粗體顯示…等。

(圖一)早期VB與VS.NET屬性視窗介面上的差異

以上介紹的是介面呈現上的差異,接下來讓我們來看看早期與現今本質上的差異,先前版本是處理COM型態資訊是UnManage的,通常以IDL(Interface Definition Language)來定義COM公開的API,而它有組相關連的Attributes(特性)集合是用來定義屬性部份,如:nonbrowsable(是用來防止屬性被瀏灠);bindable(是用來標記為適用data-bind的屬性)……等,而在.NET Framework、Visual Studio.NET是利用.NET Framework上Manage的Windows Forms類別,它可以讓我們更簡易且更統一的方法來提供更多現行和新的功能,在比較過早期與現行本質上與外觀介面呈現上的差異後,讓我們來看一個簡單的例子。

利用PropertyGrid控制項於Run-Time時期控制控制項屬性設定值:

1、 建立Windows應用程式新專案:
從[開始][所有程式][ Microsoft Visual Studio .NET][ Microsoft Visual Studio .NET]開啟Visual Studio .NET IDE於[啟始頁]視窗中的[專案]頁籤中按[新增專案],在新增專案對話盒內的[專案類型]選擇[Visual Basic專案]資料夾,在[範本]中選擇[Windows應用程式],最後修改[名稱]為『PropertyGrid_WinAp』按確定,至此我們建立一個名稱為『PropertyGrid_WinAp』的Windows應用程式專案。
2、 加入PropertyGrid控制項到工具箱中:
一般在我們開啟專案後在工具箱中預設並不會把[PropertyGrid控制項]加入,我們必須以手動的方式將它加入以便使用,其操作步驟:從 [工具] 功能表中選取 [自訂工具箱],在對話方塊中選取 [.NET Framework 元件] 索引標籤,然後在鍵盤上按[P]快速找到以P為開頭的控制項後選取 [PropertyGrid]控制項按確定,再到[工具箱]的頁籤中即可找尋到圖示如: 的[PropertyGrid控制項]。
3、 加入控制項到表單中:
從工具箱中一如往常的將[PropertyGrid]、[TextBox]、[Label]、[ComboBox]等控制項一一拖曳至表單上,將PropertyGrid控制項名稱改為MyPropertyGrid,其餘使用預設名稱。
4、 撰寫相關程式碼:

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim Obj As Object
For Each Obj In Me.Controls
ComboBox1.Items.Add(Obj.name)
Next
End Sub

上面的程式碼是於Form1_Load事件中(即表單載入時)將在表單上的每一個控制項名稱列舉在ComboBox1下拉選單中,此處的Me代表表單本身。

Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
Dim Obj As Object
For Each Obj In Me.Controls
If Obj.name = sender.SelectedItem Then MyPropertyGrid.SelectedObject = Obj : Exit Sub
Next
End Sub

將表單上所有的控制項名稱加入ComboBox1下拉選單中後,接下來我們在ComboBox1_SelectedIndexChanged事件中(即ComboBox1選項改變時),將選中的物件設定給MyPropertyGrid.SelectedObject即在MyPropertyGrid方格中顯示該物件的屬性。
5、 執行與檢驗
請按[F5]執行,在ComboBox1中選擇我們要在MyPropertyGrid方格中顯示該物件屬性的控制項名稱,即可立即於MyPropertyGrid控制項中見到所選擇控制項的屬性,如(圖二)。
若要修改Label1的前景顏色為紅色,一如設計時期(Design-Time)修改屬性視窗中的設定值一般,將其ForeColor設為紅色即可,如(圖三) Label1的前景顏色立即轉為紅色,看到這兒讀者是否和筆者一樣感到份驚奇與興奮呢?該控制項讓誐們將設計時期做的事延伸到執行時期耶!


(圖二)於[PropertyGrid] 控制項方格中顯示[Label1]控制項的屬性



(圖三) 利用[PropertyGrid] 控制項將設計時期做的事延伸到執行時期

6、 應用與思考
在應用程式開發的過程中有種防止使用者鍵入錯誤的資料,或甚提醒、導引使用者鍵入應建立的資料,我們稱之為『防呆』的作法,而列舉選項給使用者來操作也是其中一項防呆的作法,而我們怎麼利用[PropertyGrid] 控制項來將應用程式作得更Smart呢?例:若在ComboBox2下拉選單是列舉敝公司年度公司旅遊方案,方案一:購買住宿卷二張;方案二:北台灣二天一夜遊;方案三:南台灣二天一夜遊供各位同仁選擇參考,但後來經主管裁示方案四:買住宿卷一張+北台灣一日遊;方案五:買住宿卷一張+南台灣一日遊此二方案,但此時投票系統已開發完成,是故只能在Run-Time時間來改變設定,請見(圖四)在ComboBox1下拉選單中選擇ComboBox2後於MyPropertyGrid方格中選擇[Items]叫出[字串集合編輯器],再輸入後二項方案即可在回到ComboBox2下拉即可看見共五個方案可供選擇,請見(圖五)是不是變得很Smart呢?不過投票這事,似乎也只是投票而已,一點也都不Smart,票數只不過是個Stupid的數字遊戲絲毫不具任何意義。


(圖四)原下拉選單



(圖五) 在執行時期動態加入下拉項目

到目前為止我們介紹的是由工具箱中拖曳[PropertyGrid] 控制項,然後才撰寫相關的程式碼,使其可以在Run-Time時設定控制項物件的相關屬性,接下來我們要介紹的是怎麼樣利用程式碼類別的方式來建立與呈現物件的相關屬性,其步驟如下:

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

Imports System
Imports System.Drawing
Imports System.Windows.Forms

Public Class ClsProGrid
Inherits System.Windows.Forms.Form
Private ProGrid As PropertyGrid
Public Sub New()
MyBase.New()
ProGrid = New PropertyGrid()
ProGrid.Size = New Size(300, 500)
ProGrid.SelectedObject = ProGrid
Me.Controls.Add(ProGrid)
Me.Text = "執行時期屬性視窗"
End Sub
End Class

上面程式碼Imports的部份是匯入的命名空間(Namespace),我們使用System.Drawing命名空間內的Size結構來設定PropertyGrid控制項的大小,而我們使用System.Windows.Forms命名空間下的PropertyGrid類別來建立該控制項(註1),ProGrid.SelectedObject = ProGrid呈現本身的屬性;Me.Controls.Add(ProGrid)將ProGrid加入至表單上。
註1:若專案要使用到的共通命名空間(Namespace),我們可以將其命名空間匯入於專案層內,而非於每個類別或表單內加入Imports敘述,如上我們可以將Imports System、System.Drawing、System.Windows.Forms三行敘述去除,而在[專案屬性頁]內的[通用屬性]下的[匯入]中將System、System.Drawing、System.Windows.Forms命名空間加入匯入至專案匯入區即可(圖六)。


(圖六)匯入專案層級的命名空間

3、 設定啟始物件:在執行之前我們需改變它的啟始物件,如(圖七)選擇啟始物件為[ClsProGrid],後按確定,執行即可見著如(圖八)之畫面。


(圖七)修改啟始物件



(圖八)執行時期屬性視窗

看到這兒各位是否會有個疑問,難到PropertyGrid只可顯示一般系統所提供的控制項類別物件的屬性嗎?我們可以依照我們的需求來自訂自已所屬的類別物件於PropertyGrid控制項中顯示嗎?答案當然是可以的,如此做的話將建立一個自訂獨立的類別,而非使用各別控制項,設定會較簡單且容易管理維護,將不必要或較細節的部分隱藏起來,接下讓我們繼續探索[PropertyGrid]控制項的奧祕吧!

2008年11月22日 星期六

DynamicProperties動態屬性設定1-3

RunPC 109期:在.Net上保存應用程式設定(一)
(2) 如何在專案中設定非預設的動態屬性設定(自訂的動態屬性設定)
在Form1表單屬性視窗上點DynamicProperties下之Advanced出現如圖六之對話盒,其列表皆為預設的動態屬性,所以我們在屬性視窗中與該對話盒內比對找尋沒有對應的屬性來成為這節的例子,恰好我們找到屬性視窗中有Location屬性,而Dynamic Properties對話盒沒有該設定視窗位址的選項,就讓我們舉Location屬性為非預設的動態屬性的例子,步驟如下:

(圖六)


1. 於app.config內輸入:

< key="Form1.X" value="10">
< key="Form1.Y" value="10">
儲存,代表設定表單於瑩幕上的位址。
註:Location屬性內由X,Y所組成,表示表單Form1左上角點,是故該例子以保存表單的X,Y值(“Form1.X;Form1.Y)為例。

2. 於Sub New程序內模擬InitializeComponent內的作法利用AppSettingsReader類別來讀取設定檔的內容值;宣告x,y為整數,利用AppSettingsReader的GetValue方法,傳入二參數一為key值("Form1.X","Form1.Y")另一為傳回值所屬的資料型態為System.Int32再將資料型態轉為整數Integer,最後將表單的位置移至(x,y)這點上。

Dim MyConfigReader As System.Configuration.AppSettingsReader=New system.Configuration.AppSettingsReader()
Dim x,y As Integer
x = CType(MyConfigReader.GetValue("Form1.X",GetType(System.Int32)), Integer)
y = CType(MyConfigReader.GetValue("Form1.Y",GetType(System.Int32)), Integer)
Me.Location = New System.Drawing.Point(x, y)

注意:因為InitializeComponent是系統所產生的程序,不允許被修改。
3. 執行後卻發現未能如預期的將表單移置(x,y)這點上,甚至呈現的位置也都沒有改變,那是因為未將將StartPosition改為Manual,將StartPosition改為Manual再次執行,表單就可以移置正確的位置上。

這段我們模擬系統所產生的程式碼,來完成我們自訂動態屬性部分,重點是在如何利用AppSettingsReader類別來取得設定檔案的設定值,接下來讓我們思考當所開發的系統安裝後,使用者使用時我們總不可能請他手動去更改設定檔案部分,那該怎樣可以在系統執行時期時,提供如VS.Net IDE環境設定 (圖七) 般的使用者介面(UI)給使用者來設定,以防手動設定上造成不可預期的錯誤,這正是下面所要討論的。


(圖七)

(3) 如何在執行時期保留動態屬性值
此處我們會說明怎樣用.Net所提供的System.Xml名稱空間下的相關類別來維護XML(即app.config)的資料,及使用System.Reflection來找到設定檔的位置。
讓我們延續上面的例子使它可以在執行時期,保存視窗目前的位置(X,Y),待下次再次載入表單時,可以取回上次保存後的設定值,以便設定視窗位置。
1. 使用者介面:在表單上建立二個Label(Label1、Label2)、二個TextBox(txtX、txtY)來呈現目前視窗的位置、二按鈕(CmdSet、CmdSave),當按下CmdSet按鈕時來設定視窗位置;而當按下CmdSave時儲存視窗目前位置至設定檔案中, Label1、Label2的Text屬性分別設X:和Y:。
2. 在Sub New程序中之Me.Location = New System.Drawing.Point(x, y)下加入:

txtX.Text = CType(x, System.String)
txtY.Text = CType(y, System.String)

取得目前視窗啟動所在位置。
3. 於CmdSet_Click事件中加入以下程式碼,用來立即設定視窗位置。

Dim X, Y As Integer
X = CType(txtX.Text, System.Int32)
Y = CType(txtY.Text, System.Int32)
Me.Location = New System.Drawing.Point(x, y)

4. 於Form1_Move事件中加入以下程式碼,在視窗位置移動時txtX、txtY立即顯示視窗所在位置。

txtX.Text = CType(Me.Location.X, System.String)
txtY.Text = CType(Me.Location.Y, System.String)

5. 於CmdSave_Click事件中撰寫儲存目前位置至設定檔中。
利用Reflection來找設定檔的位置

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 "Form1.X"
AddNode.Attributes.GetNamedItem("value").Value = CType(Me.Location.X, System.String)
Case "Form1.Y"
AddNode.Attributes.GetNamedItem("value").Value = CType(Me.Location.Y, System.String)
End Select
End If
Next
XmlDoc.Save(StrCnfgPath)

RA.Location取得目前執行檔案的位置,而再加上".config"即前面說的佈署時設定檔的位置,而非開發時期用的app.config設定檔案。再來利用.Net操控XML的XmlDocument類別載入設定檔(StrCnfgPath),再找出每個在"configuration"下"appSettings"的節點,判斷節點的名稱是否為"add",如果是的話比較該節點的"key"值是否為"Form1.X"或"Form1.Y"(當然也可以是其它您所設定的key值),再將目前表單的位置存回它對應"key"的"value"值,最後存回設定檔中,即可完成儲存設定的動作。
註:在本例VS.Net IDE中按F5執行,儲存目前所在位置(X,Y),停下重新執行是否發現並未如預期的將設定值儲存下來呢?可別太快下結論以為這是系統Debug還是程式有謬誤喔!那是因為在開發環境中每次執行時會將app.config內容蓋回我們儲存的設定檔內,而我們在上面程式碼所抓取的StrCnfgPath卻是『執行檔名.exe.config』而非app.config設定檔案,是故只要在佈署時就不會有此問題。

結論:
這篇文章中讓我們體驗到微軟在VS.Net IDE內的一個體貼的設計(DynamicProperties),甚至讓我們不用撰寫一行程式碼就可以輕鬆設定預設的動態屬性,完成早期需要繁雜程序才可完成的動作,而自訂的動態屬性設定只要模擬學習系統產生的程式碼,即可以輕易完成設定,最後我們談到怎麼在執行時期保留動態屬性值,讓使用者可以簡易的完成保存應用程式設定值,而不至於要手動修改設定檔案。其中我們也說明了開發時期的app.config設定檔與佈署安裝的『執行檔名.exe.config』設定檔案的關係,希望該篇文章對讀者有所幫助。我們預計於下一篇文章中為各位介紹如何在.Net中更彈性的保存應用程式設定值,怎麼利用.Net上XML相關物件來操控自訂的XML設定檔案,如表(一)中選項配分中的階層關係……等,而非只是Key與Value的成對關係。

2008年11月16日 星期日

DynamicProperties動態屬性設定1-2

RunPC 109期:在.Net上保存應用程式設定(一)
二、.Net動態屬性設定方式

當各位讀者看完以上.Net之前保存應用程式各種設定的方式,是否可以意識到保存應用程式設定檔於真實系統中的重要性和在佈署系統上的方便性,可將應用程式的相關設定參數展期至系統佈署時期才做,增加系統設定上的彈性,而非一定要在開發時期將系統相關參數綁死在程式碼內。
如果您接觸過.Net一段時日,您是否發覺到XML已被廣泛的應用在.Net上,這就是我們常聽到的.Net是架構在XML的基礎上,不論是ADO.Net內的DataSet還是Web Service的Soap、WSDL甚至於此篇文章所提及的設定檔案部分亦是XML的格式,但它的副檔名為config而非正常情況下的xml,在視窗應用程式它的設定檔案預設檔名為app.config;而Web應用程式它的設定檔預設檔名為web.config,在VS.Net中建立Web應用程式專案時,web設定檔(web.config)即隨專案而被建立,而VS.Net 中建立視窗(Windows)應用程式專案時,app.config卻沒有隨專案而被建立,而要等到在屬性視窗中設定DynamicProperties的時間點上才會被建立,這是二設定檔建立時間上的差異點。
接下來讓我們開始探索.Net上的動態屬性設定,在此篇文章中將為各位介紹(1)如何設定預設的動態屬性(DynamicProperties)。(2)如何在專案中設定非預設的動態屬性設定(自訂的動態屬性設定)。(3)如何在執行時期保留動態屬性值。

讓我們舉一個動態設定資料庫連結參數的例子,如何利用.Net所提供的DynamicProperties將資料庫連結參數的設定延展至系統佈署時期,接下來就讓我們來看它的操作步驟:
(1) 設定預設的動態屬性步驟:
操作步驟前我們先假設:開發電腦上所連結的資料庫所在電腦名稱為DevPc,佈署後要連結的資料庫電腦名稱為RunPc,測試資料庫為Pubs;測試開發時期連結到DevPc這台電腦上的Pubs資料庫,而安裝佈署後連結至正式使用的電腦名稱為RunPc上的資料庫Pubs。
1. 建立新專案:選擇您所喜好的語言,VB.Net或VC#…等,在此以VB.Net為例,選Windows Application專案,將此專案命名為ProDynamicPropers。
2. 設定資料連結:利用『Ctrl+Alt+S』叫出Server Explorer,點選Connect to Database圖( ),則系統會出現資料連結對話盒,於伺服器名稱上打上DevPc(開發時期所用的測試資料庫機器)後輸入連至該資料庫的帳號、密碼,勾選允許儲存密碼,再選Pubs資料庫後按確定,到現在為止只是在VS.Net IDE上建立一個資料連結(DevPc.Pubs.dbo),再來讓我們用簡單而快速的拖曳方式來建構應用程式資料連結物件部分。
3. 建立應用程式資料連結相關物件:展開Data Connections下DevPc.Pubs.dbo資料連結,再展開其下的Tables後將authors資料表拖曳至Form1表單上,系統會自動建立SqlConnection1、SqlDataAdapter1這兩個物件,選SqlDataAdapter1物件按右鍵點Generate Dataset,出現Generate Dataset對話盒後按OK,產生DataSet11物件。
註:SqlConnection1物件是應用程式與MS SQL資料庫連結的物件;SqlDataAdapter1物件是應用程式透過SQL命令經由SqlConnection1物件對MS SQL資料庫進行操作;而DataSet11物件是一分在類似在記憶中的資料庫且是離線的,它可來自不同的資料庫或其它實體檔案;對於.Net上詳細的操控資料的方式可參考相關ADO.Net的書籍。
4. 建立資料呈現介面:利用『Ctrl+Alt+X』快速鍵叫出Toolbox,點選Windows Forms頁籤,拖曳DataGrid控制項 至Form1上,雙按Form1表單進入程式碼視窗或直接點選Solution Explorer上的View Code按鈕,於Form1_Load事件中輸入以下程式碼:
Me.SqlDataAdapter1.Fill(DataSet11, "Authors")
Me.DataGrid1.DataSource = DataSet11.Tables("Authors").DefaultView
透過SqlDataAdapter1的Fill方法,從MS SQL資料庫中取回資料放到DataSet11離線資料庫中,且命名為"Authors"的DataTable;利用DataGrid1來呈現資料而它的來源正是上面DataSet11內"Authors"的DataTable。按F5執行資料庫中的資料會於DataGrid1中呈現。
5. 觀察動態屬性設定前之連結參數設定處:於程式碼視窗中展開 ”Windows Form Designer generated code”,您可以在InitializeComponent程序中找到底下這段程式碼:
Me.SqlConnection1.ConnectionString = "data source= DevPc;initial catalog=pubs;password=1234;persist security info=T" & _
"rue;user id=sa;workstation id=DevPc;packet size=4096"
由此可知目前程式碼與資料庫連結參數是綁在一起的,每次安裝前就得改這部分的連結碼,再次編譯方可正確的佈署到正式的平台上工作。
6. 建立動態屬性:點選SqlConnection1物件,按F4叫出屬性視窗,展開DynamicProperties選項,選擇ConnectionString之ellipsis按鈕( ),勾選[Map Property to a key in configuration file]按OK,此處的Key是存在app.config的Key值除非特殊需求否則不建議修改,它的命名規則為『物件名.屬性名』。
注意:此時app.config才被建立。
7. 觀察建立完動態屬性後的差異:
a、屬性視窗中被設定為動態屬性之屬性會出現 小圖示見圖四,左邊還未建立ConnectionString的DynamicProperties而右邊已建立ConnectionString的DynamicProperties。



(圖一)VB6內設計時期內設定命令列引數

(圖二)VS.NET內設計時期設定命令列引數

(圖三)佈署時期設定引數



b、解析設定檔(app.config)物件的建立:
Dim configurationAppSettings As System.Configuration.AppSettingsReader = New System.Configuration.AppSettingsReader()
AppSettingsReader類別是.Net內建用來解析設定檔的類別,位於System.Configuration名稱空間之下,所建立解析物件configurationAppSettings之GetValue方法可用來傳入app.config 檔appSettings內add元素的key值來取得該value值。
c、連結參數的改變:
Me.SqlConnection1.ConnectionString = CType(configurationAppSettings.GetValue("SqlConnection1.ConnectionString", GetType(System.String)), String)
SqlConnection1.ConnectionString:為6、Dynamic Property對話盒中的Key也是app.config內appSettings下的其中之一Key值,而後的GetType(System.String)參數是代表"SqlConnection1.ConnectionString" 鍵值所取回的資料型態是屬字串。


8. 模擬應用程式連結測試資料庫(DevPc)改為連結至正式資料庫(RunPc):將app.config內key為SqlConnection1.ConnectionString之data source=DevPc改為data source=RunPc(前題假設資料庫名稱同為Pubs,而帳號、密碼不變),按F5執行,觀查是否已抓取正式資料庫之資料。
9. 佈署上設定檔的差異:在.Net上只要利用XCopy即可佈署所開發的系統,現在將該系統的執行檔(ProDynamicPropers.exe)與設定檔(app.config)複製到另一台電腦同一個目錄後,檢查設定檔案(app.config)的連結設定,正確無誤後執行,但卻會出現如下圖五的錯誤,那是因為系統真正參考到的設定檔案並非app.config檔案而是在bin資料夾下的ProDynamicPropers.exe.config設定檔,是故將app.config檔名改為ProDynamicPropers.exe.config即可正確運作。
註:在VS.Net IDE環境下每次執行時,會將bin資料夾內的ProDynamicPropers.exe.config的內容改成app.config的內容,而該執行檔案也是參考該資料夾下的『執行檔名.exe.config』。


(圖五)


至此我們已經完成預設的動態屬性設定操作步驟,而且在動態屬性部分還沒寫一行程式碼,完全利用VS.Net IDE自動設定的功能來完成,讀者是否體驗到用VS.Net來開發系統就是這麼簡單呢?接下來讓我們來探索非在DynamicProperties下所提供的動態屬性設定方式。

2008年11月12日 星期三

DynamicProperties動態屬性設定1-1

MyShare furl Yahoo! My Web TwitThis ');//-->

RunPC 109期:在.Net上保存應用程式設定(一)

在與各位讀者探索.Net的應用程式設定DynamicProperties(動態屬性)設定方法之前,我們先來了解一下它可能運用地方:如:資料庫的連結這是它最常被應用到的地方,各位是否和筆者早期於學校撰寫畢業專題(進銷存管理系統),用VB開發資料庫應用程式一般,將Visual Basic與MS SQL Server相關連結的部分深植於應用程式中或直接由ADODC按右鍵設屬性頁簡易設定而帶來往後安裝與維護上的惡夢,每當開發系統的資料庫由本機移植到另一台學校測試的SQL Server上時,系統與資料庫連結的部分須重寫,然後再次重新編譯原始碼,方可以安裝、佈署系統,雖然當時的筆者已知要將連結資料庫的參數設成常數,往後維護只要更改常數部分即可,但還是造成筆者在佈署上的諸多不便。後來筆者於實作中了解到動態設定屬性參數對真實開發系統的重要性,如:上面所提到的資料庫連結參數、系統環境參數的配置…等,將其配置於外部檔案,在所部佈署的機器上即使系統環境不同時只需改變外部設定檔案參數即可,而不用重新再次編譯原始碼。

一、早期動態參數設定方式概述
當然早在.Net還未出現前,我們已經可找到相關的動態應用參數設定解決方式,請參考如下:
1、 最簡單的方式是利用File System Object讀取外面某個文字檔內儲存值來當系統參數(參考:Programming in the FSO Object Model),File System Object讀取外面文字檔為非標準作法,不建議使用。
2、 早期系統開發上較常用的是讀取ini檔案的方式,可參考Using INI Files to Save Application Data - Saving Entire Sections 這篇文章,此方式會有所限制,所以建議改採用第五項作法(XML設定檔方式)。
3、 直接截取命令列引數:使用Command方法來當參數,在.Net上改用System.Environment.CommandLine方法來截取命令列引數,在此簡述它們的開發和佈署時期的應用方式;VB6的開發設定方式:選專案功能表內『專案屬性』打開專案屬性對話盒,選擇『製成』頁讖,於命令列的引數內輸入相關引數(如:Arg1,Arg2…)見圖一;VS.Net的開發設定方式:選Project功能表內Properties,出現Project Property Pages選擇Configuration Properties內的Debugging在於Start Options項目中找到Command line arguments輸入相關引數(如:Arg1,Arg2…)見圖二;佈署後的設定方式:建立該專案執行檔(如:MyApp.exe)的捷徑,再選擇捷徑內容於『捷徑』頁讖內目標項內後帶相關參數見圖三,即可用Command或System.Environment.CommandLine來截取。


(圖一)VB6內設計時期內設定命令列引數

(圖二)VS.NET內設計時期設定命令列引數

(圖三)佈署時期設定引數

4、 利用註冊檔為參數值儲存所,使用GetSetting方法取得註冊檔內資料、SaveSetting方法來儲存資料到註冊檔內、DeleteSetting方法刪除應用程式在註冊檔內的機碼。
註:一般VB6所儲存的註冊檔位置為『HKEY_CURRENT_USER\Software\VB and VBA Program Settings\』到當然若要儲存到非該位置下,則需使用到特殊API的呼叫。
5、 彈性最佳的方式,配合MSXML取得外部自訂的XML設定檔。如今該方式已成為筆者開發系統最常應用留存系統設定檔的方式,也是筆者在XML應用上最為廣泛使用。如:筆者在系統開發上的一個實際的例子,財吉寶現金卡融資審核表及其相關消費性貸款融資審核表,因業務上需求各選項配分必須隱藏且必須因應市場反應而作適當彈性調整配分,而XML恰好俱自我描述功能與樹狀結構設計,是故筆者以XML來訂定相關系統選項配分、描述與資料庫連結參數…等,請見表一。
註:此處融資審核表還有一處有趣的部分,它產生出來的融資審核表為非HTML而是動態的Word報表,且是不可以修改的喔!有興趣的讀者可參考以上的網址。


(表一)消貸系統融資審核表XML設定檔

以上五種方法為非.Net平台上的作法而此篇文章將焦點放置於.Net的應用上,在此就不加以詳細說明,讀者若有興趣了解相關主題可參考相關連結或來信與筆者討論。

MyShare furl Yahoo! My Web TwitThis ');//-->

加入書籤: MyShare HemiDemi Baidu Google Bookmarks Yahoo! My Web PChome Del.icio.us Digg technorati furludn bookmark 其他更多書籤

BOOKS:New and Upcoming