位置:海鸟网 > IT > ASP.NET >

ASP.NET2.0服务器控件之复合控件样式

为了设置复合控件的外观,复合控件必须提供一些样式属性,尤其是针对子控件的样式属性。在本文中,我们将重点介绍为复合控件实现样式属性的两种方法。

  1、上传部分样式属性

  在为复合控件实现样式属性之前,读者应首先了解"样式冒泡"的基本概念。样式冒泡多用于实现复合控件的样式属性。由于在复合控件中包含多个子控件,因此,这些子控件的样式属性可能在一定情况下,干扰复合控件的样式属性,引起样式属性混乱。为了更加明确的定义复合控件的样式属性,可以采取将子控件的样式属性上传为顶级样式属性的方法,这就是所谓的"样式冒泡"。

  通常情况下,开发人员可能面对两种情况:一种是上传子控件中少数样式属性,另一种是上传子控件中所有样式属性。本小节只介绍针对第一种情况的实现方法,而另外一种将在后面一节中进行讲解。 

  本节说明的这种实现样式属性的方法,其关键是通过为子控件的Attributes指定键/值对,引入样式属性,由此将子控件的样式属性上传为复合控件顶级属性。为了方便读者理解这一方法,下面列举了一个典型应用。

  在本示例中,实现了一个复合控件MyControl,其子控件集合中包括一个Table控件。当前,需要将Table子控件的样式属性CellPadding和Border上传为MyControl的顶级样式属性。具体源代码如下所示。 



public class MyControl : CompositeControl{ 
 // 相关代码 ......
 // 定义初始值

