现在的位置: 首页 > 综合 > 正文

编写组件自己的设计器

2013年07月07日 ⁄ 综合 ⁄ 共 6743字 ⁄ 字号 评论关闭

.NET框架是建造在大脑思维的扩展能力上的。由于.NET框架运行时期与设计时期的设计和实现是同一组工程师,用户就可以得到比其他的框架或者类库更加紧密地综合性能。

这种综合能力的一个关键因素就是基于代码的运行时期和设计时期的相互作用。由于运行时期的代码可以与设计时期的代码分开,设计部分就可以致力于在设计时期组件的行为和表现上施加相当多的注意力。

首先,我们来讨论一下词组“设计器(Designer)”的用法。通常意义下,他意味着在任何可以管理组件运行时期行为的.NET对象。不过有时更加广义的指在Microsoft® Visual Studio® .NET中设计Windows® Forms, Web Forms, 或者Components时的设计时期界面。这篇文章里“设计器”指某个特定组件的自己的设计器而不是广义的设计器,除非我们特别说明。

这篇文章会讲解设计器的各种特性,如何把他们和组件关联起来,如何使用这些特性来创造更强的设计时期用户界面。

 

什么是设计器?

就像上面提到的,设计器是负责管理设计界面上的组件的设计时期行为和表现的对象。特别的,设计器就是指实现了System.ComponentModel.Design.IDesigner接口的对象。

public interface IDesigner : IDisposable

{

        IComponent Component {get;}       

        DesignerVerbCollection Verbs {get;}

        void DoDefaultAction();

        void Initialize(IComponent component);

}

一般的,不必要从头开始写一个设计器。.NET框架SDK中所有的设计器都是继承自System.ComponentModel.Design.ComponentDesigner的默认实现。任何实现IComponent的对象(一般是继承自Component)都会自动的得到ComponentDesigner作为他的设计器。同样有一些实现System.ComponentModel.Designer.IRootDesigner的其他类型的设计器,我们把他们叫做“根设计器(Root Designer)”,他可以允许一个对象成为Visual Sudio .NET设计环境中的“根(root)”对象。像System.Windows.Forms.FormSystem.Windows.Forms.UserControls这些类型就有根设计器,这样在VS.NET中就会有设计视图和代码视图。一个类型可以有多个关联的设计器,不过每一个设计器都只能是一种类型。在这篇文章里,我们会探索标准IDesigner实现的特性。

大部分设计器执行3个基本工作:

l创建和修改组件的设计时期界面

