.net控件开发(六)之深究控件样式_[Asp.Net教程]                                           					
   前言:
上节中已讲述了呈现原理,我们在开发自已控件时,重写了Render方法来创建自定义输出,ASP.net给控件开发者提供了编程方式控制样式的途径。样式控制控件的可视化外观。如果您开发支持样式的控件,则从 System.Web.UI.WebControls.WebControl 派生,它将样式作为强类型属性(例如 Font、Height、Width 等)公开并为使用样式属性提供方法
概要:
1.样式概述,了解控件样式属性的创建方法
2.操作ControlStyle的几种方法
3.WebControl是如何把样式生成委托给ControlStyle属性的
4.样式的状态管理
  5.重写样式属性
6.实现自定义类型化样式
正题
一.样式概述:样式是如何工作的
我们都知道在webcontrol类中已经内含了许多样式属性,这些属性由生成的html元素的可视化外观。WebControl类的样式功能封装在controlstyle属性中,样式属性实际上是ControlStyle属性的子属性。我们来看看示例就清楚了,这段代码包含了webcontrol 的BorderColor属性的定义。
public virtual Color BorderColor
{
    get
    {
        if (!this.ControlStyleCreated)
        {
            return Color.Empty;
        }
        return this.ControlStyle.BorderColor;
    }
    set
    {
        this.ControlStyle.BorderColor = value;
    }
}
  
其中的this.ControlStyle.BorderColor中,从这里可以看出BorderColor的内部属性,那么我们有必要来看看ControlStyle的源代码,它是如何实现的。一步一步的解析其实现
public Style ControlStyle
{
//定义一个ControlStyle属性
    get
    {
        if (this.controlStyle == null)
        {
            this.controlStyle = this.CreateControlStyle();
            if (base.IsTrackingViewState)
            {
                this.controlStyle.TrackViewState();
            }
            if (this._webControlFlags[1])
            {
                this._webControlFlags.Clear(1);
                this.controlStyle.LoadViewState(null);
            }
        }
        return this.controlStyle;
    }
}
protected virtual Style CreateControlStyle()
{
//定义CreateControlStyle方法
    return new Style(this.ViewState);
}
  
ControlStyle是一个只读属性,返回一个Style类型,当第一次访问该属性的时候被创建。其实现过程:当controlStyle不存在时,我们就调用了CreateControlStyle方法去创建一个Style类型,然后,执行视图状态跟踪任务,其具体过程由Style类所提供的TrackViewState方法来完成。然后加载视图状态。
对于上述代码,我们来了解两点:
1.为什么是由Style类提供的TrackViewState方法完成呢。我们接下来看看如过程:
 在ControlStyle我们调用了this.controlStyle.TrackViewState()方法,而其中的TrackViewState方法调用的是Style的TrackViewState方法:
伪代码如下:style中的TrackViewState方法
protected internal virtual void TrackViewState
{
    
        this.ViewState.TrackViewState();
  
}
  
2.那么我们来看看CreateControlStyle为我们做了什么。
CreateControlStyle方法的默认实现返回一个Style对象,调用其Style的构造函数,接收一个ViewState状态包,那么ViewState是在WebControl是如何实现的呢, protected virtual StateBag ViewState
{
    get
    {
        if (this._viewState == null)
        {
            this._viewState = new StateBag(this.ViewStateIgnoresCase);
            if (this.IsTrackingViewState)
            {
                this._viewState.TrackViewState();
            }
        }
        return this._viewState;
    }
}
  
它去实例化一个StateBag,创建一个状态包实例,再判断其视图状态是否跟踪了其视图状态,在这里是确保视图被正确保存到了ViewState中。它是一个虚方法,所以我们可以在派生的控件中可以重载其方法来创建并返回一个自定义的Style类的自定义样式。
ControlStyle属性返回的的类型是System.Web.UI.WebControls.Style类,而Style实现了样式属性并有内建了状态管理和生成功能。
伪代码:
public class Style : Component, IStateManager
{    
…}
  