 private int _cellPadding = 0;
 private int _border = 1;
 ......
 // 定义样式属性,它和Table控件的样式属性CellPadding和Border类似 
 public int CellPadding{
  get { return _cellPadding; }
  set { _cellPadding = value; }
  // 实现属性Border 
  public int Border{
   get { return _border; } 
   set { _border = value; }
   ...... 
   // 重写CreateChildControls方法 
   protected override void CreateChildControls() {
    //相关代码 
    ......
    Table t = new Table(); //将前面定义的属性添加到键/值对中   
    t.AddAttributes.Add("CellPadding",_cellPadding.ToString()); 
    t.AddAttributes.Add("Border",_border.ToString()); 
    ......
   }
 }

  以上代码显示了MyControl的一些关键源代码,其重点在于说明实现部分样式属性冒泡的关键步骤。(1)初始化顶级样式属性的字段,如果有必要可以定义初始值。(2)定义与需要升级的子控件的样式属性相同名称的属性。上面的代码中定义了属性CellPadding和Border。(3)在子控件的Attributes的键/值对中引入第2步中定义的属性。

  当设置MyControl中的样式属性CellPadding和Border的属性值时,实际是设置Table子控件的CellPadding和Border的属性值。通过以上3个关键步骤就可实现样式冒泡。

  如果读者仔细观察可以发现,以上介绍的这种实现样式冒泡的方法存在一些问题:一、这种方法只适用于升级子控件中少数样式属性。如果需要将子控件的所有样式属性都升级,而仍然使用这种方法,则实现起来非常繁琐,容易产生错误。二、所实现的样式属性缺乏逻辑性和组织性。在某种情况下,例如,多个子控件的同一样式属性都需要升级为顶级属性,这时使用该方法将会引起混乱。 

  为了解决这些问题,下面介绍一种上传子控件全部样式属性的实现方法。 

  2、上传全部样式属性

  在上一节中,说明了有关实现复合控件样式的内容,但是,那种实现方法只能实现子控件部分的样式,并且缺乏逻辑性和组织性。本小节介绍的实现复合控件样式属性的方法有效避免了以上问题。它实现了多重委托的属性,即对每个子控件分别定义Width、Height等样式,更进一步的讲,即实现每个子控件对应的Style类型的复杂样式属性,例如,TextBoxStyle、ButtonStyle。通过这种方式子控件的样式属性就上传为顶层属性,以便于设置子控件的外观。

  显而易见,实现子控件的样式属性上传的关键是实现Style类型的复杂样式属性。为此,开发人员必须为复杂样式属性提供自定义视图状态管理。需要读者注意的是,复合控件的视图状态与普通控件视图状态有所不同。由于复合控件包含子控件,因此,相应的视图状态中既包括父控件的视图状态,也包括子控件对应的复杂样式属性的视图状态。例如,上文实例中控件的视图状态即包含3个部分:父控件自身的视图状态、ButtonStyle的视图状态和TextBoxStyle的视图状态。除此之外,具体的实现过程与实现普通的复杂属性基本一致。

  不知读者是否还记得上一篇文章中的那个复合控件,即由一个文本框TextBox和一个按钮Button组成的复合控件CompositeEvent。在此,我们对该控件添加设置了控件的顶层样式属性ButtonStyle和TextBoxStyle。下面列举了控件类CompositeEvent的源代码。



using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.ComponentModel.Design;
namespace WebControlLibrary{
 public class CompositeEvent : CompositeControl {
  //声明变量 
  private Button _button;
  private TextBox _textBox;
  private static readonly object EventSubmitKey = new object(); 
  //声明样式变量 
  private Style _buttonStyle;
  private Style _textBoxStyle;
  //定义属性ButtonText,用于指定按钮上的文字 
  [Bindable(true), Category("Appearance"), DefaultValue(""), Description("获取或设置显示显示在按钮上的文字")] 

  public string ButtonText {
   get { EnsureChildControls(); return _button.Text; } 
   set { EnsureChildControls(); _button.Text = value; } 
  }
  //定义属性Text,表示文本框的输入 

  [Bindable(true), Category("Appearance"), DefaultValue(""), Description("获取或设置文本框输入文本")]
  public string Text { 
   get { 
    EnsureChildControls(); 
    return _textBox.Text; 
   }
   set {
    EnsureChildControls();
    _textBox.Text = value;
   }
  }
  // 定义ButtonStyle属性 
  [ Category("Style"), Description("Button的样式属性"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerDefaultProperty) ] 

  public virtual Style ButtonStyle { 
   get { 
    if (_buttonStyle == null) { 
     _buttonStyle = new Style(); 
     if (IsTrackingViewState) { 
      ((IStateManager)_buttonStyle).TrackViewState();
     }
    }
    return _buttonStyle;
   }
  }
  //定义TextStyle属性

  [ Category("Style"), Description("设置TextBox的样式属性"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty) ] 

  public virtual Style TextBoxStyle { 
   get {
    if (_textBoxStyle == null) {
     _textBoxStyle = new Style();
     if (IsTrackingViewState) {
      ((IStateManager)_textBoxStyle).TrackViewState();
     }
    }
    return _textBoxStyle;
   }
  }
  // 实现事件属性结构 
  public event EventHandler Submit { 
   add { Events.AddHandler(EventSubmitKey, value); } 
   remove { Events.RemoveHandler(EventSubmitKey, value); }
  }
  // 实现OnSubmit 
  protected virtual void OnSubmit(EventArgs e) { 
   EventHandler SubmitHandler = (EventHandler)Events[EventSubmitKey]; 
   if (SubmitHandler != null) {
    SubmitHandler(this, e); 
   }
  }

  // 重写ICompositeControlDesignerAccessor接口的RecreateChildContrls方法 
  protected override void RecreateChildControls() { EnsureChildControls(); } 
    //重写CreateChildControls方法,将子控件添加到复合控件中 
  protected override void CreateChildControls() {
   Controls.Clear(); 
   _button = new Button();
   _textBox = new TextBox();
   _button.ID = "btn"; 
   //_button.Click += new EventHandler(_button_Click);
   _button.CommandName = "Submit"; 
   this.Controls.Add(_button); 
   this.Controls.Add(_textBox);
  }
  // 重写OnBubbleEvent方法,执行事件冒泡 
  protected override bool OnBubbleEvent(object source, EventArgs e) { 
   bool handled = false; 
   if (e is CommandEventArgs) {
    CommandEventArgs ce = (CommandEventArgs)e; 
    if (ce.CommandName == "Submit") { 
     OnSubmit(EventArgs.Empty); 
     handled = true;
    }
   }
   return handled; 
  }
  //重写Render方法,呈现控件中其他的HTML代码 
  protected override void Render(HtmlTextWriter output) {
   AddAttributesToRender(output);
   if (_textBoxStyle != null) { 
    _textBox.ApplyStyle(TextBoxStyle);
   }
  if (_buttonStyle != null) { 
   _button.ApplyStyle(ButtonStyle);
  }
  output.AddAttribute(HtmlTextWriterAttribute.Border, "0px"); 
  output.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "5px"); 
  output.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0px");    
  output.RenderBeginTag(HtmlTextWriterTag.Table);
  output.RenderBeginTag(HtmlTextWriterTag.Tr); 
  output.RenderBeginTag(HtmlTextWriterTag.Td); 
  _textBox.RenderControl(output);
  output.RenderEndTag();
  output.RenderBeginTag(HtmlTextWriterTag.Td);
  _button.RenderControl(output); 
  output.RenderEndTag();   
  output.RenderEndTag();
  output.RenderEndTag();
 }
 //复杂样式属性的状态管理,重写3个相关方法LoadViewState、  SaveViewState、TrackViewState

 protected override void LoadViewState(object savedState) { 
  if (savedState == null) {
   base.LoadViewState(null);
   return;
  }
  if (savedState != null) { 
   object[] myState = (object[])savedState;
   if (myState.Length != 3) { throw new ArgumentException("无效的ViewState"); }    
   base.LoadViewState(myState[0]); if (myState[1] != null) {    
     ((IStateManager)TextBoxStyle).LoadViewState(myState[1]);
   }
   if (myState[2] != null) {
    ((IStateManager)ButtonStyle).LoadViewState(myState[2]); 
   }
 }
}

 protected override object SaveViewState() { 
  object[] myState = new object[3];
  myState[0] = base.SaveViewState();
   myState[1] = (_textBoxStyle != null) ? ((IStateManager)_textBoxStyle).SaveViewState() : null;
   myState[2] = (_buttonStyle != null) ? ((IStateManager)_buttonStyle).SaveViewState() : null; 
   for (int i = 0; i < 3; i++) { 
    if (myState[i] != null) { return myState; } 
   }
   return null;
  }
  protected override void TrackViewState() { 
   base.TrackViewState();
   if (_buttonStyle != null) { 
    ((IStateManager)_buttonStyle).TrackViewState(); 
   }
   if (_textBoxStyle != null) { 
    ((IStateManager)_textBoxStyle).TrackViewState();
   }
  }
 }
}

