以相同方式出入:透過程式碼保存元件
不同於以往的設計工具,.net framework 元件的 win forms 與其它 vs .net 設計工具,僅依賴表單狀態的程式碼保存性。沒有神奇的格式,也沒有隱藏資料,只是運作平穩的普通程式碼。當然,像點陣圖和本土化字串等,可以視為二進元資料與程式碼一起封裝,但元件狀態和元件所含內容,則必須透過程式碼來保存。在您設計工具的同時,也會產生程式碼。倘若您針對該程式碼進行處理,則會重新剖析,並且將變更內容反應在設計工具中。
.net framework 的設計工具可提供所有配備以利用此功能。所有設計工具對於任何類型最希望得知的內容包括:
有哪些關於物件狀態的資訊有助於保存性?
如何將該類資訊以現行物件傳回?
在先前的章節中,已經簡單討論過這個問題。再次聲明,typeconverter 是處理的核心。產生程式碼和剖析程式碼設計工具,皆與名為 creationbundle 的 persistinfo 特定類型有關。例如:
[typeconverter(typeof(intboolstring.intboolstringconverter))]
public class intboolstring {
private int intval;
private string stringval;
private bool boolval;
public intboolstring(string s, int i, bool b) {
this.intval = i;
this.stringval =s ;
this.boolval = b;
}
public bool bool{
get {return boolval;}
set {boolval = value;}
}
public int int {
get {return intval;}
set {intval = value;}
}
public string string {
get {return stringval;}
set {stringval = value;}
}
public override string tostring() {
return intval + "," + boolval + "," + stringval;
}
public class intboolstringconverter : typeconverter {
public override bool canconvertfrom(
itypedescriptorcontext context,
type sourcetype) {
return (sourcettype == typeof(string));
}
public virtual object convertfrom(
itypedescriptorcontext context,
object value,
object[] arguments) {
if (value is string) {
string stringvalue = (string)value;
int intvalue;
bool boolvalue;
int commaindex =
stringvalue.indexof(',');
if (commaindex != -1) {
intvalue = int32.
parse(stringvalue.
substring(0, commaindex));
commaindex = stringvalue.
indexof(',',
commaindex + 1);
if (commaindex != -1) {
int nextcomma = stringvalue.indexof(',', commaindex + 1);
if (nextcomma != -1) {
boolvalue = boolean.parse(stringvalue.substring(commaindex+1,
nextcomma - commaindex));
stringvalue = stringvalue.substring(nextcomma+1);
return new intboolstring(intval, boolval, stringvalue);
}
}
}
throw new formatexception("can't convert '" + stringvalue + "' to intboolstring object");
}
}
public override persistinfo getpersistinfo(itypedescriptorcontext context, object value) {
if (value is intboolstring) {
intboolstring ibs = (intboolstring)value;
return new creationbundle(typeof(intboolstring), null,
new creationargument[] {
new creationargument(ibs.int, typeof(int32)),
new creationargument(ibs.bool, typeof(bool)),
new creationargument(ibs.string, typeof(string))});
}
return base.getpersistinfo(context, value);
}
}
public override object createinstance(itypedescriptorcontext
context, idictionary propertyvalues) {
return new intboolstring((int)propertyvalues["int"],
(bool)propertyvalues["bool"],
(string)propertyvalue["string"]);
}
public override bool getcreateinstancesupported(itypedescriptorcontext context) {
return true;
}
}
使用 creationbundle 物件的好處,在於這些物件若擁有符合傳入 creationargument 中各種類型的建構函式,就能夠得知用來儲存資訊的物件建立方式。在呼叫 typeconverter::createinstance,並以此方式嘗試建立和初始設定物件時,typeconverter 的預設實作會呼叫 creationbundle::invoke。倘若無可用的建構函式,則 createinstance 呼叫會採用 idictionary,以允許物件製作更多自訂項目。傳入的 idictionary 包含每個屬性名稱中不保存的值。
元件的屬性值通常由多個物件所組成,其它架構通常就是為此目的而使用屬性陣列。然而,陣列卻有些缺點,例如,陣列必須在傳出時先行複製,於傳回時再次複製,結果當然大幅影響效能;在新增、修改或刪除值的時候,陣列也無法提供智慧型通知。事實上,如果屬性傳回陣列,是否新增或刪除項目就相當耗費工夫。陣列也是快照值,若基本物件不變更,就無法進行更新。
反之,.net framework 在這種情況下,則使用實行 icollection 物件的集合。物件可建立出集合並傳送給其它物件,同時參照項目可視基本物件的變更而保持最新狀態。若另一物件也針對集合進行變更,則亦將同時通知該物件。對於使用集合的 .net framework 設計工具,還需要支援含有 get 和 set 的 all 屬性,且其類型必須為集合可保留的物件陣列。例如:
public class intcollection : icollection {
private int[] values;
public intcollection(int[] intvalues) {
this.values = (int[])intvalues.clone();
}
public int[] all {
get {
return (int[])values.clone();
}
set {
values = (int[])value.clone();
}
}
public int count {
get {
if (values == null) {
return 0;
}
return values.length;
}
}
[browsable(false)]
public object syncroot {
get {
return this;
}
}
[browsable(false)]
public bool isreadonly {
get {
return false;
}
}
[browsable(false)]
public bool issynchronized {
get {
return true;
}
}
}
.net framework 的保存性機制,可保存或不保存本集合。若該集合由較先進的類型組成,如上述之 boolintstring 範例類型,則需要利用與類型相關聯的 typeconverter,為集合中每個項目建立有效的 persistinfo (特別是 vs .net 設計工具的 creationbundle)。
元件設計工具
如之前所述,.net framework 中的內建設計工具,足以滿足元件的多數要求。不過,.net framework 還包括元件設計工具的完整擴充性架構。所有設計工具皆以 system.componentmodel.design.idesigner 介面為基礎,列示如下:
public interface idesigner {
// 與本設計工具相關的元件
icomponent component {get;}
// 與元件相關的設計階段動作,
// 如 tabcontrol 的「add tab」
designerverb[] verbs {get;}
// 處理設計工具所使用的任何資源。
// 設計工具在本次呼叫後即無法使用。
void dispose();
// 呼叫以要求設計工具執行「預設動作」。
// 通常為了回應執行階段在元件上「連按兩下」
// 動作而呼叫。
void dodefaultaction();
// 以既定元件來初始設定設計工具。
void initialize(icomponent component);
}
不難看出,idesigner 是相當直接的。設計工具透過 designerattribute 與元件產生關聯:
[designer("mynamespace.design.mycomponentdesigner, mynamespace.dll")]
public class mycomponent : component {
}
倘若 designerattribute 未出現在類別中,則必須待找出設計工具位置後,才能夠跨越類別階層。在前述範例中,預設的 componentdesigner 可能位於元件基礎類別中,同時可供使用。有些設計工具會顯示出 ui,有些則不顯示。在 componentdesigner 中,由於元件通常沒有 ui,因此可以看到代表物件的圖示。另一方面,win 表單控制項則有設計工具可於設計階段顯示出實際控制項。
圖 3:設計階段的 win 表單控制項
請注意:位於設計工具下的圖示是未顯示 ui 的控制項;而顯示 ui 的 win 表單控制項,則於執行階段顯示在表單設計工具中。然而不論顯示與否,這些控制項全數皆以 idesigner 為基礎建立而成。控制項的設計工具通常會攔截設計中的控制項 windowproc (來自system.winforms.design.controldesigner 的設計工具可以簡單地覆寫 wndproc 方法來完成),以執行諸如點擊測試等複雜的工作。然而,對於大部份元件而言,內建的預設設計工具應該已經足夠使用。
使用設計工具服務與基礎架構
在 vs .net 中的 .net framework 設計工具,顯露出多樣化服務和基礎架構元件,可簡化複雜的作業或允許設計工具了解有關其它部份的狀態。這些服務永遠使用 getserviceobject 方法透過 iserviceobjectprovider 進行存取。以下是部分特殊設計工具服務的清單:
類型
描述
idesignerhost
與任何最上層設計工具相關的主要類別。可提供方法以增加服務、建立與放置元件、移除元件並進行批次作業。
icomponentchangeservice
新增、移除、重新命名或修改元件時提供通知。
iselectionservice
設定或取得目前設計工具中選取的項目。
itoolboxservice
允許在工具箱內查驗和修改項目及選項狀態等等。
iundoservice
提供配備以建立動作的復原/重做單位,並管理復原/重做堆疊。
ihelpservice
允許設定說明主題或者叫用說明項目。
imenucommandservice
允許處理設計工具功能表指令和動作。
ireferenceservice
在設計工具中將參照內容對應到物件。例如,名稱 button1 對應到元件 button1 。
idesignerhost 是 vs .net 中所有設計工具的基礎。idesignerhost 亦為 iserviceobjectprovider,可利用動態方式新增和移除服務。它同時也提供建立元件的方法,以確保是否置於適當的位置。以下是使用 idesignerhost 建立元件的範例:
public class mydesigner : idesigner {
// ?.
private void onsurfacedoubleclick(object s, eventargs e) {
idesignerhost host =
(idesignerhost)this.component.site.getserviceobject(typeof(idesignerhost));
if (host != null) {
object newcomponent = host.createcomponent(typeof(mycomponent));
dosomethinginterestingwithcomponent(newcomponent);
}
}
// ?}
元件授權
在擴充性的 .net framework 模型中,授權架構亦可同時擴充。為了方便使用,架構定義了一項內建的標準授權方法,以控制元件是否於設計使用階段獲得授權,不過研發人員可自由替換本機制。
// 新增 licenseproviderattribute 到控制項。
[licenseprovider(typeof(licfilelicenseprovider))]
public class mycontrol : richcontrol {
// 建立新的「空」授權。
private license license = null;
public mycontrol () {
// 使控制項的建構函式生效。
license = licensemanager.validate(typeof(mycontrol), this);
// 是否執行其他範例工作? }
public override void dispose() {
if (license != null) {
license.dispose();
license = null;
}
}
protected override void finalize() {
dispose();
base.finalize();
}
}
此範例可以使用內建授權支援來啟動授權工作。licfilelicenseprovider 只要在類別的 assembly 目錄中,尋找名為 <classname>.lic 的檔案,其中 classname 必須是完整的類型名稱。例如,對於 string 類型而言,名稱則為 system.string.lic。本檔案可包含字串「system.string 是已授權的元件」。若發現本檔案,licensemanager.validate 會傳回授權物件,並隨著類別實例來進行處置。
實行您個人的授權機制也一樣輕鬆簡單。只要建立取自 licenseprovider 的個人類別,並實行個人的 getlicense 方法即可。您可以登錄為基礎實行授權機制,其中已授權的設計階段元件在登錄檔中有一個項目:
public class registrylicenseprovider: licenseprovider {
public override license getlicense(
licensecontext context,
type type,
object instance,
bool allowexceptions) {
registrykey licensekey = registry.localmachine.
opensubkey("software//mycompany//componentlicenses");
if (context.usagemode == licenseusagemode.designtime) {
if (licensekey != null && licensekey.getvalue(type.fullname) != null) {
return new reglicense(this, type);
}
if (allowexceptions) {
throw new licenseexception(type, instance,
"couldn''t get design-time license for ''"" +
type.fullname + "''");
}
return null;
}
else {
return new runtimereglicense(this, type);
}
}
private class runtimereglicense : license {
public string licensekey {
get {
return type.fullname;
}
}
public override void dispose() {
}
}
private class reglicense : license {
private registrylicenseprovider owner;
private type type;
public reglicense(registrylicenseprovider owner, type type) {
this.owner = owner;
this.type = type;
}
public string licensekey {
get {
return type.fullname;
}
}
public override void dispose() {
}
}
[licenseprovider(typeof(registrylicenseprovider))]
public class mycontrol : control {
}
使用本授權的元件在下列登錄檔中會有一個登錄項目:
hkey_local_machine/software/mycompany/componentlicenses
<type full name>="true"
結論
在管理程式碼中撰寫控制項,可為傳統的 c++/com 方法帶來更多的優勢。microsoft 從最基本的通用語言執行階段開始策劃,遍及至 c# 甚至重組的 visual basic 語言,以提供研發人員效率最高的通用方法,將建構軟體時的阻礙降到最低。microsoft .net framework 是第一個以這些技術和原理為基礎,所研發成功的大型程式碼庫範例,而支援的整合式設計工具,則是本方法成功的關鍵。