科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件探讨ASP.NET 2.0中的Web控件改进技术

探讨ASP.NET 2.0中的Web控件改进技术

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

 ASP.NET 2.0并没有抛弃1.1版本中的任何现有控件,而是增加了一组新的控件;同时还引入了若干新的控件开发技术。本系列文章将对这些内容展开全面探讨。

作者:朱先忠 编译 来源:IT专家网 2008年4月18日

关键字: 改进 web asp net Windows

  • 评论
  • 分享微博
  • 分享邮件
全面探讨ASP.NET 2.0中的Web控件改进技术之概述(一)

  ASP.NET 2.0并没有抛弃1.1版本中的任何现有控件,而是增加了一组新的控件;同时还引入了若干新的控件开发技术。本系列文章将对这些内容展开全面探讨。

  一、 引言

  到目前为止,你可能已经了解了大量的ASP.NET 2.0新特征—母版页面,主题,提供者,等等……所有这样内容都相当精彩;但是,你是否了解到有关定制Web控件开发方面的重大变化?这正是我在本文中所想讨论的。如果你已经从事于控件开发,那么,我想本文所描述的ASP.NET 2.0中的新的改进特征会立即应用于你的控件开发中。

  首先应该注意的是,你以前使用ASP.NET 1.1(或1.0)开发的所有Web控件在2.0版本下将继续良好运行—微软并没有破坏你的现有代码。在本文中,我将向你介绍的所有相关内容,包括许多新的令人激动的技术,所有这些你都可以添加到现有控件或在新的控件环境中使用。

  作者注:本文假定你对定制Web控件开发已经有一个基本了解。在本文中,我以一个增强版本的EmailContact控件为例对ASP.NET 2.0中的Web控件改进技术作全面探讨。

  二、 改进

  表格1描述了ASP.NET 2.0在定制Web控件开发方面所作的大部分的重大改进。在本系列文章中,我将对这些特征展开逐一讨论。

  表格1:ASP.NET 2.0 Web控件改进功能。

 三、 增强EmailContact Web控件

  本文中的定制EmailContact Web控件(参考图1)允许在你的站点中加入一个“contact us”表单,它具有完整的电子邮件功能。在本文中,我将使用该功能增强这一控件。

  图1.缺省状态下的EmailContact控件

  四、 一个新的基类

  以前,开发者都是从WebControl类派生他们的可视化Web控件。我之所以在此使用了“可视化”一词是因为,典型情况上,没有在浏览器中生成任何内容的控件都是派生自Control类。这一点并没有改变—你应该继续使用该Control类来派生任何这样的非可视化控件—它们执行不可见功能或在浏览器中生成除可视化HTML内容之外的任何其它内容。而且,在开发可视化Web控件时,你还应该继续使用WebControl类。然而,我们所开发的大多数复合控件都是为了利用现有控件的功能。在这种情况下,你应该总是从WebControl类进行派生,但是你还要记住另外一些有关细节—否则的话,有可能导致许多问题。

  复合控件必须实现INamingContainer接口,并且需要包括在你的控件类中。这个接口能够确保在你的控件及其可以生成的整个控件层次中的所有的HTML标签中都具有唯一的标签命名。当你在单个页面上存在多个相同类型的复合控件的情况下,这是相当关键的。在这样的情况下,你需要确保任何生成的子元素都具有唯一的名称。忘记实现该接口能够导致各种问题的出现。

  在ASP.NET 2.0以前,复合控件开发者还需要记住在一个控件的Render方法中调用EnsureChildControls。在我以前的文章中曾经向你介绍如何重载该Render方法并且在调用基类的Render方法前调用这个方法。要使控件在Visual Studio设计时刻正确生成这一步是必要的;否则,有可能带来许多不便。

  上面两个步骤在复合控件开发中如此普遍,以致于许多开发者往往都会构建一个包括这两个细节的基类,然后从该基类下派生他们所有的新的复合控件。作为代替,ASP.NET 2.0提供了(更准确地说是“名字为”)CompositeControl。借助于这个类来构建你的复合控件,你就不必再记住实现INamingContainer或从Render方法中执行一个EnsureChildControls调用了。

  另外,还存在其它一些新的基类,例如用于数据绑定的控件等,在此不再赘述。 全面探讨ASP.NET 2.0中的Web控件改进技术之ControlState篇(二)

  我的观点是:ViewState有可能成为你最好的朋友,也有可能成为你最坏的敌人—这要依赖于你使用它的方式来决定。如果你在以前曾经使用过ViewState,那么,你肯定会喜欢新的ControlState。

  关于ViewState的最令人头痛的问题之一就是,它的“all-or-nothing”状态管理方法。页面开发者可以很容易地决定在任何控件级,页面级或在整个站点级(经由web.config)上关闭ViewState。事实上,如果你在整个站点级上通过web.config关闭ViewState的话,那么,你不妨猜测一下你还能够在其它什么地方关闭它?答案是:还可以在machine.config中实现—在此情况下,它能够影响到同一服务器上的所有站点。如果一个页面开发者决定关掉在ViewState中实现状态管理的能力,那么,你的控件生成有可能出现部分不可用,或更有甚者—完全不可用。

  为此,在新版本中,微软创建了ControlState—旨在解决这一问题。页面开发者不能关掉ControlState,因此使用它进行属性选择更为安全。

  使用ControlState与使用ViewState几乎完全一致。然而,ControlState并没有提供象ViewState这样的一个变量,而是提供了称为SaveControlState和LoadControlState的方法以便于你的控件能够进行重载。这些方法与SaveViewState和LoadViewState方法的工作原理完全一致。

  因为ControlState在属性语句中没有提供一个相应的变量,所以,你必须借助于ASP.NET开发者以前在他们的对象中所使用的成员变量(属性语句)来实现相同的功能。

  Protected _MailServer As String = "First name:"
  Public Property MailServer() As String
  Get
  Return _ MailServer
  End Get
  Set(ByVal value As String)
  _MailServer = value
  End Set
  End Property

  然而,因为我使用了一个标准的成员变量来保存值,所以我需要一种方法以便把数据存储在ControlState中—这正是前面提到的方法“登场”的原因。就象在它们相应的ViewState方法中那样,ASP.NET将在页面生命周期内调用这两个方法。其中,SaveViewState方法返回一个将被持久存储的对象类型。通过返回一个对象数组,这个方法可以存储多个值。并且,就象发生在SaveViewState方法中一样,也是使用数组的0下标元素来调用基类的SaveControlState方法。

  Protected Overrides Function SaveControlState() As Object
  Dim state() As Object = New Object(2) {}
  state(0) = MyBase.SaveControlState()
  state(1) = _MailServer
  Return state
  End Function

  注意:LoadControlState方法以一个对象作为参数—这个对象是以前在SaveControlState中返回的一个对象。在这个方法中,我重新分配了成员变量—通过把该参数转换为一个对象数组,然后获得每个下标的值。与以前一样,我使用数组的0下标来调用基类的LoadControlState方法。

  Protected Overrides Sub LoadControlState( _
  ByVal savedState As Object)
  If savedState IsNot Nothing Then
  Dim state() As Object = CType(savedState, Object())
  MyBase.LoadControlState(state(0))
  _MailServer = CType(state(1), String)
  End If
  End Sub

  借助于这些方法来存储数据,在页面开发者关掉ViewState时,控件就不会出现前面那些麻烦。你可能对ControlState的存储位置感到惊讶;它对应于另一个生成到HTML页面中的隐藏的文本框。就象在ViewState情况下数据被存储在__ViewState隐藏文本框中类似,ASP.NET 2.0使用__ControlState隐藏文本框来存储ControlState数据。

  遗憾的是,微软没有向开发者提供内在地使用ControlState的能力—就象在ViewState情况下那样。所以,为了ControlState使用,你需要注册你的控件。你可以重载控件的OnInit事件并且调用Page对象的RegisterRequiresControlState方法。

  Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
  MyBase.OnInit(e)
  If Page IsNot Nothing Then
  Page.RegisterRequiresControlState(Me)
  End If
  End Sub

  现在,你可以使用ControlState来存储你认为足够重要的数据—如果不把它存储起来,那么你的控件可能生成一些无用的内容。

  记住,你在设计时刻对属性的修改将被硬编码到该控件的ASPX声明中,从而在相邻的再次回寄之间自动地存储。然而,如果表单上的一个行为改变了一个控件的属性,那么,这将会激活状态管理机制的使用。如果不把该属性存储在一个状态中,那么,在下一次回寄时它将恢复到“硬编码”状态。

  现在,总的来看,我们应该把与外观相关的属性存储在ViewState中,而把与行为相关的属性存储在ControlState中。通过这种方式,如果一个页面开发者关掉ViewState,那么你的控件尽管可能看起来样子别扭,但是仍能正确工作。

  全面探讨ASP.NET 2.0中的Web控件改进技术之灵敏标签篇(三)

  当你最开始在Visual Studio 2005中使用Windows表单控件或是ASP.NET Web控件时,你首先会注意到,在许多控件右上角出现一个箭头形状的小玩意儿(见图2中的示例)。点击这个箭头会弹出一个小窗口,其中包含该控件的一些属性,还有一两个链接。微软设计这些灵敏标签是为了显示你需要操作的一些属性,其最终目的是为了使该控件在一个页面或表单上能够正确工作;并且你将注意到,它们比一个普通的快捷菜单更为精致。本节中我们讨论的内容既适用于Windows表单控件也适用于ASP.NET服务器控件。