二.操作ControlStyle的几种方法
为了操作ControlStyle属性,WebControl定义了三种方法。
1.CreateControlStyle 自定义控件可以重载该方法来修改样式属性的默认值或为ControlStyle属性创建一个新的类型化样式。
格式:
protected virtural Style CreateControlStyle()
创建由WebControl类在内部用来实现所有与样式有关的属性的样式对象。
  
注意:因为WebControl不会自动保存ControlStyle的ViewState,所以如果要重写CreateControlStyle,那么最好按默认的策略,把ViewState作为Style保存数据的地方:
示例:
protected override Style CreateControlStyle()
{
   return new TableStyle(ViewState);
}
  
1. ApplyStyle把上传给该方法的样式中设置的样式属性复制到控件自已的ControlStyle中。
格式:
public void ApplyStyle(Style s)
将指定样式的所有非空白元素复制到服务器控件,改写控件的所有现有的样式元素。其中s表示要复制的样式。
示例:
开始:如下

Style style = new Style();
        style.BorderColor = Color.Red;
       this.controlTable.ApplyStyle(style);//这里的controlTable是一个自定义控件
      应用样式后:
    
  
1.MergeStyle 从上传给该方法的样式中复制样式属性到控件自已的ControlStyle中,即复制那些指定的样式中设置的而不是在ControlStyle中设置的属性。
格式:
public void MergeStyle(Style s) 
   
    将指定样式的所有非空白元素复制到服务器控件,但不改写该控件现有的任何样式元素。其中s表示要复制的样式。
示例:
开始:如下
Style style = new Style();
        style.BorderColor = Color.Red;
this.controlTable.MergeStyle(s);
应用后:

现在,就可以看出ApplyStyle与MergeStyle的区别了。
三.WebControl是如何把样式生成委托给ControlStyle属性的
接下来,我们来分析WebControl是如何把样式生成后给ControlStyle属性的。在WebControl的AddAttributesToRender方法中,它调用了ControlStyle 属性的AddAttributesToRender方法。
protected virtual void AddAttributesToRender(HtmlTextWriter writer)
{
  。。。。。。。。。。。。。
。。。。。
    if (this.ControlStyleCreated && !this.ControlStyle.IsEmpty)  //是否已经创建了控件样式
    {
        this.ControlStyle.AddAttributesToRender(writer, this);
}
。。。。。。。。。。。
。。。。
}
public bool ControlStyleCreated
{
    get
    {
        return (this.controlStyle != null);
    }
}
  
前面说过。WebControl中的样式属性实际上是ControlStyle属性的子属性,ControlStyle属性 AddAttributesToRender方法实现了把这些属性作为控件标签中的HTMl或Css来生成逻辑。
四.样式的状态管理
WebControl是如何把与样式相关的状态管理委托给ControlStyle属性来完成的呢?从前面我们可以看到ControlStyle属性的Style类型实现了IStateManager接口,并对自身进行状态管理。在TrackViewState,SaveViewState,LoadViewState方法中,WebControl调用了ControlStyle属性的相应方法,
为了在WebControl实现状态管理,在Style中有两个构造器,一个只有一个StateBag类型的参数,另一个没有任何参数。当在CreateControlStyle中创建控件的样式时,WebControl使用的是一个参数的构造器,把自已的ViewState传给该构造器,
protected virtual StyleCreateControlStyle()
{
    return new Style(this.ViewState);
}
 
Style的两个构造函数:
publicStyle(StateBag bag)
{
   。。
    GC.SuppressFinalize(this);
}
 
  
publicStyle() : this(null)
{
   ,。。。
}
五:重写样式属性
 样式属性的重载与其他属性的重写类似,但必须记住一点:就是对属性值的所估摸的修改必须给控件的ControlStyle属性。
 示例:
