一、 控件设计器
控件设计器类派生自System.Web.UI.Design.WebControls.CompositeControlDesigner。该类通过控件类中声明的一个属性绑定到控件上:
DesignerAttribute(GetType(EmailContactDesigner))
在Web表单设计器中,它能够把控件的所有外观和行为特征呈现在用户面前。当页面开发者把一个Web控件拖动到Web表单上时,页面开发者可以 通过各种交互方式取得控件的各种属性。这些属性将影响到页面开发者所看到的内容―不只是影响到控件本身,还包括一些更微妙的幕后元素(例如灵敏标签)。
实际上,控件设计器能够在设计时刻生成一种与运行时刻不同的输出结果。在有些情况下,一个控件在运行时刻可能没有任何可视化生成,但是却要求在 设计时刻实现一些显示,以便更容易地操作它。这种情况的一个示例就是,ASP.NET 2.0中所提供的声明性数据源。这些控件提供了数据存储和缓冲功能,但是却没有可视化生成。然而,在设计时刻,它们表现为一个带有一些描述性文本的灰色窗 口―出现在web表单设计界面上。另一个关于不同生成的例子是,有些控件不生成任何内容―除非它们被绑定到数据源。例如,当GridView(在1.1版 本中是DataGrid) 带有数据时,它看上去十分正常;但当不存在要显示的数据行时,看上去什么东西也没有(或只显示为空的头部)。当你把一个这样的控件拖动到Web表单上时, 控件往往使用几个具有示例性质的空数据行进行显示―这是由设计器类所提供的。
当Visual Studio需要决定在一个灵敏标签中显示的内容时,它存取设计器类的一个称为ActionLists的属性,并且使用它的结果来构建该灵敏标签。该 ActionLists属性返回一个System.ComponentModel.Design命名空间中的 DesignerActionListCollection类型的对象。在我的设计器类中,我将重载这个属性并且构建一个 DesignerActionListCollection对象。我将返回的这个对象将在类范围上加以声明,并且检查ActionLists属性是否是一 个“nothing”值。
以下是引用片段: Private o_ActionLists As _ DesignerActionListCollection |
控件设计器中的值都是被缓冲的,因此不需要被重复创建,以便使设计器更为有效地生成控件。
控件设计器类的一个重要特征是一个称为Component的成员。它是在控件设计器类所继承的基类中进行声明的,并且包含一个对实际的控件(设计器类被绑定到其上)的引用。我可以使用这个变量,并且把它转换成我的实际的控件类型―既然该变量被声明为object类型。
以下是引用片段: Dim ctlEmailContact As EmailContact = _ CType(Component,EmailContact) |
其结果是一个称为ctlEmailContact的对象,它包含一个这个类当前正在设计的实际控件的强类型实例。所以,我在这个对象上作的任何改变或执行的任何操作都将立即被反映到Web表单设计界面中,并呈现在页面开发者面前。
关于这个属性重载的其它方面的实现还包括,把我前面创建的ActionList类的一个实例添加到我在类级上创建的DesignerActionListCollection对象。
o_ActionLists.Add(New EmailContactActionList(ctlEmailContact))
你可能还记得我在EmailContactActionList类中创建的一个构造器,当时它接收EmailContact控件的一个实例。如你所见,我在此也使用了该构造器―把我设计的控件实例传递给它。
下面是控件设计器类用于构建灵敏标签的完整源码:
以下是引用片段: Private o_ActionLists As DesignerActionListCollection Public Overrides ReadOnly Property ActionLists() As _ System.ComponentModel.Design.DesignerActionListCollection Get If o_ActionLists Is Nothing Then o_ActionLists = New DesignerActionListCollection Dim ctlEmailContact As EmailContact2 = _ CType(Component, EmailContact2) o_ActionLists.Add( _ New EmailContactActionList(ctlEmailContact)) End If Return o_ActionLists End Get End Property |
在这个示例中,我仅创建了一个ActionList类,使用行为列表项填充它,并且把该类添加到将被返回的 DesignerActionListCollection类―这是通过重载控件设计器的ActionLists属性来实现的。其实,我完全可以据实际需 要创建许多ActionList类,并且简单地把它们添加到ActionLists属性集合即可。如果我想在逻辑上组织大量的灵敏标签项―为了在多个控件 中重用它们时,这是很有用的。至于决定为何以及何时这样做,则要依赖于实际来决定。
现在,在我编译完这个控件并把它拖动到一个表单上时,我将看到一个小箭头出现在其右上角―点击它将显示你在图2中所看到的内容。在此,改变任何 其中一个属性都与在属性浏览器中改变完全一致。点击相应的链接将执行在EmailContactActionList类的方法中定义的行为。
关于该控件,我们就讨论这些内容。记住一点:不要把暂时不需要的属性添加到一个灵敏标签中。
二、 模板设计时刻编辑
在以前的文章中,我曾经介绍过如何在你的控件中添加模板能力。在此,我仅简单地涉及其中一点,因为它与这里的讨论有一些关系―我指的是从Web表单设计界面上编辑模板内容的能力―在以前的ASP.NET 1.x时代这是不容易实现的。
模板设计时刻编辑功能出现在例如GridView这样的控件中―你可以把该控件置于“模板编辑”模式,然后只需把其它控件拖动到该模板区域即可。如果没有这种方便的话,页面开发者必须切换到ASPX视图并象下面这样手工地创建模板内容:
以下是引用片段: <dnd:EmailContact ID="ctl1" runat="server"> <HeaderTemplate> <asp:Label ID="lbl1" runat="server" Text="Label" /> <asp:Textbox ID="txt1" runat="server"/> </HeaderTemplate> <FooterTemplate> <asp:LinkButton ID="lnk1" runat="server" Text="LinkButton" /> </asp:LinkButton> <asp:DropdownList ID ="ddl1" runat="server" /> </FooterTemplate> </dnd:EmailContact> |
尽管这也并不是太糟糕,但是使页面开发者使用设计器界面进行设计效果会更好一些。
为了把模板编辑能力添加到一个控件上,你必须重载控件设计器类的Initialize方法,并且设置一个标志以通知设计器你想支持模板编辑功能。
以下是引用片段: Public Overrides Sub Initialize( _ ByVal Component As IComponent) MyBase.Initialize(Component) SetViewFlags(ViewFlags.TemplateEditing, True) End Sub |
注意在此,我对基类方法进行了调用以确保我不取消自己不想实现的内容。这个调用通知设计器它将支持模板编辑功能,但是你仍然需要对编辑实现部分进行编程。
微软所使用的构建设计器的方式是,把模板分类到模板组中;这种情况下,在你的控件中将会存在许多模板。模板本身是在一个称为 TemplateDefinition的对象中定义的;而TemplateGroup对象包含一个或多个这些定义对象。TemplateGroup对象包 含在一个TemplateGroupCollection类型的对象中;因此,正是在这里(TemplateGroupCollection类型的对象 中),设计器类将在类级上声明一个这种类型的变量。就象在灵敏标签情况下一样,基础结构负责缓存这个对象―这正是我在一个类级范围上声明它的原因。
Private o_TemplateGroups As
TemplateGroupCollection = Nothing
这个集合取得一个称为TemplateGroups的内置属性―我现在必须重载之。正是这个属性的内容将被暴露给页面开发者以实现相应的模板编辑功能。
以下是引用片段: Public Overrides ReadOnly Property TemplateGroups() As TemplateGroupCollection Get If o_TemplateGroups Is Nothing Then ... End If Return o_TemplateGroups End Get End Property |
在这个属性重载中,我将构建Visual Studio使用的TemplateGroupCollection。属性中的“Is Nothing”检查有助于阻止对这个对象不必要的重新构建。
首先我将实例化o_TemplateGroups对象。
o_TemplateGroups = New TemplateGroupCollection()
现在,我需要使用我在“灵敏标签”一节中所讨论的组件变量来取得我设计的控件。
Dim ctl As EmailContact = CType(Component, EmailContact)
稍后,我将使用这个变量。但是首先,我必须建立我要使用的相应于TemplateGroup和TemplateDefinition对象的两个对象变量。
以下是引用片段: Dim o_TemplateGroup As TemplateGroup Dim o_TemplateDefinition As TemplateDefinition |
现在我可以开始定义组和模板了。
以下是引用片段: o_TemplateGroup = New TemplateGroup("Surrounding Templates") 文本“Surrounding Templates”将出现在分类标题中,适用于我放在这个组中的所有模板定义。 o_TemplateDefinition = New TemplateDefinition(Me, "Header Template", ctl,"HeaderTemplate",False) o_TemplateGroup.AddTemplateDefinition _ (o_TemplateDefinition) |
让我来详细分析一下该TemplateDefinition构造器中的参数。第一个参数是一个添加了模板编辑功能的设计器的实例―通常是Me; 第二个参数是模板名―它将显示于一个快捷方式菜单或灵敏标签中。第三个参数是正在设计的控件―通过转换Component对象来得到它。第四个参数是控件 中模板属性的名字。参数列表最后的Boolean参数被设置为False以便指定这个模板既接收服务器控件也接收HTML控件。如果把它设置为True则 仅允许把服务器控件(常规Web控件)添加到模板上。
你可能已经猜出,对于你想定义的每个模板和每个模板组,都要重复这一操作。最终结果是,我的o_TemplateGroups对象中填充了我的模板中定义的所有信息―而这正是我在这个属性中所返回的内容。
图3.在模板编辑方式的EmailContact控件
现在,我重新编译并转到我的测试页面。当我右击EmailContact控件时,我将看到“EditTemplates”被添加到快捷方式菜单 中。子菜单下的列表将显示我在设计器类中所定义的模板组,而当我选择“Surrounding Templates”组时,我会看到类似图3所示的内容。现在,你可以看到我手工地添加到这两个模板中的控件;而事实上,我现在就可以把其它控件从工具箱 直接拖动到其上。再次右击鼠标并选择“Edit Templates”将返回到标准控件视图并且在控件中显示该模板内容。
另外还注意,一个“Edit Templates”链接也被自动地添加到该控件的灵敏标签上。如果以前不存在一个灵敏标签,那么,当我把模板编辑代码添加到设计器类上时,它会自动地为我创建一个。
下面是完整的模板编辑代码:
以下是引用片段:
Public Overrides Sub Initialize(ByVal Component As IComponent)
MyBase.Initialize(Component)
' Turn on template editing
SetViewFlags(ViewFlags.TemplateEditing, True)
End Sub
Private o_TemplateGroups As TemplateGroupCollection = Nothing
Public Overrides ReadOnly Property TemplateGroups() _
As TemplateGroupCollection
Get
If o_TemplateGroups Is Nothing Then
o_TemplateGroups = New TemplateGroupCollection()
Dim o_TemplateGroup As TemplateGroup
Dim o_TemplateDefinition As TemplateDefinition
Dim ctl As EmailContact2 = CType(Component, _
EmailContact2)
o_TemplateGroup = New TemplateGroup( _
"Surrounding Templates")
o_TemplateDefinition = New TemplateDefinition( _
Me, "Header Template", ctl, "HeaderTemplate", True)
o_TemplateGroup.AddTemplateDefinition( _
o_TemplateDefinition)
o_TemplateDefinition = New TemplateDefinition( _
Me, "Footer Template", ctl, "FooterTemplate", True)
o_TemplateGroup.AddTemplateDefinition( _
o_TemplateDefinition)
o_TemplateGroups.Add(o_TemplateGroup)
End If
Return o_TemplateGroups
End Get
End Property
本文出自 “青峰” 博客,转载请与作者联系!