您的位置:首页 > 其它

翻译文章:元数据驱动的用户界面(Metadata-Driven User Interfaces)

2008-04-11 14:13 621 查看
查看原文
John deVadoss
微软公司
2005.7

用于:
企业体系结构
用户界面

概述:本文解释了如何由元数据动态创建Windows窗体,以及当考虑到客户端逻辑定制时如何执行窗体。

目录
介绍
阅读前提
设计要素
设计时实现
运行时实现
结论

介绍

因为客户之间、用户之间需求的变化,很多业务应用程序需要用户界面可护展,用户界面的客户端业务逻辑也需要根据个别用户的需要定制。一个用户与另一个用的屏幕布局可能不同,这可能包括控件位置,可见性等等,或者可能因为设备不同而界面不同,就象智能电话,平板设备或个人数字助理。客户端业务逻辑定制也包括:定制有效性规则,更改控件属性,以及其他修正。例如:为了删除及移动文件,经理比下属拥有不同的特权。

有很多技术能够实现业务应用程序的可护展、可定制,大多数应用程序通过将可定制项,如用户界面布局及客户端业务逻辑作为元数据存储在仓储中解决。元数据因而可由运行时引擎解释,从而为用户显示屏幕,以及当用户在屏幕完成动作时执行客户端业务逻辑。

这种方式的优点是双重的:第一,当表示层相关组件在中心仓储完成定制时,不需要重新部署;第二,仅需要非常轻的客户端安装,也就是仅需将运行时引擎部署到客户端机器上。

本文描述了利用微软.NET Framework实现的此技术,为此,本文解释了如何由元数据创建Windows窗体,以及考虑到客户端逻辑定制时如何运行窗体。

阅读前提

l 了解关于编写Visual Studio Add-Ins的知识
l DTE对象模型的用途

设计要素

实现元数据驱动的用户界面,本质上有三个设计要素。

l 首先是设计元数据架构并决定仓储机制。仓储可以是关系数据库,如SQL Server或任何其他存储,如XML。本文我们使用XML文件作为元数据仓储。
l 当我们确定元数据架构后,我们实现一个设计时环境,在此环境中用户(通常为开发者或定制人)能创建及修改特定屏幕。本文将跨在微软Visual Studio Windows Forms Designer(窗体设计器)之上来实现设计时环境。
l 最后,我们设计并实现运行时引擎。

仓储结构

首先,我们要决定能以元数据存储的内容。如上述所提,元数据应该包含所有可定制的项目。例如,如果客户想定制屏幕布局及客户端验证逻辑,那么元数据应该包含有关界面布局信息,包括放置在屏幕的控件,他们的属性,放置位置,元数据也应该包括客户端业务逻辑。

以下是一个包括界面信息的XML文件例子。