l修改组件提供的公开的属性(Properties)、特性(Attributes)、事件(Events

l增加叫做动词(Verbs)的动作,这些动作可以在组件的设计时期执行

在设计时期,.NET设计器基础架构会把一个设计器关联到每一个驻扎(Sited)的组件上。就是说,每一个组件会得到一个名字和允许在设计服务下访问的连接。然后每一个设计器比如VS.NET设计器就可以有能力和用户进行交互,可以操作代码生成和持久保持(持久性)。

 

 


组件如何和设计器关联?

元数据(与类、属性、事件、方法联系在一起的信息)的能力和适应性在整个.NET框架中都得到使用,设计器同样会使用这些重要信息。设计器使用System.ComponentModel.DesignerAttribute来和组件关联。这个特性的构建器的参数可以使用指向assembly的字符串类型名字或者是一个实际的类型引用。字符串形式是很有用的,因为它可以保证组件的设计时期和运行时期完全分开。由于运行时期和设计时期的代码在分开的集合(assemblies)中,组件卖主可以最小化运行时期的内存分配。相反的,如果设计器包含在同一个assembly中或者总可以得到时,DesignerAttribute也可以使用实际的类型引用参数。使用字符串形式同样可以避免循环依赖编译,不过由于设计器类型编译时期就得到,所以我们这里的例子都使用简化的类型引用方式。

举个例子来说,如果一个叫做MyCompany.SysComponent组件在MyCompany.dll中,而叫做MyCompany.Design.SysComponentDesignMyCompany.Design.dll中:

当然,设计器同样可以与组件包括在同一个集合中(assembly),比如是嵌入的类,而且可以使用internalpublicprotected修饰符。

namespace MyCompany

{

[Designer("MyCompany.Design.SysComponentDesigner, MyCompany.Design")]

public class SysComponent : Component

{

}

}

namespace MyCompany.Design

{

   internal class SysComponentDesigner : ComponentDesigner

{

      // …

}

}

当然,设计器同样可以与组件包括在同一个集合中(assembly),比如是嵌入的类,而且可以使用internalpublicprotected修饰符。

namespace MyCompany

{

      [Designer(typeof(SysComponentDesigner))]

      public class SysComponent : Component

      {

             internal class SysComponentDesigner : ComponentDesigner

             {

             // …

             }

      }

}

使用实际类型引用的好处就是当引用错误的时候编译器就会告诉我们,而字符串名字却不会告诉我们错误。

System.ComponentModel.Design.IDesignerHost接口允许设计界面上的组件访问设计器。使用一个IServiceProvider(比如在设计时期通过一个IComponentSite属性得到的ISite),任何组件的设计器都可以通过IDesignerHost.GetDesigner方法访问。这里的代码来访问一个给定组件的动词(Verbs)。我们会在这篇文章的后面来讨论动词。

public DesignerVerbCollection GetComponentVerbs(IComponent comp)

{

        if (comp.Site != null){

            IDesignerHost host;

            host = comp.Site.GetService(typeof(IDesignerHost));

            if (host != null){

                IDesigner designer;

                designer = host.GetDesigner(comp);

                if (designer != null){

                    return designer.Verbs;

                }

            }

        }

        return new DesignerVerbCollection();

}

修改设计时期的状态信息

在很多情况下,让一个控件或者组件在设计时期的行为与运行时期的行为一样是没有什么实际需要的。比如,一个时间控件在设计时期就不会执行时间事件,一个系统监督控件不会勾出(hook)系统事件。设计器有一种简单的处理方式。

举一个例子,假设一个用户控件可以有拖放输入行为,就比如是RichEdit。很明显,在设计时期托一个文件或者一些文本到这个控件上会产生歧义。为了防止这种现象,一个设计器可以禁止掉控件的拖放支持。

public class DragDropControlDesigner : ControlDesigner

{

      public override void Initialize(IComponent c)

      {

         base.Initialize(c);

         ((Control)c).AllowDrop = false;

      }

}

这个时候,设计器设置控件的AllowDrop属性为false。注意在自己的代码前面调用基类的Initialize方法。这是很重要的,基类的Initialize方法在设计器可以访问之前会设置基类的一些状态。同时也要注意到Initialize方法的参数是一个IComponent,这是要被设计的组件对象的实例。可以通过基于ComponentDesigner的设计器的ComponentDesigner.Component属性来访问。如果你是基于ControlDesigner写一个设计器,他同样有一个叫Control的属性让你可以访问在设计的Control。很多情况,这个返回值和ComponentDesigner.Component的返回值是一样的,不过他保存了任何时候的类型转换。理论上,你自然是可以使用这点来给一个非控件写一个基于ControlDesigner的设计器,然后重载Control这个属性返回给UI一个组件。

ControlDesigner像上面的例子一样执行了好几个步骤。由于设计器操作了生动的组件实例界面设计,因此一个控件必须是可见而且为了可以在设计界面上操作必须是允许操作的。如果不是这样的话,这个空间要么是看不见的,要么是不能够正常地接受鼠标和键盘输入,因此也就不能够移动。所以,在ControlDesigner.Initialize方法里,VisibleEnabled必须设置为true

定义组件之间的关系

一些组件有他的关联组件,这些关联组件要么是一起显示要么是一起不显示在设计界面上。比如,ToolBar控件的按钮就是实际的组件自己。TabControlTabPages也是一样的。如果你要把一个ToolBar从一个form复制到另一个form,就不能够只复制ToolBar对象本身,而把其他的那些对象留下来不管。这种场合同样适合于GroupBoxMainMenu控件。那么,如何用一种通用的方式来解决这个问题呢?

ComponentDesigner上,有一个叫AssociatedComponents属性就是用来解决这个问题的。无论什么时候,一个拖放或者是复制/粘贴操作作用在组件上,VS.NET设计器就会循环调用AssociatedComponents的每一个组件的设计器来决定要拖放、复制、粘贴的全部对象。

在下面的这个例子里,MainComp把他所有的SubComp作为AssociatedComponent来返回。SubComp组件只是简单的返回驻扎的(siteVS.NET设计器。组件有时根据非设计器生成元素的集合项来初始化他们的状态。如果一个MainComp被复制到另一个form中或者是组件设计器中,他同时会复制所有的SubComp。想一想,这是多么让人激动啊。

[Designer(typeof(MainComp.MainCompDesigner))]

public class MainComp : Component

{

   public SubCompCollection SubComponents {

      get {  

         return subCollection;

      }

   }

 

   internal class MainCompDesigner : ComponentDesigner {

      public override ICollection AssociatedComponents{

         get{

            return ((MainComp)base.Component).SubComponents;

         }

      }

   }

}

 

[DesignTimeVisible(false)]

[Designer(typeof(SubComp.SubCompDesigner))]

public class SubComp : Component {

   public SubCompCollection SubSubComponents {

      get {  

         return subCollection;

      }

   }

   internal class SubCompDesigner : ComponentDesigner {

 

      public override ICollection AssociatedComponents{

         get{

            // Only return sited subs in this case. 

            // For example, this component could have

            // a number of sub components that were

            // added by the component and aren't sited

            // on the design surface.  We don't want

            // to move those around.

            //

            ArrayList comps = new ArrayList();

            foreach (SubComp sc in

               ((SubComp)Component).SubSubComponents) {

               if (sc.Site != null) {

                  comps.Add(sc);

               }

            }

            return comps;

         }

      }

   }

}

使用组件自己的设计器来更改组件的属性、特性、事件

一个一般的设计器应用就是如何来调整控件在设计界面上的表现。有很多场合我们需要在组件的设计时期修改或者添加他的属性。VS.NET设计器已经为设计器上的每一个组件添加了很多属性,比如(Name)属性或者是Locked属性。这些属性在组件的属性中并不真正的存在的。属性的特性同样也是可以修改的。大部分时候,设计器应该可以中途截获或者是把组件的某些属性影子化(shadowing)。通过影化(shadowing)一个组件,设计器可以跟踪用户设置的值并且来决定是否把这个改变传给实际的组件。

当我们使用Control.VisibleControl.Enabled情形时,可以让控件总是可以看见并且是可以使用的。又或者是在Timer.Enabled情形,可以不让Timer控件被唤醒而且执行时间事件。在设计时期这些属性都是可以使用的,而且不会影响到设计界面上控件的状态。这种影化(shadowing)可以在基于ComponentDesigner类的设计器上很好的实现。

首先,ComponentDesigner有三类方法来修改被编辑组件暴露的属性。

l      PreFilterProperties

l      PostFilterProperties

l      PreFilterAttributes

l      PostFilterAttributes

l      PreFilterEvents

l      PostFilterEvents

要遵守的一般原则就是在PreFilter等方法中添加或者移去某些项(items),并且在PostFilter方法中修改已经存在的项。在PreFilter方法中一定要首先调用基类方法,而在PostFilter方法中要在最后调用基类方法。这个保证了所有设计器有能力来应用他们的改变。ComponentDesigner同样有一个内嵌的保存被作影子属性(shadowed)值的字典(dictionary)。这个为设计器保存了当为属性创造成员时出现的问题。

我们就举

抱歉!评论已关闭.