  如果读者看过前面的文章,那么应该对以上代码不陌生。限于篇幅,本文不对先前说明过的内容进行讲解,而重点说明有关样式冒泡的内容。

  与样式冒泡相关的内容可以分成三个部分:一是定义Style类型的复杂样式属性:ButtonStyle和TextBoxStyle;二是在Render方法中为子控件应用复杂样式属性;三是实现复杂样式属性的自定义视图状态管理部分。以上三个部分的实现,实际是实现子控件样式上传过程中最为关键的三个步骤。前两个部分的实现比较简单,在此就不多加说明。下面重点说明最后一个部分的实现。

  第三部分主要实现复杂样式属性的自定义状态管理。在TrackViewState方法中,分别对基类、_textBoxStyle和_buttonStyle调用TrackViewState。在SaveViewState方法中,首先定义一个myState对象数组,然后按顺序将基类、TextBox和Button的视图状态数据保存到myState中并返回。在LoadViewState方法中,实现将所保存的状态数据(savedState)的第一部分加载入基类,第二部分加载给TextBoxStyle,第三部分加载给ButtonStyle,之所以按照如此顺序加载是与SaveViewState方法中的保存顺序对应的。

  下面是CompositeEvent控件的应用代码片断。请读者注意的是:ButtonStyle和TextBoxStyle都是作为内部嵌套形式属性而标记,用户通过设置样式属性即可完成对子控件的外观设置。

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register TagPrefix="Sample" Assembly="WebControlLibrary" Namespace="WebControlLibrary" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
<script runat="server">
void demo1_Submit(object sender, EventArgs e) 
{
  lbMessage.Text = "您刚才输入的是:" + demo1.Text;
 }
</script>
<html xmlns="">
<head id="Head1" runat="server"> 
<title>为复合控件实现样式</title>
</head>
<body> <form id="form1" runat="server"> 
<div>
<Sample:CompositeEvent ID="demo1" runat="server" ButtonText="提交" OnSubmit="demo1_Submit">
<TextBoxStyle Width="198px" Height="20px" BorderWidth="1px" BackColor="orange">
</TextBoxStyle>
<ButtonStyle Width="84px" Height="24px" BorderWidth="1px" BorderStyle="dotted"></ButtonStyle>
</Sample:CompositeEvent>
<br />
<asp:Label ID="lbMessage" runat="server">
</asp:Label> </div>
</form>
</body>
</html>

  下面列举了示例应用效果图。



图1 效果图


  如图1所示,复合控件中的文本框和按钮外观由ButtonStyle和TextBoxStyle属性设置。读者可以如同设置单个控件的样式那样,设置文本框和按钮的样式。当然,这些属性必须包含在<ButtonStyle>和<TextBoxStyle>标记中。这是必须牢记的。

  3、小结

  本章介绍了为复合控件实现样式属性的具体方法,其中包括上传部分样式属性和上传全部样式属性等。对于开发人员而言,需要根据具体情况选择使用不同的方法。通常而言,对于简单的复合控件,建议使用上传部分样式属性的方法;而对于功能复杂的复合控件而言,则建议使用上传全部样式属性。