我们举一个了简单示例。
控件代码
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
/**//// 
/// LabelStyle 的摘要说明
/// 
/// 
namespace cnblogs.sui
{
    public class LabelStyle :Label
    {
        public LabelStyle()
        {
            base.BorderColor = System.Drawing.Color.Red;
            base.ForeColor = System.Drawing.Color.Red;
            //
            // TOD 在此处添加构造函数逻辑
            //
        }
        public override System.Drawing.Color BorderColor
        {
            get
            {
                return base.BorderColor;
            }
            set
            {
                throw new NotSupportedException("can not set BorderColor");
            }
        }
        public override System.Drawing.Color ForeColor
        {
            get
            {
                return base.ForeColor;
            }
            set
            {
                base.ForeColor = value;
            }
        }
    }
}
Aspx页面: <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Namespace="cnblogs.sui" TagPrefix="lab" %>
    无标题页
    
    在构造器中,LabelStyle对基类的BorderColor设置成Color.Red,该值将委托给ControlStyle属性,通过在LabelStyle构造器中
设置基类的BorderColor,ForeColor,把新的值传给ControlStyle属性,它把新的值传给出ControlStyle属性,该属性负责状态管理以
及样式属性的生成,如果没有把改变传到ControlStyle那么重载的样式属性就不会控预期的那样显示。  
六.实现自定义类型化样式
 继承自Style类的类称为类型化样式。Style类可以由控件开发人员来扩展,创建一个自定义类型化样式,它重写或者添加Style类的属性。服务器控件也可以把自定义类型化样式作为ControlStyle属性的类型。例如,Table控件的ControlStyle属性就是TableStyle类型,该类型是扩展的Style,添加了例如CellPadding、CellSpacing和GridLines属性等。在初步了解类型化样式属性的基本概念之后,下面列举了实现类型化样式属性的方法要点。
1、创建一个派生类自System.Web.UI.WebControl.Style的类
2、定义样式为控件提供属性
3、重写Reset方法,
4.      重写AddAttributesToRender方法,产生 HTML和CSS
5.      复制定义的属性或给定样式属性合并
示例:创建一个MyPanel控件,及相关联的类型化样式MypanelStyle,这个MyPanel控件并从WebControl继承,在MyPanelStyle中设置了3个样式属性。
        (1)BackImageUrl,用于指定背景图片的URL;
     (2)HorizontalAlign,用于指定所添加内容的水平对其方式;
     (3)Wrap,用于指定是否允许对所添加的内容换行