为了构建你自己的灵敏标签,你需要使用一个控件设计器类。事实上,你在另外其它一些问题上也会使用这个类。但是,在我详细讨论设计器类前,我想先创建一个ActionList类—这个类将定义我的灵敏标签中包含的元素。

  一个ActionList类继承自System.ComponentModel.Design命名空间中的DesignerActionList类。但是,在详细讨论这个类之前,让我先来解释一下存在于灵敏标签中的四种类型的元素:category header,property mapping,action link以及information item。图2展示了我构建这个灵敏标签的目标。你能够从中看出我所指的这四种类型的元素吗?我把这个灵敏标签根据标题分为三类:“Appearance & Behavior”,“Support”和“Information”。其中,“Appearance & Behavior”分类中包含了两个属性:Mail Server和Pre-defined Display。这些实际上都是EmailContact控件本身的属性。“Support”分类包含两个激活某些类型的一个行为的链接,而“Information”分类仅用于显示信息。现在,有了这四种类型的元素,我将着手创建我的ActionList类。

  我将创建一个称为EmailContactActionList的类,并且从DesignerActionList中加以派生。(你可以在本文源码列表1中看到完整的类)。我将创建一个构造器—它接收一个EmailContact实例作参数并且把它的范围扩大到一个称为ctlEmailContact的类级变量。后面,当我把代码添加到设计器类时,你将看到这个构造器的使用情况。现在,我已经建立了一个类级的对象,它包含我正在设计的Web控件的实例。

  接下来,我将创建灵敏标签将显示的属性的“property mapping”。在图2中,你看到我已经在该灵敏标签中标出了两个属性:MailServer和PreDefinedDisplay。这些将分别映射到EmailContact控件的称为MailServer和PreDefinedDisplay的属性上。ActionList类中的属性映射将在get存取器中返回控件的属性,而在set存取器中设置控件的属性。然而,由于微软设计ActionList基础结构的方式决定了,你不能直接设置该控件的属性。而是,你必须使用反射机制来存取该控件的属性,然后再设置它的值。为了方便这一实现,我编写了一个称为GetControlProperty的方法,它能够返回一个PropertyDescriptor对象。这样以来,开发者就不需要再重复每一种属性映射下的反射编码。下面是一个属性映射看起来的样子。

  Public Property MailServer() As String
  Get
  Return ctlEmailContact.MailServer
  End Get
  Set(ByVal value As String)
  GetControlProperty("MailServer"). _
  SetValue(ctlEmailContact, value)
  End Set
  End Property

  接下来,我需要建立的是你在图2中所看到的链接:“About EmailContact”和一个到我自己的网站的链接。这些链接将执行我将在这个类中创建的方法。我的第一个方法名为ShowAboutBox,它显示一个Windows表单以用作我的控件的一个“关于”信息提示窗口。第二个方法称为LaunchWebSite,它执行一个对System.Diagnostics.Process.Start的调用以便在一个浏览器实例中启动我的网站。这两个方法的唯一的要求是:每一个签名都必须是一个“Sub”(在C#语言中相应于一个void函数)并且不带参数。

  注意,在这个灵敏标签示例中仅显示了两个属性和两个链接,但是借助于我刚才所展示的技术,你完全可以提供你所需要的尽可以多的这些对象。然而,我建议:不要使用太多的信息来重载一个灵敏标签。记住,你仅想把信息放于此以便页面开发者立即使用,从而使得Web控件开发更具直观性。

  现在,既然我已经创建了我的属性映射和行为方法,那么接下来,我将创建灵敏标签的内容。其中,DesignerActionList类提供一个称为GetSortedActionItems的重载函数。以后,一个设计器类将重载这个函数,并且它会返回一个DesignerActionItemCollection(定义于System.ComponentModel.Design命名空间)类型的对象。

  这个属性重载的实现部分将创建一个新的DesignerActionItemCollection对象并且使用四个不同的类(DesignerActionHeaderItem,DesignerActionPropertyItem,DesignerActionMethodItem和DesignerActionTextItem)的实例来填充它。注意,这四个类都派生自抽象DesignerActionItem类。下面,我将同你逐个展开讨论。

  Dim o_Items As DesignerActionItemCollection = _
  New DesignerActionItemCollection

 这段代码使用DesignerActionHeaderItem类来创建灵敏标签分类头部,并且在它们的构造器中接收分类名字。我将直接把这个类的实例插入到我刚才创建的集合中。

  o_Items.Add(New DesignerActionHeaderItem( _
  "Appearance & Behavior"))

  为每一种分类创建准确的标题是相当重要的,这不仅是因为它作为在灵敏标签中作为该分类的头部相应的显示文本这一用途。早些时候,我创建了两个分别称为MailServer和PreDefinedDisplay的属性映射;现在我想把它们添加到灵敏标签中。为此,我将创建DesignerActionPropertyItem类的实例并且把它们添加到集合中。

  o_Items.Add(New DesignerActionPropertyItem( _
  "MailServer", "Mail Server", _
  "Appearance & Behavior"))

  注意,该构造器接收三个参数:属性名,将出现在灵敏标签上的文本信息,以及相应类型的准确标题(在DesignerActionHeaderItem的实例中定义)。

  接下来,我想以相同的方式把行为链接添加到灵敏标签。注意,也仅仅是在此时,我们使用了DesignerActionMethodItem类的实例。

  o_Items.Add(New DesignerActionMethodItem( _
  Me, "ShowAboutBox", "About EmailContact2", _
  "Support", "Displays the about box.", True))

  在此,构造器接收方法名,链接说明,类型文本以及一个用作链接的提示信息的描述等共四个参数。其中,第四个参数决定这个链接是否还出现在属性浏览器的底部。

  最后,我将把信息项添加到灵敏标签中—这是使用DesignerActionTextItem类来实现的。

  o_Items.Add(New DesignerActionTextItem( _
  "ID: " & ctlEmailContact.ID, "Information"))

  在此,构造器仅接收要显示的文本和该文本要放置的类型。

  列表1(见源码文件)中的最终代码展示了我要添加到这个集合中的所有项。当该方法完成时,它简单地返回这个集合。在后面一篇中,我们将讨论控件设计器类的问题。

  全面探讨ASP.NET 2.0中的Web控件改进技术之控件设计与模板设计篇(四)

  一、 控件设计器

  控件设计器类派生自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

 全面探讨ASP.NET 2.0中的Web控件改进技术之自动格式化与Web资源篇(五)

  一、 自动格式化

  现在,让我们开始讨论本文中最有趣的内容。你是否曾把一个GridView拖动到一个表单上并且注意到在属性浏览器有一个标有“Auto Format…”的链接?当你点击它时,你会得到一个预定义格式的选择:例如Corporate,Elegant,Classic,等。事实上,随着ASP.NET 2.0发行的所有安全控件套件(Security Control Suite)都提供了这个特征。读完本节后,你一定会为在你的控件中提供这种功能的容易程度而感到吃惊。

  首先,我要确定我想在控件中提供什么格式。我将通过创建三种格式来简化操作。其中的两个分别称为“Monochrome”和“Colorful”。第三个(实际上是第一个)称为“No Format”;当把它拖动到一个表单时,它能够把控件恢复到其原始状态。

  我创建的每一种格式都需要它自己的继承自System.Web.UI.Design命名空间的DesignerAutoFormat类。因此,我先从一个称为ColorfulFormat的继承自DesignerAutoFormat的类开始。首先,我需要为这个类提供一个缺省的构造器;并且我将在其中调用基类的构造器,发送给它一个参数—格式名称(将出现在列表中)。

  Public Sub New()
  MyBase.New("Colorful")
  End Sub

  接下来,我必须重载Apply方法。这个方法接收一个Control类型的参数。我的控件设计器类将调用这个方法,并且把我的控件实例发送到这个参数中。然后,我要把该参数转化成我的控件类型;这样以来,我就有一个可以使用的强类型引用了。

  Public Overrides Sub Apply( _
  ByVal control As System.Web.UI.Control)
  Dim ourControl As EmailContact2 = CType(control, EmailContact2)
  If ourControl IsNot Nothing Then
  ...
  End If
  End Sub

  现在,我要设置我需要的任何属性—这通常包括一些风格属性,但是它事实上能够包括我需要的一切。因此,在“IsNot Nothing”检查中,针对这个特定的格式,我只需简单地设置我想实现的任何风格属性。列表2展示了完整的列表。

  ourControl.BackColor = Drawing.Color.Aquamarine
  ourControl.BorderStyle = BorderStyle.Double
  ourControl.BorderWidth = Unit.Pixel(2)
  ourControl.BorderColor = Drawing.Color.DarkRed
  ourControl.HeadingStyle.Font.Name = "arial"
  ourControl.HeadingStyle.Font.Bold = True
  ourControl.HeadingStyle.Font.Size = FontUnit.Large

 接下来,我将创建一个称为MonochromeFormat的类并且执行相同的步骤—仅在这次,我把风格属性设置为black和white以相应于一种“monochrome”格式。源码文件中的列表3展示了这个类的完整代码。

  最后,我必须创建NoFormat类。这个类遵循与前两个相同的设计;但是,我不是把风格属性值设置成各种颜色及字体,而是在所有的风格属性上调用Reset方法。这可以把所有的风格值设置为它们的缺省状态。

  顺便说一下,既然我的EmailContact控件派生自CompositeControl类,而它又派生自WebControl类;所以,我还可以在我的控件的类级上取得风格属性。这意味着:我可以拥有BackColor,ForeColor等属性,而且可以直接从我的控件中存取它们而不必通过一个属性来实现。然而,为了复位这些风格值,我不是直接从我的控件类取得一个Reset方法,而是由WebControl类为我提供了一个ControlStyle属性,它用作相应于所有的容器风格值的入口点。注意:在复位控件的外观时,我还把Height属性设置为一个具有400个像素的缺省值。下面是完整的类:

  Public Class NoFormat
  Inherits DesignerAutoFormat
  Public Sub New()
  MyBase.New("No Format")
  End Sub
  Public Overrides Sub Apply(ByVal control _
  As System.Web.UI.Control)
  Dim ourControl As EmailContact2 = CType(control, _
  EmailContact2)
  If ourControl IsNot Nothing Then
  ourControl.ControlStyle.Reset()
  ourControl.HeadingStyle.Reset()
  ourControl.CaptionStyle.Reset()
  ourControl.FieldStyle.Reset()
  ourControl.ReadonlyFieldStyle.Reset()
  ourControl.ButtonStyle.Reset()
  ourControl.Height = Unit.Pixel(400)
  End If
  End Sub
  End Class

  现在,既然我已经创建了我想实现的所有格式类,那么,接下来,我需要对它们做一些实际的操作。为此,我将再转回到控件设计器类—实现一些重载。我需要重载的属性称为AutoFormats,它返回一个System.Web.UI.Design命名空间中的DesignerAutoFormatCollection类型的对象。

  就象在灵敏标签和模板编辑部分一样,我将实例化的对象(为了通过属性返回之)是在类级上声明的,因为ASP.NET基础结构会负责对它进行缓冲处理。

  Private o_AutoFormats As _
  DesignerAutoFormatCollection

  然后,在属性重载实现中,我可以测试对象变量是否是一个null值。现在,我们只需简单地使用我以前创建的每一个格式类的实例来填充o_AutoFormats集合对象。

  Public Overrides ReadOnly Property AutoFormats() _
  As DesignerAutoFormatCollection
  Get
  If o_AutoFormats Is Nothing Then
  o_AutoFormats = New DesignerAutoFormatCollection
  With o_AutoFormats
  .Add(New NoFormat)
  .Add(New MonochromeFormat)
  .Add(New ColorfulFormat)
  End With
  End If
  Return o_AutoFormats
  End Get
  End Property

如果我重新编译并再次测试该控件,我将看到一个称为“Auto Format…”的链接被添加到灵敏标签的顶部。如果以前不存在灵敏标签,那么Visual Studio将自动地使用“Auto Format…”链接在其中创建一个。你将还注意到一个“Auto Format…”链接出现在属性浏览器的底部。

  如果我点击任何一个链接,我将看到如图4所示的内容。选择列表中的任何一种格式从而在预览窗口中改变它;然后点击OK把此设置提交给Web表单设计界面下的控件。

  图4.自动格式化屏幕

  现在,既然你已经了解了如何进行格式化,那么接下来,你应该学习何时以及为什么进行格式化。

  我仿佛听到CSS爱好者正在对本文大声叫喊。其实,我本人就是一个“层叠式样表(CSS)”迷,并在我所有的站点中使用它们。自动格式化特征在有些情况下(例如控件组)是非常有用的。例如,我们不妨考虑微软的安全控件套件(Security Control Suite)。从中,你会看到七种Web控件可用于一个站点中以为页面添加认证功能。其中,每一种控件都提供了自动格式化功能,并且每一个控件都显示一样的列表用于从中选择一个格式—这是相当不错的功能。

  试想一下我们今天已经实现的一些流行的可视化效果:例如Outlook工具栏中的许多风格,各种TreeView控件,菜单和工具栏,等。你完全可以把这其中的许多特征集成到几乎任何站点的设计风格中。在这种情况下,自动格式化是一种相当有用的工具。

  现在请记住,在每一个格式类中,我把风格值改变为我的一些风格属性。如果你在Web表单中应用这些格式中的任一个,那么,你将会看到这些风格值确实在属性浏览器中改变了。如果我想(尽管我不会选择)这样做的话,那么我可以修改style属性的CssClass值,并且把一个风格表类指派给它们。这当然可以顺利地工作,但是之后,我需要同我的控件一起发布该CSS文件,并且还要确保页面链接到它。

  如果我能够在我的控件中使用这些资源文件而不必随同控件的DLL文件一起发行它们,不是相当酷吗?好,下面我们来讨论本文的最后一个话题—Web资源。

  一、 Web资源

  Web资源是被编译到你的控件DLL中的一些嵌入式文件。Windows表单控件开发者很久以来就拥有这种能力,只是到现在它才被应用于Web领域。让我们想象开发一个依赖于某些图像才能正确生成的控件的情形。只是简单地发布该控件相应的DLL文件而不必考虑单独发布外部图像文件,这是特别有用的。分析一下微软提供的新的Tree Web控件,那么你就会发现这一特征。在此,图像文件是讨论使用Web资源的最佳候选,尽管我们还能够把它们使用于JScript文件中。

  为了说明这一问题,我想把一个邮箱图像添加到我的EmailContact控件标题的左边。你可能已经注意到,在图4中“CONTACT US”标题文字的左边已经存在一个空图像。为此,我是通过简单地创建一个Image子控件并且把它添加到控件层次中的适当位置来实现的。事实上,我还创建了一个称为ShowMailIcon的属性以允许页面开发者隐藏/显示图像。使用常规方法就能创建一个HeadingImageUrl字符串属性,并且在图像控件的ImageUrl属性中设置它的值。这将允许页面开发者把这个图像设置为他所希望的任何内容。我同意这一方法,而我在这个控件中就使用了这种方法。然而,我更深入了一步。如果HeadingImageUrl属性不是空的,那么我使用它填充图像控件的ImageUrl属性(如前所述);但是如果该属性是空的,那么我要实现一些更酷的内容。

  在我的Web控件工程中,我有一个称为mailbox.gif的邮箱图像。我将把图像的BuildAction属性设置为EmbeddedResource(注意,我可以通过属性浏览器来实现这一点)。

  接下来,我需要存取我的工程的AssemblyInfo.vb文件。在ASP.NET 1.1中,这个文件位于我的工程的根文件夹下,但是在ASP.NET 2.0中,它位于我的工程文件夹下。不仅如此,我还必须点击属性浏览器顶部的“Show All Files”按钮存取这个文件。在AssemblyInfo.vb文件的底部,我需要添加下列语句:

  <Assembly: WebResource("mailbox.gif","image/gif")>

  这一句把该嵌入式文件标识为一个可用的Web资源,以便于后面的提取操作。

  为了提取该文件,我使用了Page.ClientScript对象的GetWebResourceUrl方法。如前所述,我想使页面开发者能够使用我自己的属性重载图像;因此我以如下方式进行提取:

  If Me.HeadingImageUrl <> "" Then
  imgMail.ImageUrl = Me.HeadingImageUrl
  Else
  imgMail.ImageUrl = Page.ClientScript.GetWebResourceUrl( _
  Me.GetType(), "mailbox.gif")
  End If

  这正是我所喜爱的新的Web控件特征,因为我往往不时地在一些地方使用小图像来修饰我的控件。现在,我能够嵌入它们,并且只需要我的控件的DLL文件。

  测试该控件将显示如图5所示的结果。

  图5.带有嵌入式图像的最终完成后的控件

  该Web表单使用一个HTTP处理器来检索图像并且把它生成到浏览器。显然,这种使用可能性远远不止此。

  二、 总结

  我希望你喜欢本系列文章所介绍的内容。其实,ASP.NET 2.0在Web开发方面加入了许多酷的内容,例如引入了母板页面,主题,数据源,以及其它优秀特征。事实上,你也可以把回调(一种新的页面特征)嵌入到控件中,从而为它们加入AJAX功能以实现更好的响应时间并产生一种更为丰富的客户端体验。遗憾的是,有关这一方面的内容已经非本文所及。在本文中所介绍的新的控件开发特征当前尚不多见;当然,这也是我写本文的原因。如你所见,这其中的大多数内容都与设计时刻体验相关;但是,我一直认为,提高控件的直观性与使之有用且能够实现既定功能一样重要。如果不容易使用,那么你甚至无法全面地使用该控件所提供的功能。

    • 评论
    • 分享微博
    • 分享邮件
    闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾惧綊鏌熼梻瀵割槮缁惧墽鎳撻—鍐偓锝庝簻椤掋垺銇勯幇顏嗙煓闁哄被鍔戦幃銏ゅ传閸曟垯鍨介弻锝夘敇閻曚焦鐤佸Δ鐘靛仦閻楁洝褰佸銈嗗坊閸嬫挸鈹戦檱閸嬫劗妲愰幒妤€绠涙い鎾楀嫮鏉归梻浣告惈閻鎹㈠┑鍡欐殾闁割偅娲栭悡娑樏归敐澶嬫暠闁活偄绻樺缁樻媴閸涘﹥鍎撳銈嗗灥閹虫﹢寮绘繝鍥ㄦ櫜濠㈣泛锕ㄩ幗鏇㈡⒑閸濆嫭绁紒杈ㄦ礋閹潡鍩€椤掑嫭鐓熼幖鎼灣缁夐潧霉濠婂懎浠︾紒鍌涘浮閹瑩顢楅崒婊呮婵犵數鍋涢悧鍡涙倶濠靛鍊堕柣妯肩帛閻撳繘鏌涢銈呮瀾闁稿﹥鍔楅埀顒侇問閸犳牠鈥﹀畡閭﹀殨闁圭虎鍠楅弲顒勬煕閺囥劌骞楁繝銏″灴濮婅櫣鎷犻幓鎺戞瘣缂傚倸绉村Λ娆戠矉瀹ュ鍐€妞ゆ挾鍋熼敍娑㈡⒑鐟欏嫬绀冩い鏇嗗洤绀冮柍褜鍓欓—鍐Χ閸℃ê钄奸梺鍛婃煥濞撮鍒掑▎鎺旂杸婵炴垶鐟㈤幏娲煟閻樺厖鑸柛鏂胯嫰閳诲秹骞囬悧鍫㈠幍闂佸憡鍨崐鏍偓姘炬嫹

    濠电姷鏁告慨鐑藉极閸涘﹥鍙忛柣鎴f閺嬩線鏌涘☉姗堟敾闁告瑥绻橀弻锝夊箣濠垫劖缍楅梺閫炲苯澧柛濠傛贡缁骞掗弬鍝勪壕闁挎繂绨肩花浠嬫煕閺冩挾鐣辨い顏勫暣婵″爼宕卞Δ鈧ḿ鎴︽⒑缁嬫鍎愰柟鐟版喘瀵鈽夐姀鈥充簻闂備礁鐏濋鍛閹绢喗鈷戠紒顖涙礃閺夊綊鏌涚€n偅灏い顏勫暣婵″爼宕卞Δ鈧ḿ鎴︽⒑缁嬫鍎愰柟绋垮⒔濡叉劙骞橀幇浣告倯闂佸憡绮岄崯鎶藉触椤愨懡鏃堟偐闂堟稐绮堕梺鍝ュ櫏閸嬪鎮橀幒妤佺厽闁绘ê寮剁粚鍧楁倶韫囨梻鎳呯紒顕嗙秮閹瑩鎮滃Ο閿嬪闁荤喐绮庢晶妤冩暜閹烘挾顩插ù鐓庣摠閻撴洟鏌熼幆褜鍤熸繛鍙夋尦閺岀喖顢欓挊澶婂Б闁绘挶鍊栭妵鍕疀閹炬潙娅濋梺褰掓敱濡炶棄顫忔繝姘<婵ê宕·鈧梻浣告啞椤ㄥ棗煤閻旈鏆﹀┑鍌溓瑰敮闂侀潧锛忛崟顓炲及婵犵數濮烽弫鍛婃叏閹绢喗鏅濋柕鍫濇礌閸嬫挸顫濋悡搴$睄閻庤娲╃紞鈧紒鐘崇洴瀵噣宕掑⿰鍛潓婵犵數濮烽弫鍛婃叏閹绢喖纾圭紓浣股戝▍鐘崇箾閹存瑥鐏柣鎾存礋閹鏁愰崘銊ヮ瀳婵犵鈧尙鐭欓柡灞炬礋瀹曟儼顦叉い蹇e幘閳ь剚顔栭崰鏇犲垝濞嗘劒绻嗘慨婵嗙焾濡插綊姊洪柅鐐茶嫰婢ь垶鎮介銈囩瘈鐎殿喖顭烽幃銏ゅ礂閻撳海妾┑鐘灱濞夋盯鏁冮妷銉㈡灁濠电姵纰嶉埛鎴︽煕濠靛棗顏柣蹇涗憾閺屾盯鎮╁畷鍥р拰濡ょ姷鍋涢崯顐︺偑娴兼潙閱囨繝闈涚墕楠炴劙姊绘担鍛靛綊寮甸鍌滅煓闁规儳绠嶆禍褰掓倵閿濆骸鏋熼柣鎾跺枛楠炴牕菐椤掆偓閳ь兙鍊曞玻鍧楀箛椤撶姷顔曢梺鍛婄懃椤ャ垽顢旈崼鐔蜂患濠电娀娼ч悧蹇涙儗濞嗘挻鍋i柟顓熷笒婵¤姤绻涢崼銉х暫婵﹥妞藉畷婊堝箵閹哄秶鍑规繝鐢靛仜瀵爼鎮ч悩璇茬畺闁炽儲鏋煎Σ鍫ユ煏韫囥儳纾块柛姗€浜堕弻锝堢疀閺囩偘绮舵繝鈷€鍌滅煓闁诡垰鐬奸埀顒婄秵閸嬪棛绮绘ィ鍐╃厱妞ゆ劑鍊曢弸鎴︽煕婵犲啫濮堥柟渚垮妽缁绘繈宕熼鐐殿偧闂備胶鎳撻崲鏌ュ箠閹邦喖鍨濋柣銏㈩暯閸嬫捇鏁愭惔婵堝嚬闂佷紮绲奸崡鍐差潖缂佹ɑ濯撮悷娆忓娴犫晠姊洪崨濠冪叆妞ゆ垵顦悾鐑芥晸閻樿尙顦ㄩ梺鑲┾拡閸撴艾鈻撴ィ鍐┾拺闂傚牊绋撶粻鐐烘煕婵犲啯绀嬬€规洘锕㈤崺鈧い鎺嗗亾妞ゎ亜鍟存俊鍫曞幢濡も偓椤洭姊虹粙鍧楊€楃€规洦鍓熼幆鍐敆閸曨兘鎷婚梺绋挎湰閻熝囧礉瀹ュ鍊电紒妤佺☉閹虫劙鎯屽▎鎾寸厵閻庣數枪琚ラ梺绋款儐閹告悂锝炲┑瀣亗閹艰揪绱曢惈鍕煟鎼淬値娼愭繛鍙夊灴瀹曪繝宕橀懠顒佹闂佸憡顨堥崐锝夋偄閸忓皷鎷归梺褰掑亰閸犳艾鈻旈崸妤佲拻闁稿本鐟х粣鏃€绻涙担鍐叉处閸嬪鏌涢埄鍐槈缂佺姷濞€楠炴牕菐椤掆偓閻忣亪鏌涘▎蹇曠闁靛洤瀚伴獮鎺楀幢濡炴儳顥氶梻鍌欑閻ゅ洭锝炴径鎰瀭闁秆勵殔閺勩儵鏌曟径鍡樻珔妤犵偑鍨烘穱濠囨倷閹绘巻鎸冮梺鍛婂灱椤鎹㈠┑瀣仺闂傚牊鍒€閻愮儤鐓曢柕濠忛檮閵囨繈鏌℃担鍓插剶闁哄睙鍥ㄥ殥闁靛牆鎳嶅▽顏呯箾閿濆懏鎼愰柨鏇ㄤ簼娣囧﹪宕奸弴鐐茬獩濡炪倖鏌ㄩ崥瀣敂閿熺姵鈷掑ù锝呮嚈瑜版帩鏁勯柛娑欐綑绾惧綊鏌涢敂璇插箺鐎规洖寮剁换娑㈠箣濞嗗繒浠煎Δ鐘靛亼閸ㄧ儤绌辨繝鍥舵晬婵犲灚鍔曞▓顓犵磼閻愵剚绶茬紒澶婄秺瀵鏁愰崱妯哄妳闂侀潧楠忕徊浠嬎夊┑鍡╂富闁靛牆绻愰々顒勬煛娴g瓔鍤欐い鏇悼閹风姴顔忛鍏煎€梻浣稿閸嬫帡宕戦崨鎼晛闁搞儺鍓氶埛鎺懨归敐鍛暈闁哥喓鍋ら弻鐔煎箹娴h櫣鏆悗瑙勬礈婢ф骞嗛弮鍫氣偓锕傚箣閻愬瓨鐝ㄩ梻鍌欑劍鐎笛呮崲閸岀倛鍥ㄧ鐎n亝鐎梺绯曞墲閵囨粓鍩€椤掍礁绗掓い顐g箞椤㈡绻濋崒姘兼浆闂傚倷鐒﹀鍧楀储娴犲鈧啴宕卞▎鎰簥濠电偞鍨崹鍦不婵犳碍鐓涘璺侯儏閻忊晠鏌涢弬鎯у祮婵﹨娅g划娆忊枎閹冨闂備礁婀遍幊鎾趁洪鐑嗗殨濠电姵纰嶉弲鏌ユ煕濞戝彉绨兼い顐㈢Т閳规垿鎮欓崣澶樻!闂佸憡姊瑰ú婊勭珶閺囥垹绠柤鎭掑劗閹锋椽姊洪崨濠勭畵閻庢凹鍘奸敃銏ゅ箥椤斿墽锛滈柣搴秵閸嬪嫰鎮橀幘顔界厱闁冲搫鍟禒杈殽閻愬樊鍎旈柡浣稿暣閸┾偓妞ゆ巻鍋撴い鏂跨箰閳规垿宕堕妷銈囩泿闂備礁鎼ú銊╁磻閻愬搫闂繛宸簼閻撳啴鎮归崶顏嶅殝闁告梻鍠撶槐鎺撴綇閵婏箑纾抽悗瑙勬礃鐢帡鍩㈡惔銊ョ婵犻潧妫悗鏉戔攽閿涘嫬浜奸柛濠冨灴瀹曟繂鐣濋崟顐ょ枃濠殿喗銇涢崑鎾搭殽閻愬弶顥滈柣锝嗙箞瀹曠喖顢曢妶蹇曞簥闂傚倷鑳剁划顖炲礉濞嗗繄缂氱憸蹇曞垝婵犲洦鏅濋柛灞剧〒閸樼敻姊虹粙璺ㄧ闁稿鍔欏鍐差潨閳ь剟寮诲☉銏犳閻犳亽鍔庨崝顖炴⒑鐠団€虫灓闁稿鍊曢悾鐤亹閹烘繃鏅濋梺缁樓规禍顒勬儎鎼淬劍鈷掑ù锝呮啞鐠愶繝鏌涙惔娑樷偓婵嗙暦瑜版帗鍋ㄩ柛鎾冲级閺呮粓姊洪崘鍙夋儓闁瑰啿绻樺畷鎰板垂椤愶絽寮垮┑顔筋殔濡鏅剁紒妯圭箚闁圭粯甯炵粔娲煛鐏炵偓绀嬬€规洘鍎奸ˇ鍙夈亜韫囷絽骞橀柍褜鍓氶鏍窗閺囩姴鍨濇繛鍡樺姃缁诲棙鎱ㄥ┑鍡欑劸婵℃彃銈稿娲閳哄啰銈烽梺绋块绾绢厼危閹版澘绠婚悗娑櫭鎾剁磽娴e湱鈽夋い鎴濇噹閳绘捇顢橀悙鈺傛杸闂佸疇妫勫Λ妤佺濠靛牏纾奸悹鍥皺婢ф洘銇勯弴顏嗙ɑ缂佺粯绻傞~婵嬵敇閻愭壆鐩庣紓鍌欒兌閸嬫挻鍒婇懞銉d粓闁归棿绀侀悿楣冩煥濠靛棭妲归柛瀣у墲缁绘繃绻濋崒姘闁轰礁鐗撳铏规嫚閳ヨ櫕鐏嶉梺鎸庢磸閸ㄤ粙鐛繝鍥ㄥ亹婵炶尙绮弲銏ゆ⒑缁嬫寧婀扮紒顕嗙悼濡叉劙寮婚妷锔规嫼闂佸憡绻傜€氬嘲危閹间焦鐓熸俊銈傚亾閻庢碍婢橀悾宄扳攽閸ャ劌鍔呴梺鎸庣箓濞诧絿绮径鎰拺缂備焦锚婵鏌涙惔娑樷偓婵嬪箖閳ユ枼妲堥柕蹇娾偓鏂ュ亾閸洘鐓熼柟閭﹀灡绾墽鎮鑸碘拺闁告縿鍎辨牎闂佹寧娲忛崹钘夘嚕婵犳艾鐒洪柛鎰ㄦ櫅椤庢捇姊洪懡銈呮瀾婵犮垺锕㈤敐鐐哄箳濡や礁鈧敻鎮峰▎蹇擃仾缂佲偓閸儲鐓曢柣妯虹-婢х敻鏌曢崱鏇狀槮妞ゎ偅绮撻崺鈧い鎺戝缁犳牠鏌嶉崫鍕櫤闁诡垳鍋為妵鍕箛閳轰讲鍋撳┑瀣濠电姵纰嶉埛鎺楁煕鐏炴崘澹橀柍褜鍓欓崲鏌ユ箒闂佹悶鍎滈崨顔筋啎闂備礁澹婇悡鍫ュ磻閸涙潙鐭楅煫鍥ㄧ⊕閻撴瑩鏌熼娑欑凡鐞氭岸姊洪幎鑺ユ暠婵﹤顭烽崺鈧い鎺嗗亾缂佺姴绉瑰畷鏇㈡焼瀹撱儱娲、娑㈡倷閹绘帞鈧參姊虹粙璺ㄧ伇闁稿鍋ら幃锟犲閳ヨ尙绠氬銈嗙墬缁矂鎯冮幋鐘电<閺夊牄鍔岄崫娲煛鐏炶濡奸柍瑙勫灴瀹曢亶鍩¢崒鍌﹀缁辨挻鎷呴幓鎺嶅闂佽鍑界紞鍡涘磻娴e湱顩叉繝濠傜墛閻撴稓鈧箍鍎遍崯顐d繆閸ф鐓冮柦妯侯樈濡插湱绱掔紒妯兼创鐎规洖銈搁幃銏ゆ惞閸︻厼甯ㄥ┑鐘愁問閸犳牠鏁冮妸銉㈡瀺闁挎繂鎳夊Σ鍫ユ煟閵忊懚鐟邦啅濠靛洢浜滈柡鍌涘劤鐎氬酣鏌嶈閸撴岸宕濋弴鈶┾偓鏃堝礃椤斿槈褔骞栫划鍏夊亾閼碱剙鍤┑鐘垫暩閸嬬姷浜稿▎鎾崇獥闁哄诞鍛濠电偛妯婃禍婊堟倿閸偁浜滈柟鍝勭Ч濡惧嘲霉濠婂嫮鐭掗柡宀€鍠栧畷顐﹀礋椤掑顥e┑鐐茬摠缁挾绮婚弽褜娼栨繛宸簻缁犱即骞栧ǎ顒€鐏柍瑙勭⊕缁绘繄鍠婂Ο宄颁壕闁惧浚鍋呴幃娆撴煕濡ゅ懍鎲鹃柡宀€鍠栭幃褔宕奸悢鍝勫殥缂傚倷绶¢崑鍕矓瑜版帒钃熸繛鎴欏焺閺佸啴鏌ㄥ┑鍡樺窛闁伙絿鍘ч—鍐Χ閸℃ê闉嶅┑锛勫仩濡嫰锝炶箛娑欐優闁革富鍘鹃敍婊冣攽閳藉棗鐏犻柟纰卞亰閿濈偛鐣濋崟顒€鈧灚绻涢崼婵堜虎婵炲懏锕㈤弻娑㈠箻鐎靛憡鍣梺璇茬箰閸熸潙顫忓ú顏勫窛濠电姳鑳剁换渚€姊洪崫銉バg€光偓缁嬭法鏆﹂柕蹇嬪€曠粻鐟懊归敐鍛辅闁归攱妞藉娲川婵犲啫闉嶉悷婊勬緲閸燁垳绮嬪鍡欘浄閻庯綆鍋嗛崢閬嶆煟鎼搭垳绉靛ù婊勭矒椤㈡棃顢橀姀锛勫幍濡炪倖姊归弸濠氭嚀閸ф鐓涘ù锝嚽归弳锝団偓瑙勬礃鐢帡鍩ユ径濠庢僵妞ゆ巻鍋撶紒鐘靛仱濮婄粯鎷呯粵瀣闂佸憡鍨归弲顐ゆ閻愬搫骞㈡繛鎴炨缚閿涙盯姊虹化鏇炲⒉閽冭鲸绻涢崨顖毿i柕鍥у楠炴帡骞嬪┑鍐ㄤ壕闁煎鍊曢ˉ姘攽閸屾碍鍟為柣鎾存礋閹﹢鎮欓幓鎺嗘寖闂佸疇妫勯ˇ杈╂閹烘埈娓婚柨鏇楀亾婵炶绠撻幃鈥斥枎閹惧鍘介梺缁樻煥閹诧紕娆㈤崣澶堜簻闊浄绲藉顕€鏌″畝鈧崰鏍箖閳╁啯鍎熼柕蹇ョ悼椤㈠懘姊绘担鐑樺殌鐎殿喖鐖奸獮鎰板礃閼碱剚娈鹃梺缁樻煥閸氬藟閸喓绠鹃柟瀵稿仧閹冲嫰鏌e┑鎾剁瘈婵﹤顭峰畷鎺戭潩椤戣棄浜鹃柟闂寸绾剧懓顪冪€n亝鎹i柣顓炴閵嗘帒顫濋敐鍛婵°倗濮烽崑鐐烘偋閻樻眹鈧線寮撮姀鐘栄囨煕鐏炲墽鐓瑙勬礀閳规垿顢欓惌顐簻閻g兘顢楅崟顐㈠亶闁诲海鏁哥涵鍫曞磻閹捐埖鍠嗛柛鏇ㄥ墰椤︺儳绱撻崒姘毙㈤柨鏇ㄤ簻椤曪絾绻濆顒€鑰垮┑掳鍊曢敃銈夊箖閹达附鈷戦柛娑橈梗缁堕亶鏌涢妸褎娅曟俊鍙夊姍閺屽棗顓奸崱娆忓箺闂佺澹堥幓顏嗘閺囥垺鍋╂い鎾卞灪閻撴洟骞栧ǎ顒€濡洪柟鏌ョ畺閹稿﹤鈹戦崰銏犵秺瀹曟宕楅懖鈺冣枏缂備胶鍋撳畷妯衡枖閺囥垹鐒垫い鎺嗗亾缂佺姴绉瑰畷鏇㈡焼瀹撱儱娲︾€佃偐鈧稒锚娴滄姊洪崫鍕偍闁搞劍妞介幃鈥斥槈閵忥紕鍘遍梺闈涱檧缁蹭粙宕濆顑芥斀闁挎稑瀚敮鑸点亜椤撯€冲姷妞ぱ傜窔閺屾盯濡搁妶鍛ギ濠电姭鍋撳〒姘e亾婵﹨娅g槐鎺懳熼幘铏础缂侇喗妫冮、姘跺焵椤掑嫨鈧線寮崼婵嬪敹闂佺粯妫佸▍锝夊汲閵忋倖鈷掗柛灞捐壘閳ь剛鍏橀幃鐐烘晜闁款垰浜剧紒妤佺☉閹冲繐鐣烽弻銉︾厱閻忕偟铏庨崵銈嗙箾閹寸儐鐒搁柡鍐ㄧ墕瀹告繃銇勯幘璺盒㈡鐐村浮濮婄粯鎷呴崨濠冨創濠电偛鐪伴崹钘夌暦閻熸噴娲敂閸涱厺绨甸梻浣告啞閸旓附绂嶅┑瀣獥闁归偊鍘剧弧鈧繝鐢靛Т閸婄粯鏅堕弴鐘电<闁逞屽墴瀹曞ジ鎮㈢粙鍨紟婵犵妲呴崹杈┾偓绗涘懏鍏滃Δ锝呭暞閻撱垽鏌涢幇鍏哥盎闁哄鐩弻锛勪沪閻愵剛顦ㄧ紓浣虹帛缁嬫牠藝閺屻儲鍊垫慨妯哄船閸樺鈧娲橀崹鎸庝繆閹间礁鐓涢柛灞绢殕鐎氳偐绱撻崒姘偓鐑芥倿閿曞倹鏅┑鐘愁問閸犳牕煤閿曞倸桅闁告洦鍨伴崡鎶芥煏婵炲灝鍔氱紒顐㈢Ч濮婅櫣鍖栭弴鐔告緭闂佹悶鍔忓Λ鍕敋閿濆绠绘い鏃傗拡濞煎﹪姊洪悙钘夊姕闁哄銈稿畷鎴﹀箻缂佹ê浠洪梺鍛婄☉閿曪箓宕i崱妞绘斀闁绘ḿ绮☉褎淇婇锝庢疁闁糕斁鍋撳銈嗗笒閸婂綊宕甸埀顒勬⒑鐎圭媭鍤欑紒澶屾嚀閻g兘宕奸弴鐐嶁晠鏌ㄥ┑鍡楊伀闁烩晛閰e缁樼節鎼粹€茬盎濠电偠顕滄俊鍥╁垝濞嗘挸绠涢柡澶庢硶閻ゅ懏淇婇妶蹇曞埌闁哥噥鍨堕幃鈥斥槈閵忊€斥偓鍫曟煟閹扮増娑уù婊冨⒔缁辨帡鍩€椤掑倵鍋撻敐搴″幋闁稿鎸鹃幉鎾礋椤掑偆妲瑰┑鐐茬摠缁矂鎮ユ總绋课ュù锝呭濞笺劑鏌嶈閸撶喖鐛崘顔碱潊闁挎稑瀚板顔界節閵忥絾纭炬い锕侀哺瀵板嫰鏁撻敓锟�

    重磅专题
    往期文章
    最新文章