<?xml version="1.0" encoding="utf-8"?>
<form xmlns:xsd=http://www.w3.org/2001/XMLSchema
xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance" name="Form1" xmlns="http://tempuri.org/
MetadataForm.xsd">
<controls>
<control type="System.Windows.Forms.Form">
<position>
<height>224</height>
<width>300</width>
<top>13</top>
<left>13</left>
</position>
<controls>
<control type="System.Windows.Forms.Button">
<position>
<height>23</height>
<width>75</width>
<top>144</top>
<left>160</left>
</position>
<controls />
<name>button2</name>
<text>button2</text>
<tabIndex>2</tabIndex>
<event-handlers />
</control>
<control type="System.Windows.Forms.Button">
<position>
<height>23</height>
<width>75</width>
<top>144</top>
<left>64</left>
</position>
<controls />
<name>button1</name>
<text>button1</text>
<tabIndex>1</tabIndex>
<event-handlers>
<eventInfo eventName=
"Click" eventHandler="button1_Click" />
<eventInfo eventName=
"EnabledChanged" eventHandler="button1_
EnabledChanged" />
</event-handlers>
</control>
<control type="System.Windows.Forms.TabControl">
<position>
<height>100</height>
<width>200</width>
<top>24</top>
<left>48</left>
</position>
<controls>
<control type="System.Windows.Forms.TabPage">
<position>
<height>74</height>
<width>192</width>
<top>22</top>
<left>4</left>
</position>
<controls>
<control type="System.Windows.Forms.Button">
<position>
<height>23</height>
<width>56</width>
<top>24</top>
<left>112</left>
</position>
<controls />
<name>button3</name>
<text>button3</text>
<tabIndex>2</tabIndex>
<event-handlers>
<eventInfo eventName=
"Click" eventHandler="button3_Click" />
</event-handlers>
</control>
<control type="System.Windows.Forms.TextBox">
<position>
<height>20</height>
<width>64</width>
<top>40</top>
<left>16</left>
</position>
<controls />
<name>textBox2</name>
<text>textBox2</text>
<tabIndex>1</tabIndex>
<event-handlers />
</control>
<control type="System.Windows.Forms.TextBox">
<position>
<height>20</height>
<width>64</width>
<top>8</top>
<left>16</left>
</position>
<controls />
<name>textBox1</name>
<text>textBox1</text>
<tabIndex>0</tabIndex>
<event-handlers />
</control>
</controls>
<name>tabPage1</name>
<text>tabPage1</text>
<tabIndex>0</tabIndex>
<event-handlers />
</control>
<control type="System.Windows.Forms.TabPage">
<position>
<height>74</height>
<width>192</width>
<top>22</top>
<left>4</left>
</position>
<controls>
<control type="System.Windows.Forms.LinkLabel">
<position>
<height>23</height>
<width>120</width>
<top>24</top>
<left>24</left>
</position>
<controls />
<name>linkLabel1</name>
<text>linkLabel1</text>
<visible>false</visible>
<tabIndex>0</tabIndex>
<event-handlers>
<eventInfo
eventName="LinkClicked" eventHandler=
"linkLabel1_LinkClicked" />
</event-handlers>
</control>
</controls>
<name>tabPage2</name>
<text>tabPage2</text>
<visible>false</visible>
<tabIndex>1</tabIndex>
<event-handlers />
</control>
</controls>
<name>tabControl1</name>
<text />
<tabIndex>0</tabIndex>
<event-handlers />
</control>
</controls>
<name>Form1</name>
<text>Form1</text>
<tabIndex>0</tabIndex>
<event-handlers>
<eventInfo eventName="Load" eventHandler="Form1_Load" />
</event-handlers>
</control>
</controls>
<script>
<events eventHandler="linkLabel1_LinkClicked">
<content> private void linkLabel1_LinkClicked
(object sender, System.Windows.Forms.
LinkLabelLinkClickedEventArgs e)
{

}</content>
<type>text</type>
</events>
<events eventHandler="Form1_Load">
<content> private void Form1_Load(object sender, System.EventArgs e)
{
textBox1.Text = " " ;
textBox2.Text = " " ;
}</content>
<type>text</type>
</events>
<events eventHandler="button1_Click">
<content> private void button1_Click(object sender, System.EventArgs e)
{
textBox1.Text = "Button 1 Clicked" ;
}</content>
<type>text</type>
</events>
<events eventHandler="button1_EnabledChanged">
<content> private void button1_Enabled
Changed(object sender, System.EventArgs e)
{

}</content>
<type>text</type>
</events>
<events eventHandler="button3_Click">
<content> private void button3_Click(object sender, System.EventArgs e)
{
Button3.Enabled = true ;
textBox1.Text = "Button 3 Clicked" ;
}</content>
<type>text</type>
</events>
</script>
</form>

让我们看看XML文件的内容。Form根标记意味着元数据包含同屏幕或对话框有关的信息,在form元素下面是controlsscript元素。

Controls元素包含有关内嵌到窗体的控件的信息,它是包括零个或多个control元素的容器,control元素代表含有它自己属性的控件,如控件名称,控件放置的相对位置,标题文本以及tab顺序。Control元素也有一个屏幕元素事件及事件处理程序的集合。如果控件包括子控件,其信息保存在controls素中。Control元素有一个类型属性,它的值是控件的完全限定CLR(公共语言运行时)类型名称,运行时引擎在运行时建立类型时必需此信息。

Script元素是客户端业务逻辑的容器节点,持有事件处理器程序和它的代码定义,当窗体被再现时,代码将被集成到窗体的代码页里面。

