美丽心灵

A Beautiful Mind
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
元数据驱动的用户界面

元数据驱动的用户界面

查看原文

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,将创建称为MetadataFormCLR类型,此类型的源文件位置为:$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文件以及加载并运行窗体。