效果如下:
  
 
下面是整个的源代码:
控件代码:
控件代码
  1using System;
  2using System.Data;
  3using System.Configuration;
  4using System.Web;
  5using System.Web.Security;
  6using System.Web.UI;
  7using System.Web.UI.WebControls;
  8using System.Web.UI.WebControls.WebParts;
  9using System.Web.UI.HtmlControls;
 10using System.ComponentModel;
 11/**//// 
 12/// MyPanel 的摘要说明
 13/// 
 14/// 
 15namespace cnblogs.sui
 16{
 17
 18    MyPanelStyle#region MyPanelStyle
 19
 20    public class MypanelStyle :Style
 21    {
 22        //定义内部属性
 23        internal const int PROP_BACKIMAGEURL = 1;
 24        internal const int PROP_HORIZOHTALALIGN = 2;
 25        internal const int PROP_WRAP = 3;
 26        构造函数#region 构造函数
 27        public MypanelStyle()
 28        { }
 29        public MypanelStyle(StateBag bag) : base(bag) { 
 30        }
 31        #endregion
 32        属性列表#region 属性列表
 33        [Bindable(true), Description("背影图片的url"), NotifyParentProperty(true)]
 34
 35        //背影图片
 36        public virtual string BackImageUrl {
 37
 38            get {
 39                if (IsSet(PROP_BACKIMAGEURL))
 40                {
 41                    return (string)ViewState["BackImageUrl"];
 42
 43                }
 44
 45                return String.Empty;
 46            }
 47            set {
 48                ViewState["BackImageUrl"] = value;
 49            }
 50
 51        }
 52        //实现行布局属性
 53        [Bindable(true), Category("layout"), DefaultValue(HorizontalAlign.NotSet), NotifyParentProperty(true)]
 54        public virtual HorizontalAlign HorizonalAlign
 55        {
 56            get {
 57
 58                if (IsSet(PROP_HORIZOHTALALIGN))
 59                {
 60                    return (HorizontalAlign)ViewState["HorizontalAlign"];
 61                }
 62                return HorizontalAlign.NotSet;
 63            }
 64
 65            set
 66            {
 67                if (value < HorizontalAlign.NotSet || value > HorizontalAlign.Justify)
 68                {
 69                    throw new ArgumentOutOfRangeException("value");
 70                }
 71                ViewState["HorizontalAlign"] = value;
 72            }
 73        }
 74        //实现IsEmpty
 75        protected new internal bool IsEmpty  //使用 new 修饰符显式隐藏从基类继承的成员
 76        {
 77
 78            get {
 79
 80                return base.IsEmpty && !IsSet(PROP_BACKIMAGEURL) && IsSet(PROP_HORIZOHTALALIGN) && IsSet(PROP_WRAP);
 81
 82            }
 83        }
 84        //实现换行
 85        [Bindable(true), Category("layout"), DefaultValue(true), NotifyParentProperty(true)]
 86        public virtual bool Wrap {
 87
 88            get {
 89
 90                if (IsSet(PROP_WRAP))
 91                {
 92
 93                    return (bool)ViewState["Wrap"];
 94    
 95                }
 96                return true;
 97            }
 98            set {
 99
100                ViewState["wrap"] =value;
101            }
102        }
103        #endregion
104        internal bool IsSet(int propNumber)
105        {
106
107            string key = null;
108            switch (propNumber)
109            {
110
111                case PROP_BACKIMAGEURL: key = "BackImageUrl";
112                    break;
113                case PROP_HORIZOHTALALIGN: key = "HorizontalAlign";
114                    break;
115                case PROP_WRAP: key = "Wrap";
116                    break;
117            }
118            if (key != null)
119            {
120                return ViewState[key] != null;
121
122            }
123            return false;
124        }
125        //重写AddAttributesToRender方法
126        public override void AddAttributesToRender(HtmlTextWriter writer, WebControl owner)
127        {
128            if (IsSet(PROP_BACKIMAGEURL))
129            {
130                   string s=BackImageUrl;
131                   if (s.Length > 0)
132                   {
133
134                       if (owner != null)
135                       {
136                           s = owner.ResolveUrl(s);
137                       }
138                       writer.AddStyleAttribute(HtmlTextWriterStyle.BackgroundImage, "url(" + s + ")");
139
140                   }
141            }
142            if (IsSet(PROP_HORIZOHTALALIGN))
143            {
144                HorizontalAlign hAlign = this.HorizonalAlign;
145
146                if (hAlign != System.Web.UI.WebControls.HorizontalAlign.NotSet)
147                {
148                    TypeConverter hac = TypeDescriptor.GetConverter(typeof(HorizontalAlign));
149                    writer.AddAttribute(HtmlTextWriterAttribute.Align, hac.ConvertToInvariantString(hAlign));
150                }
151            }
152
153            if (IsSet(PROP_WRAP))
154            {
155                bool wrap = Wrap;
156                if (!Wrap)
157                {
158                    writer.AddAttribute(HtmlTextWriterAttribute.Nowrap, "nowwrap");
159                }
160            }
161
162
163            base.AddAttributesToRender(writer, owner);
164        }
165        public override void CopyFrom(Style s)
166        {
167            if (s != null)
168            { 
169             base.CopyFrom(s);
170             if (s is MypanelStyle)
171             {
172
173                 MypanelStyle mps = (MypanelStyle)s;
174                 if (!mps.IsEmpty)
175                 {
176                     if (mps.IsSet(PROP_BACKIMAGEURL))
177                         this.BackImageUrl = mps.BackImageUrl;
178                     if (mps.IsSet(PROP_HORIZOHTALALIGN))
179                         this.HorizonalAlign = mps.HorizonalAlign;
180                     if (mps.IsSet(PROP_WRAP))
181                         this.Wrap = mps.Wrap;
182
183                 }
184             }
185            }
186           
187        }
188
189        // 重写MergeWith方法
190        public override void MergeWith(Style s)
191        {
192            if (s != null)
193            {
194                if (IsEmpty)
195                {
196                    CopyFrom(s);
197                    return;
198                }
199                base.MergeWith(s);
200                if (s is MypanelStyle)
201                {
202                    MypanelStyle mps = (MypanelStyle)s;
203                    if (!mps.IsEmpty)
204                    {
205                        if (mps.IsSet(PROP_BACKIMAGEURL) && !this.IsSet(PROP_BACKIMAGEURL))
206                            this.BackImageUrl = mps.BackImageUrl;
207                        if (mps.IsSet(PROP_HORIZOHTALALIGN) && !this.IsSet(PROP_HORIZOHTALALIGN))
208                            this.HorizonalAlign = mps.HorizonalAlign;
209                        if (mps.IsSet(PROP_WRAP) && !this.IsSet(PROP_WRAP))
210                            this.Wrap = mps.Wrap;
211                    }
212                }
213            }
214        }
215
216        //重写Reset方法
217        public override void Reset()
218        {
219            base.Reset();
220            if (IsEmpty) return;
221            if (IsSet(PROP_BACKIMAGEURL))
222                ViewState.Remove("BackImageUrl");
223            if (IsSet(PROP_HORIZOHTALALIGN))
224                ViewState.Remove("HorizontalAlign");
225            if (IsSet(PROP_WRAP)) ViewState.Remove("Wrap");
226        }
227
228        public void Method()
229        {
230            throw new System.NotImplementedException();
231        }
232
233
234
235
236
237
238    }
239    #endregion
240
241    [ ParseChildren(false), PersistChildren(true) ]
242 [ToolboxData("<{0}:panel runat=server>{0}:panel>")]
243
244    public class MyPanel : WebControl
245    {
246        // 定义构造函数
247        public MyPanel() : base(HtmlTextWriterTag.Div) { }
248        // 实现属性BackImageUrl
249        [Bindable(true)]
250        [Category("Appearance")]
251        [DefaultValue("")]
252        public virtual string BackImageUrl
253        {
254            get
255            {
256                if (ControlStyleCreated)
257                {
258                    return ((MypanelStyle)ControlStyle).BackImageUrl;
259                }
260                return String.Empty;
261            }
262            set
263            {
264                ((MypanelStyle)ControlStyle).BackImageUrl = value;
265            }
266        }
267        // 实现属性HorizontalAlign
268        [Bindable(true)]
269        [Category("Layout")]
270        [DefaultValue("")]
271        public virtual HorizontalAlign HorizontalAlign
272        {
273            get
274            {
275                if (ControlStyleCreated)
276                {
277                    return ((MypanelStyle)ControlStyle).HorizonalAlign;
278                }
279                return HorizontalAlign.NotSet;
280            }
281            set
282            {
283                ((MypanelStyle)ControlStyle).HorizonalAlign = value;
284            }
285        }
286        // 实现属性Wrap
287        [Bindable(true)]
288        [Category("Layout")]
289        [DefaultValue("")]
290        public virtual bool Wrap
291        {
292            get
293            {
294                if (ControlStyleCreated)
295                {
296                    return ((MypanelStyle)ControlStyle).Wrap;
297                }
298                return true;
299            }
300            set
301            {
302                ((MypanelStyle)ControlStyle).Wrap = value;
303            }
304        }
305        protected override Style CreateControlStyle()
306        {
307            return new MypanelStyle(ViewState);
308        }
309
310    }
311}
Aspx页面代码:
aspx页面
 1<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>
 2<%@ Register Namespace="cnblogs.sui" TagPrefix="panel" %>
 3
 4
 5
 6
 7    无标题页
 8
 9
10    
37
38
39类视图:

解说一下:MyPanel类没有什么可说的,主要解析一下MypanelStyle类。继承于Style,是创建类型化样式的关键。定义一些字段与属性没有什么可说的,主要的说一下其中的方法。
1、重写了AddAttributesToRender方法。当控件呈现时生成相关的html和css。
2、重写了CopyFrom,MergeWith,Reset方法。
CopyFrom 与MergeWith是为了复制给定的MypanelStyle或把给定的MypanelStyle与自身合并。
重写Reset是为了删除添加到期ViewState 中的属性,