在此例子中,我们将利用XML序列化来转换仓储XML到具体的CLR类型。CLR类型利用xsd.exe工具生成,以下给出仓储的XML架构。

<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="MetadataForm"
targetNamespace=http://tempuri.org/MetadataForm.xsd
elementFormDefault="qualified"

xmlns= http://tempuri.org/ MetadataForm.xsd
xmlns:mstns= http://tempuri.org/MetadataForm.xsd xmlns:xs="http://www.w3.org/2001/XMLSchema">

<!-- Definition for MetadataForm -->
<xs:complexType name="MetadataForm">
<xs:all>
<xs:element name="controls" type=
"MetadataControls" minOccurs="1"

maxOccurs="1" />
<xs:element name="script" type=
"MetadataScript" minOccurs="1"
maxOccurs="1" />
</xs:all>
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType> <!-- Definition for MetadataControls -->
<xs:complexType name="MetadataControls">
<xs:sequence>
<xs:element name="control" type=
"MetadataControl" minOccurs="0"
maxOccurs="unbounded"></xs:element>
</xs:sequence>
</xs:complexType>
<!-- Definition for MetadataControl -->
<xs:complexType name=
"MetadataControl">
<xs:all>
<xs:element name="position" type="Position" />
<xs:element name="controls" type="MetadataControls" />
<xs:element name="name" type="xs:string" />
<xs:element name="text" type="xs:string" />
<xs:element name="visible" type="xs:boolean" default="true" />
<xs:element name="tabIndex" type="xs:int" />
<xs:element name="event-handlers" type="AllEvents" />
</xs:all>
<xs:attribute name="type" type="xs:string" />
</xs:complexType>

<!-- Definition for Events -->
<xs:complexType name=
"AllEvents">
<xs:sequence>
<xs:element name="eventInfo" type=
"EventInfo" minOccurs ="0" maxOccurs =
"unbounded" ></xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="EventInfo">
<xs:attribute name="eventName" type="xs:string"></xs:attribute>
<xs:attribute name="eventHandler" type="xs:string"></xs:attribute>
</xs:complexType> <!-- Definition for Position -->
<xs:complexType name="Position">
<xs:sequence>
<xs:element name="height" type="xs:int"></xs:element>
<xs:element name="width" type="xs:int"></xs:element>
<xs:element name="top" type="xs:int"></xs:element>
<xs:element name="left" type="xs:int"></xs:element>
</xs:sequence>
</xs:complexType>
<!-- Definition for MetadataScript -->
<xs:complexType name=
"MetadataScript">
<xs:sequence>
<xs:element name="events" type=
"eventItem" minOccurs="0" maxOccurs=
"unbounded" />
</xs:sequence>
</xs:complexType>

<!-- Definition for contentType -->
<xs:simpleType name="contentType">
<xs:restriction base="xs:string">
<xs:enumeration value="text" />
<xs:enumeration value="binary" />
</xs:restriction>

</xs:simpleType>
<!-- Definition for eventItem -->
<xs:complexType name=
"eventItem">
<xs:all>
<xs:element name="content" type="xs:string" />
<xs:element name="type" type="contentType" />
</xs:all>
<xs:attribute name="eventHandler" type="xs:string" />
</xs:complexType>
<xs:element name="form" type="MetadataForm" />
</xs:schema>

当我们在以上.xsd文件上运行命令XSD.exe,将创建称为MetadataForm的CLR类型,此类型的源文件位置为:$SOLUTIONDIR/MetadataLibrary/MetadataForm.xsd,目标文件位置为:$SOLUTIONDIR/MetadataLibrary/MetadataForm.cs。

这是MetadataForm类看起来的样子:

[System.Xml.Serialization.XmlTypeAttribute
(Namespace="http://tempuri.org/MetadataForm.xsd")]
[System.Xml.Serialization.XmlRootAttribute
("form", Namespace="http://tempuri.org/MetadataForm.xsd",
IsNullable=false)]
public class MetadataForm
{
/// <remarks/>
[System.Xml.Serialization.XmlArrayItemAttribute
("control", IsNullable=false)]
public MetadataControl[] controls;
/// <remarks/>
[System.Xml.Serialization.XmlArrayItemAttribute
("events", IsNullable=false)]
public eventItem[] script;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string name;

public MetadataForm ()
{
controls = null;
script = null;
name = null;
}
public MetadataForm(ArrayList ctrlList,
ArrayList evtList, string formName)
{
name = formName;
controls = new MetadataControl[ctrlList.Count];
script = new eventItem[evtList.Count];

int cnt = 0;
if(ctrlList.Count > 0)
{
foreach(object ctrl in ctrlList)
{
controls[cnt] = (MetadataControl)ctrl;
cnt++;
}
}
if(evtList.Count > 0)
{
cnt = 0; foreach(object evt in evtList)
{
script[cnt] = (eventItem)evt;
cnt++;
}
}
}
}

MetadataForm类包含一个MetadataControl数组对象,以及一个eventItem数组对象。

以下是eventItem类的定义:

/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute
(Namespace="http://tempuri.org/MetadataForm.xsd")]
public class eventItem
{
/// <remarks/>
public string content;
/// <remarks/>
public contentType type;
/// <remarks/>
[System.Xml.Serialization.Xml
AttributeAttribute()]
public string eventHandler;
public eventItem() {
eventHandler = content = null;
type = contentType.text ;
}
public eventItem(string eventHandler, string content,
contentType cType )
{
this.eventHandler = eventHandler;
this.content = content;
this.type = cType;
}
}

每个eventItem对象包含存储代码的事件处理程序名称,代码本身,以文本或二进制的形式(由contentType枚举定义)保持在content元素中,当前代码仅支持以文本形式存储内容,然而,此方案可以扩展来支持以二进制形式存储代码。

/// <remarks/>
public enum contentType {

/// <remarks/>
text,

/// <remarks/>
binary,
}

MetadataControl类型定义为:

/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute
(Namespace=http://tempuri.org/MetadataForm.xsd ]
public class MetadataControl
{
/// <remarks/>
public Position position;
/// <remarks/>
[System.Xml.Serialization.Xml
ArrayItemAttribute("control", IsNullable=false)]
public MetadataControl[] controls;
/// <remarks/>
public string name;
/// <remarks/>
public string text;
/// <remarks/>
[System.ComponentModel.DefaultValueAttribute(true)]
public bool visible = true;
/// <remarks/>
public int tabIndex;
/// <remarks/>
[System.Xml.Serialization.XmlArrayItemAttribute
("eventInfo", IsNullable=false)]
public EventInfo[] event-handlers;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string type;

public MetadataControl()
{
position = new Position ();
tabIndex = 0;
type = name = text = null;
visible = true;
event-handlers = null;
controls = null; public MetadataControl(string name,
string text, bool visible, int tabIndex,
string type, int ht, int wd, int top,
int left ,ArrayList evts )
{
this.name = name ;
this.text = text ;
this.visible = visible;
this.tabIndex = tabIndex;
this.type = type;
position = new Position (ht,wd,top,left);
event-handlers = new EventInfo[evts.Count];
int cnt = 0;
if(evts.Count > 0)
{
foreach(object evt in evts)
{
event-handlers[cnt] = (EventInfo)evt;
cnt++;
}
}
}
}

最后,Position类型定义为:

/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute
(Namespace=http://tempuri.org/MetadataForm.xsd )]
public class Position
{
/// <remarks/>
public int height;
/// <remarks/>
public int width;
/// <remarks/> public int top;
/// <remarks/>
public int left;
public Position()
{
height = width = top = left = 0;
}
public Position( int ht, int wd, int top, int left)
{
height = ht;
width = wd;
this.top = top;
this.left = left;
}
}

MetadataControl类拥有设计窗体内的所有控件的控件信息。窗体的属性包容在根元素,并且其他的所有控件是它controls数组的一部分。每个控件的名字,文本,tab顺序,可见性,位置及类型被收集,同样,如果一个控件是一些其他控件的父控件,子控件的信息被保持在controls数组。每个控件,它们事件的触发及事件处理程序被保持在event-handlers数组,这是EventInfo数组对象。

EventInfo类型定义为:

/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute
(Namespace="http://tempuri.org/MetadataForm.xsd")]
public class EventInfo
{
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string eventName;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string eventHandler;
public EventInfo()
{
eventName = null;
eventHandler = null ;
}
public EventInfo(string eventName, string eventHandler)
{
this.eventName = eventName;
this.eventHandler = eventHandler; }
}

设计时实现

我们把设计时环境作为Visual Studio .Net的附加组件实现,关于如可开发附加件的更多信息,请参考文章“定制附加件帮您最大化Visual Studio .NET的生产力”。

为什么要设计时?

您需要设计时工具提供图形界面来操纵原始的XML仓储存储,此工具将在两个阶段内用到:

l 在屏幕的最初开发期间;
l 在定制化阶级期间;

此工具应提供图形设计器来操纵用户界面布局,就象在屏幕上添加控件,操纵控件的属性,重定屏幕大小等等。它应该能为控件添加事件及事件处理程序,从用户的角度来看,此工具抽象元数据结构,元数据存在哪里及如何存储。

在这里,我们不深究Visual Studio附加组件及客户端脚本,仅指望您清楚理解Visual Studio附加组件如何工作。示例源代码演示如何实现我们的Visual Studio附加组件。

设计器实现

使用Windows窗体设计器来设计窗体布局并为控件挂接事件,Visual Studio附件组件编程模型可让我们访问Visual Studio环境中当前开放的Window对象,通过检查Window对象是否是从称为IDesignerHost的接口继承的,来让我们查询window是否是设计窗口。

一旦安装附加组件,Tools菜单下将会有一个菜单项。



图1. 在Tools菜单下的附加组件

初始化后,设计时附加组件在Tools菜单下添加一组菜单项。现在,Tools菜单看起来如下:



图2. 附加组件初始化后展开的Tools菜单

表1. 附件菜单项摘要
菜单项
行为
Attach
从活动文档获取IDesignerHost接口指针。活动文档应为Windows窗体设计器。
在某私有变量中保存IDesignerHost指针,以备将来使用。
New
新建一个元数据文档。
Open
从仓储中打开一个已存在的元数据文档。显示“打开文件”对话框,允许用户选择适当的元数据文档。
Show Form
转为XML文件形式。
Save
保存元数据文档。
Detach
从Windows窗体设计器分离。
Attach菜单项
打开一个新的C#语言的Windows窗体项目,为活动窗口获取IDesignerHost句柄,并保存在一个内部变量中。这也使其他菜单项允许。

每个设计器表面提供特定服务以监视它发生的动作,在当前情况下,我们获得IComponentChangeService,用来获取有关设计器表面发生的变化信息。我们为设计器编写了适当的事件处理器。

internal void AttachDesignerToForm()
{
if (applicationObject.ActiveDocument != null)
{
foreach (Window W in applicationObject.ActiveDocument.Windows)
{
designerHost = W.Object as IDesignerHost;
}
}
AttachDesignerEvent-handlers();
}

private void AttachDesignerEvent-handlers()
{
//Component change events.
IComponentChangeService ccs =
(IComponentChangeService)designerHost.GetService
(typeof(IComponentChangeService));
if (ccs != null)
{
ccs.ComponentChanged += new
ComponentChangedEventHandler(this.OnComponentChanged);
}
}

Detach菜单项
designerHost对象置为空,并解开设计器上的所有事件处理程序,也要禁止除Attach外的所有附加组件菜单项。

public void DetachFromDesigner()
{
DetachDesignerEvent-handlers();
this.designerHost = null;
}

private void DetachDesignerEvent-handlers()
{
IComponentChangeService ccs =
(IComponentChangeService)designerHost.GetService
(typeof(IComponentChangeService));
if (ccs != null)
{
ccs.ComponentChanged -= new
ComponentChangedEventHandler(this.OnComponentChanged);
}
}

New菜单项
为当前项目添加一个空的窗体,并为窗体获取设计器对象,您可通过拖放开始窗体设计,添加事件处理器,设置控件属性,及实现事件处理器。

设计器仅需捕获的事件是ComponentChanged事件,这用来记录控件触发的事件及相应的事件处理器名称。

private void OnComponentChanged(object sender, ComponentChangedEventArgs cea)
{
if (cea.Member.GetType().Name.Equals("EventPropertyDescriptor"))
{
string ctrlName = ((Control)cea.Component).Name;
EventInfo ei = new EventInfo(cea.Member.Name,cea.NewValue.ToString());

ArrayList evtInfoList = new ArrayList();
if (!ctrlEvtList.ContainsKey(ctrlName))
{
evtInfoList.Add(ei);
}
else
{
evtInfoList = (ArrayList)ctrlEvtList[ctrlName];
if(!evtInfoList.Contains(ei))
{
evtInfoList.Add(ei);
}
ctrlEvtList.Remove(ctrlName);
}
ctrlEvtList.Add(ctrlName,evtInfoList);
}
}

Save菜单项
开始以下几步:
1. 提示您为XML文件提供要保存的位置及名称;
2. 递归处理当前设计器对象,收集其中的所有控件信息;
l 为每个在设计器内的对象创建一个MetadataControl对象,保存属性,就象nametabIndexlocationsizetypevisibility
l 如果控件是其他控件的父控件,子控件的信息收集到父控件的数组成员MetadataControl中。

private MetadataControl GetTheControl(Control formCtrl)
{
MetadataControl newCtrl = new MetadataControl();

newCtrl.name = formCtrl.Name;
newCtrl.text = formCtrl.Text;
newCtrl.position.height = formCtrl.Height;
newCtrl.position.width = formCtrl.Width;
newCtrl.position.top = formCtrl.Top;
newCtrl.position.left = formCtrl.Left;
newCtrl.tabIndex = formCtrl.TabIndex;
newCtrl.type = formCtrl.GetType().ToString();
newCtrl.visible = formCtrl.Visible;
newCtrl.controls = new MetadataControl[formCtrl.Controls.Count];
newCtrl.event-handlers = null;

if(newCtrl.controls.Length > 0)
{
int cnt = 0;
foreach (Control childCtrl in formCtrl.Controls)
{
newCtrl.controls[cnt] = GetTheControl(childCtrl);
cnt++;
}
}
return newCtrl;
}

3. 控件触发的事件及相应的处理程序名称存在event-handlers(类型为EventInfo)成员中,这是更新窗体的MetadataControls成员。

private void IntegrateCtrlWithEvents(MetadataControl[] arr)
{
foreach(MetadataControl ctrl in arr)
{
if(ctrlEvtList.ContainsKey(ctrl.name))
{
ArrayList ei = (ArrayList)ctrlEvtList[ctrl.name];
ctrl.event-handlers = new EventInfo[ei.Count];
ei.CopyTo(ctrl.event-handlers);
}
else
{
ctrl.event-handlers = new EventInfo[0];
}
if (ctrl.controls.Length > 0)
{
IntegrateCtrlWithEvents(ctrl.controls);
}
}
}

4. 解析窗体代码文件以得到事件处理器代码。这是添加MetadataForm的脚本元素。

private ArrayList GetEventCode()
{
ArrayList evtCode = null;
try
{
ProjectItem pi = applicationObject.ActiveDocument.ProjectItem;
MetadataCodeSpace codespace = new MetadataCodeSpace(pi);

string[] evtHdlrNames = GetOnlyEvtHandlers(ctrlEvtList.Values);
evtCode = codespace.ExtractEventCode(evtHdlrNames);
}
catch(Exception e)
{
MessageBox.Show(e.Message,"Error !!!");
return null;
}
return evtCode;
}

public ArrayList ExtractEventCode(string[] evtHandlers)
{
eventItem tempEvt;
// Parse code file for event code
try
{
CodeElement celt = projectItem.FileCodeModel.CodeElements.Item(1);

CodeNamespace cNamespace = (CodeNamespace)celt;

CodeClass cClass = (CodeClass)(cNamespace.Members.Item(1));

foreach (CodeElement ce in cClass.Members)
{
switch (ce.Kind)
{
case EnvDTE.vsCMElement.vsCMElementFunction:

CodeFunction cf = (CodeFunction)ce;
//Chk whether its an eventhandler
if (foundEvtHandler(evtHandlers,cf.Name))
{
TextPoint startPt = cf.StartPoint;
TextPoint endPt = cf.EndPoint;

EditPoint editPt = startPt.CreateEditPoint();
tempEvt = new eventItem(cf.Name,editPt.GetLines(startPt.Line,endPt.Line+1),contentType.text);

evtCodeList.Add(tempEvt);
}
tempEvt = null;
break;
}
}
}
catch(Exception e)
{
string s = e.Message;
}
return evtCodeList;
}

5. 一旦收集到所有窗体信息,新的MetadataForm对象被创建并序列化成XML,此XML按步骤1中的文件名保存。

// Add all the controls to the form object
activeForm = new MetadataForm(ctrls,evts,GetActiveFormName());

//activeFileName contains name of the file user has chosen
StreamWriter sw = new StreamWriter(activeFileName);

XmlSerializer writer = new XmlSerializer(typeof(MetadataForm));
//Serialize the activeForm member variable
writer.Serialize(sw, activeForm);
sw.Close();

运行时实现

Open菜单项
提示您提供在设计器中想打开的XML文件路径,当指定名字后,将发生由XML文件重现窗体的过程。

以下是此过程内的有关步骤:
l 反序列化XML文件到CLR类型MetadataForm
l 创建空的窗体并基于MetadataForm对象内的form控件设置它的属性;
l 创建控件并在窗体上适当定位;
l 挂接窗体上控件的事件处理程序
l 插入从XML文件内读取的事件处理程序代码到后台代码文件内。

反序列化
象我们以前看到的,第一步是反序列化仓储文件为MetadataForm类型并将创建的类型存在一个称为activeFom的成员变量中,这里是完成这个的代码片断。

//Create the serializer
XmlSerializer serializer = new XmlSerializer(typeof(MetadataForm));

FileStream stream = null;

try {
//Open the file
Stream
= new FileStream(filename, FileMode.Open,
FileAccess.Read);

//De-serialize the XML into CLR type
activeForm =
(MetadataForm)serializer.Deserialize(stream);

stream.Close();

}
catch(Exception e) {
MessageBox.Show(e.Message);
}

创建新窗体
为当前项目添加新的空窗体并用MetadataForm对象内的窗体属性初始化。

// Adding a new form to project
Project proj = applicationObject.Solution.Projects.Item(1);

ProjectItem pi = proj.ProjectItems.AddFromTemplate
(@"C:/Program Files/Microsoft Visual Studio .NET
2003/VC#/CSharpProjectItems/CSharpAddWinFormWiz.vsz",
formName+".cs");

applicationObject.Windows.Item(formName+".cs [Design]").Activate();

// Setting properties of the form
private void SetNewFormProperties(MetadataControl ctrl)
{
((Form)designerHost.RootComponent).Name=ctrl.name;
((Form)designerHost.RootComponent).Text=ctrl.text;
((Form)designerHost.RootComponent).Height=ctrl.position.height;
((Form)designerHost.RootComponent).Width=ctrl.position.width;
}

运行时创建控件
反射的概念用来在运行时创建控件,就象我们在仓储讨论中看到的,仓储结构包含关于窗体内嵌控件的信息。

<control type="System.Windows.Forms.Button">
<position>
<height>23</height>
<width>75</width>
<top>192</top>
<left>192</left>
</position>
<controls />
<name>button1</name>
<text>Submit</text>
<tabIndex>0</tabIndex>
</control>

如果看一下XML片断,control标记包含type特性,该特性含有控件的完全限定CLR类型,此刻,我们需要做的是利用反射创建控件,我们用Activator类来创建控件实例,完成这个的代码片断如下所示。此函数接受父控件(Winodws.Forms.Control)及子控件集合(MetadataControl)作为参数。

private void AddControlsToActiveForm(MetadataControl[] ctrl,Control parent)
{
try
{
foreach (MetadataControl child in ctrl)
{
System.Type controltype = Type.GetType(child.type);
//unknown control type. Handle this as an error
if (controltype == null) continue;

//Create the control

Control control = (Control)Activator.CreateInstance(controltype);
control.Name = child.name;
control.Text = child.text;
control.Top = child.position.top;
control.Left = child.position.left;
control.Height = child.position.height;
control.Width = child.position.width;
control.Visible = true;
control.TabIndex = child.tabIndex;

parent.Container.Add(control);

PropertyDescriptor parentProp =
TypeDescriptor.GetProperties(control).Find
("Parent",true);
parentProp.SetValue(control, parent );

if (child.controls.Length > 0)
AddControlsToActiveForm(child.controls,control);
}
}
catch (Exception e)
{
MessageBox.Show(e.Message );
}
}

1. 首先我们使用Type.GetType得到给定类型的CLR类型,它接受一个包含完全限定CLR类型名称的字符串作为参数;
2. 然后,我们调用Activator类的CreateInstance方法创建控件实例并转换控件类型为典型控件,即所有Windows窗体控件的基类;
3. 然后我们设置控件的适当属性;
4. 当创建控件后,我们把它添加到父控件的Controls集合,如果子控件还有子控件,我们传递适当参数递归调用该函数。

为控件附加事件处理程序
每一个在仓储内的MetadataControl维护一个触发的事件及相应事件处理程序的集合,此信息用来绑定运行时创建的控件与指定的事件处理程序。designerHostIEventBindingService服务可用于此。每个控件有一个可触发事件列表,利用控件的属性列表绑定事件与相应的事件处理程序。

IEventBindingService eventservice =
IEventBindingService)designerHost.GetService
(typeof(IEventBindingService));
if( eventservice != null )
{
if((child.event-handlers != null) || (child.event-handlers.Length > 0))
{
EventInfo[] eiArr = child.event-handlers;
foreach( EventInfo ei in eiArr)
{
EventDescriptor ed =
TypeDescriptor.GetEvents(control).Find(ei.eventName,true);
if( ed == null )
break;

PropertyDescriptor pd = eventservice.GetEventProperty(ed);
if( pd == null )
break;

pd.SetValue(control, ei.eventHandler);
}
}
}

插入事件处理程序代码
最后,事件处理程序代码被插入到窗体代码文件,窗体的所有事件处理程序存于MetadataForm对象的Script元素中,利用CodeDom名称空间中的类,我们解析整个文件代码并适当插入代码处理程序。

private void AddEventHandlerCode()
{
eventItem[] eiArr = activeForm.script;

ProjectItem pi = applicationObject.ActiveDocument.ProjectItem;
CodeElement celt = pi.FileCodeModel.CodeElements.Item(1);

CodeNamespace cNamespace = (CodeNamespace)celt;

CodeClass cClass = (CodeClass)(cNamespace.Members.Item(1));
foreach (CodeElement ce in cClass.Members)
{
switch (ce.Kind)
{
case EnvDTE.vsCMElement.vsCMElementFunction:
CodeFunction cf = (CodeFunction)ce;
foreach(eventItem ei in eiArr)
{
if (cf.Name.Equals(ei.eventHandler))
{
EditPoint ep =

cf.GetStartPoint(vsCMPart.vsCMPartBody)
.CreateEditPoint();
ep.Insert(justCodeBlock(ei.content));
}
}
break;
}
}
}

private string justCodeBlock(string fnCode)
{
string tmpcode = fnCode.Substring(fnCode.IndexOf("{")+1);
return tmpcode.Substring(0,tmpcode.Length-1);
}

Show Fom菜单项
XML文件窗体可通过附加组件从其菜单调用Show Fom来运,这是运行一个在设计中已经打开的已存在的XML文件,或者另外提示您提供XML文件路径,这会打开当前项目中的XML文件(与Open 菜单过程一样),通过生成方案,由附加组件代码来运行。

public void ShowForm()
{
try
{
if( applicationObject.Solution.Projects.Item(1).ProjectItems.Count == 0 )
{
OnOpen();
}
BuildNRunSolution();
}
catch(Exception e)
{
MessageBox.Show(e.Message);
}
}
private void BuildNRunSolution()
{
applicationObject.Solution.SolutionBuild.Build(true);
applicationObject.Solution.SolutionBuild.Run();
}

结论
在本文当中,我讨论了利用.NET Framework实现仓储或元数据驱动的用户接口方法,我们也解释了如何利用.NET Framework类建立可定制化窗体,以XML存储窗体,加载XML文件到设计器,修改XML文件以及加载并运行窗体。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: