今天你写控件了吗?----ASP.net控件开发系列

今天你写控件了吗?----ASP.net控件开发系列之开篇

    早段时间,同事之间很流行打CS,所以那段时间的问候变成了“今天你杀XX了吗?”
控 件开发也许是程序员最能发掘自己创造力的一件事情吧?控件开发不仅要求你对当前的开发有一个较全面和略深的了解,而且也能促使你去了解开发环境的底层的东 西。比方说我在开发控件的过程中就对reflect和MSIL产生了较深的兴趣,也许,在以后的一段时间里,我会尝试去做一个reflector,哈哈, 很多事情都很难说地~
    “今天你写控件了吗?”,也许在将来会成为中国.net程序员的一个日常用语,哈哈,很多事情都很难说地~不好意思,重复了一下,毕竟,在.net的框架 下一些简单的控件的开发相对来说,是·很·容易的。.net框架已经为你做了大部分的事(当然,我这种说法是有误的,创新永远是第一位的。),我所做,只 是想凭自己三寸烂舌和一股热情,告诉大家,那个真的很容易。
现在,你有信心了吗?

    在这开篇,我还想和大家探讨几个问题?

    1.ASP.net是ASP+吗?
     记得有这么一句话,微软喜欢给一些换汤不换药的东西换一个全新的名字,让人认为是一种全新的技术,而有些完全不同的东西却整一个很相像的名字, ASP.net和ASP就是这样,ADO.net和ADO.net更是这样。在这里,我想说的是,ASP.net和ASP是很不相同的。ASP是建立在 WINDOW DNA上的(这句也许有误,我对旧的技术没什么底蕴),以解释为执行方式的,而ASP.net是建立在.net framework这一全新的东东上的,是先编译后运行的,所以ASP.net的效率更高,而且有.net framework的支持,可以做很多很多的功能,而且很方便,你不必在COM的世界为了一个功能大海捞针,在一个统一的.net环境中写写代码就OK 了,同时,Web serviceS做为它的同胞,和asp.net有很大的相通性,你可以很方便的同时为Internet提供基于浏览器和各种客户端GUI程序的服务,还 有remoting.....爽歪了啊!

    2.ASP.net都做了什么?
     ASP.net是怎么生成一个个活蹦乱跳的鲜活网页的呢?IIS做为一个HOST,为一个个ASP.net程序创建AppDomain而不是一个个 Process,每个ASP.net程序在第一次运行时被编译为机器码,为客户的Request生成Response。我们在制作网页的时候,.aspx 文件和.aspx.cs文件好像各占半壁江山,而其实的页的生成过成中,.aspx文件中的东西只是Render方法的一串Write调用。

    3.ASP.net开发,我们的重点该放在哪呢?
     是啊,我们的重点该放在哪哟?我重点培养自己哪方面的技能呢?在上一点中,我说.aspx文件只不是后台程序的一个方法(Render)中的部分而已,那 么意思是不是前台的东西就只是一个狗屁呢?对这个观点,我是坚绝反对的,理由有:第一、ASP.net最终还是用HTML、Script、CSS来实现界 面,完成功能。一个Render而已,可这个Render可了不得啊,ASP.net中大部分的东西最终不就是为了这个Render吗?第二、.net框 架已经为我们做了太多太多的东西,我们可以很舒服的完成我们需要的功能,用System.Drawing画出有水印的图,用System.Data玩把戏 般的操作数据,正则?XML?......一切都有一堆积木在那等着你堆了。所以,我们前进的道路很清晰的两路,就是,用那些老掉牙的 DHMTL/JS/CSS技术完成你的创意,另一方法,努力学习软件开发的方法论,什么模式、什么UML、什么框架、什么用例、什么MDA、什么 AOP......不好意思,接不下去了,吹概念不是我的长处。而且在控件开发中,你没有DHTML这些东西的基础,而以为对现有控件的深度发展就能做出 一个很成功的控件和创意出来?基本上......这个(读的时候这里顿一下)很难!

    4.Attribute?
     Attribute? 这个是.net中很重要的概念,在控件开发在尤其重要。很多书都说Attribute和Property不好分别,我的理解很简单,Property是内 在的属性,Attribute是相对“外在”的东西,举个例子,你的肚腩很大这是Property,然后你穿一件很宽松的衣,这是Attribute,当 然这个解释是绝对不正确的,也许“你肚腩很大”这句描述性的话是Attribute那来得正确,不过,这个有助于你理解两者的区别是如此的大,那么这两者 到底是什么呢?其实大家都知道了,我这里再八婆一下。编译器在把你的代码整成一个Assemble时,把Method、Property这些东西整成IL 指令,同时,它会弄出一份描述你的类,方法,属性,字段等等的各种信息的MetaData,所以,在IDE中,我写一个XX.在打下这个点后,后面一堆让 我爱得不得了的智能完成东东就出来了,这些就是MetaData和Reflect的功劳,此外还有VS.net的对象浏览器,某些兄弟偷看我发布的控件的 内部代码的工具,都得益于这个东东,而且IDE也利用反射读取MetaData来决定怎样在设计器如何让用户和控件交互。而!Attribute就是我们 操作MetaData的工具。

    ......还有呢?暂时没想到,呵呵。

    所以,想尝试做控件的兄弟,你不仅要有信心,你还要准备一点HTML/JS/CSS之类的基础和对Attribute等.net的特性的一些了解。你准备好了吗?

    也许是年龄的原因,所以我这个人现在做事一般不是很沉稳,所以你在以上一段吹水中找出几个错误我一点也不会吃惊,同时,我很感激你。

 

今天你写控件了吗?----ASP.net控件开发系列之(二)

“生死有序”
“装装孙子”
 上篇文章《开篇》说了不少空洞的理论,这篇文章我还是先说说“大而化之”的东西:1、ASP.net控件(包括页面本身)的生命期的细节;2、如何开始一个控件的编写。
“生死有序”
 ASP.net处理程序在接收到一个用户的页面请求后,它是如何变戏法把一个鲜活的页面呈现给客户端的呢?它都做了哪些事?按什么顺序做的?
  要说明这个问题,我们首先要明白,一个页面它本身也是一个Control。从设计模式的角度讲,页面模型是一个“合成模式(Composite)”,它本 身是一棵由多层控件组成的结构树,顶层是Page,以下有叶有树枝,叶是不再包涵子控件的控件,枝是又包涵子控件的控件,每一层控件的生成都会调用一个生 成子控件的方法,父控件调用子控件的生成方法,子又调用孙的,如此递归,保证页面中所有有效的(一般情况下是Visible=true)控件都得到生成过 程处理,(有关设计模式的东西大家可以查看吕震宇老师在博客园的设计模式系列文章,该系列文章是对阎博士《Java与模式》一书(88元,很厚)的提炼与 加工,至少可用来做设计模式浅层面速成的教材),而每个控件在生命周期大体都包括以下几个步骤:



1.实例化(Instantiate)
我们写控件一般不要接触此活动。
2.初始化(Initialize)
同上。
3.跟踪视图(Tracking View State)*
这个比较重要,涉及到视图状态,一般情况下不必重载此方法。
4.加载视图状态(Load view state)*
只会在回传过程中调用此方法,用法同上。
5.加载回传数据(Load postback data)*
如果你的控件生成之后要和客户端交互,那么这个方法就很重要,只会在回传过程中调用此方法。
6.开始载入(Load)
这个活动一般只是Page的OnLoad才会要去管它,我们写控件一般不要接触此方法。
7.有修改(Raise changed events)*
控件生成后,数据被客户端更改过,和加载回传数据是一路的。
8.回传事件处理(Raise postback event)*
一般用于实现IPostBackEventHandler接口的控件的把客户端事件转化成服务器端事件。只用于回传过程。
9.生成预处理(PerRender)**
生成前期工作,这个是很重要的一个过程,通过重载OnPreRender方法实现自定义。
10.保存视图状态(Save view state)*
如果所以信息都是用ViewState[xxx]这种方式来保存,不必重载,只有自定义视图状态管理时才重载此方法,当然,这里做了手脚,LoadViewState也就一定要和这里的Save方法配套。
11.生成(Render)***
这个是主角,控件成生什么东东基本就由这里管了。
12.卸载(Unload)
13.释放(Dispose)
了解控件的生命周期的细节对于我们自定义控件的各个部分以及调试控件,排除控件Bug都是至关重要的。
当然这此过程中有些活动是我们要特别重视的,我在这些活动的后面加了*号。
“装装孙子”
  还记得我在上一篇文章中说.net框架已经为我们做了大部分的事情吗?确实,我们写一个控件不是平地起高楼,我们已经有了很多成品和半成品了。很多情况 下,为了快速开发一个控件,你甚至可以使自己的控件直接继承于TextBox、Button之类具体的控件,略做修改,就可实现自己的功能。如果没有这样 的好事,那你也最好考虑从以下两个类继承:System.Web.UI.Control、 System.Web.UI.WebControls.WebControl,第一个类适合于派生生成不是可视HTML对象的控件,如生成< meta><xml>之类内容的控件,第二个类适合于生成各种HTML对象,它已经实现了基本的样式管理、HTML标签生成等等功能。

下一篇打算和大家探讨一下控件属性的方方面面,包括属性与HTML样式的aspx文件文本的关系,属性在VS中的高级编辑方法和在IDE中各种交互方式的定制等等

 

 

 

今天你写控件了吗?----ASP.net控件开发系列(三)

属性全接触(一)

本系列上篇文章有几位抬爱,鼓励了几句,所以劲头又足了,这不,这篇文章就出得快了,:)
希望能继续得到鼓励和指正。
这次我们来探讨下控件开发中的属性设计的方方面面,属性本是各种.net下语言的最基本语法,但控件做为一种提供给程序员二次开发的发布件,其功能的强大与使用的灵活都离不开良好的属性设计,所以我觉得属性设计是控件开发中第一座要打下的堡垒。
首先我们来看一段.aspx文件中的HTML样式的代码:

< asp:DropDownList  id ="DropDownList1"  runat ="server"  Font-Bold ="True" >
 
< asp:ListItem  Value ="1" > 1 </ asp:ListItem >
 
< asp:ListItem  Value ="2" > 2 </ asp:ListItem >
</ asp:DropDownList >


 

这种代码大家都写过无数次了,不过,现在我们要从另一种角度来分析这段代码:
DropDownList控件在和用户交互时,它没有把它的属性一股脑放在一起来表示出来,而是分为以下几种情况:
1.像ID、Runat、Font-Bold这些,放在<>标记里;
2.Font-Bold不同于其它,它有一个短横分隔成两部分,这种表示方式不是Font-Bold是一个属性的名称,而是说明它是一个子属性,是Font属性(Font类)的Bold属性,对应.cs文件的写法是:Font.Bold;
3.ListItem放在了DropDownList的一双<>标记的中间,而不是标记之类,同样1、2也放在了ListItem的中间。
下面我们来分别看看这几种属性记录方式是怎么实现的。
有一个Attribute来做这件事件PersistenceMode(System.Web.UI.PersistenceModeAttribute)。
它有以下几个值供选择:
Attribute(default)属性保存在控件的标签里;默认值
EnCodedInnerDefaultProperty 属性是编码的HTML,放在控件标签对的中间保存,以上的ListItem就是这样保存Text属性的(1、2);

 

InnerDefaultProperty 属性保存在控件标签对的中间,以上的DropDownList控件的Items属性就是这样保存的(就是那堆ListItem);

 

InnerProperty 属性和其它属性一起保存为控件标签对中的嵌套内容,DataGrid用这种方式把一堆东西包在标签里,这时,属性要用标签声明,内含属性值。

[PersistenceMode(PersistenceMode.InnerProperty)]
public   virtual  TableItemStyle ItemStyle
{
      
get
      
{
            .
      }

}


第二个议题,属性的持久性
众 所周知,网页是基于无状态机制的,就是说,页面Response后就不管理保存页面内容的状态,ASP.net提供ViewState机制来保存页面的状 态,ViewState是实现方式是送出页面中的一个名为__ViewState的input type='hidden',也就是说它和Session不同,它保存在页面中......不废话了,反正我们要保存控件的状态(就是它的各个属性),而 不至于在回传的过程中丢失,我们采用的方案就是ViewState,下面简单的举个例子说明ViewState在Property中的用法

  [
  DefaultValue(
2 ),
  TypeConverter(
typeof (Int32Converter)),
  Category(
" Behavior " ),
  Description(
" Duration of complete once transition.(s) "
  ]
  
public   int  Duration
  
{
   
get
   
{
    
object b = ViewState["Duration"];
    
return (b==null)?2:(int)b;
   }

   
set
   
{
    
if(value < 1)
    
{
     
throw new ArgumentOutOfRangeException("Duration");
    }

    ViewState[
"Duration"= value;
   }

  }


像这种方式直接使用ViewState是最简便的,我们不必改写LoadViewState、SaveViewState方法,至于怎么自定义ViewState管理,在后面的文章中将有介绍。

关于属性的东西太多了,这次就写到这,下篇将写写怎样订制属性与IDE的交互。

 

今天你写控件了吗?----ASP.net控件开发系列(三)

属性全接触(一)

本系列上篇文章有几位抬爱,鼓励了几句,所以劲头又足了,这不,这篇文章就出得快了,:)
希望能继续得到鼓励和指正。
这次我们来探讨下控件开发中的属性设计的方方面面,属性本是各种.net下语言的最基本语法,但控件做为一种提供给程序员二次开发的发布件,其功能的强大与使用的灵活都离不开良好的属性设计,所以我觉得属性设计是控件开发中第一座要打下的堡垒。
首先我们来看一段.aspx文件中的HTML样式的代码:

< asp:DropDownList  id ="DropDownList1"  runat ="server"  Font-Bold ="True" >
 
< asp:ListItem  Value ="1" > 1 </ asp:ListItem >
 
< asp:ListItem  Value ="2" > 2 </ asp:ListItem >
</ asp:DropDownList >


 

这种代码大家都写过无数次了,不过,现在我们要从另一种角度来分析这段代码:
DropDownList控件在和用户交互时,它没有把它的属性一股脑放在一起来表示出来,而是分为以下几种情况:
1.像ID、Runat、Font-Bold这些,放在<>标记里;
2.Font-Bold不同于其它,它有一个短横分隔成两部分,这种表示方式不是Font-Bold是一个属性的名称,而是说明它是一个子属性,是Font属性(Font类)的Bold属性,对应.cs文件的写法是:Font.Bold;
3.ListItem放在了DropDownList的一双<>标记的中间,而不是标记之类,同样1、2也放在了ListItem的中间。
下面我们来分别看看这几种属性记录方式是怎么实现的。
有一个Attribute来做这件事件PersistenceMode(System.Web.UI.PersistenceModeAttribute)。
它有以下几个值供选择:
Attribute(default)属性保存在控件的标签里;默认值
EnCodedInnerDefaultProperty 属性是编码的HTML,放在控件标签对的中间保存,以上的ListItem就是这样保存Text属性的(1、2);

 

InnerDefaultProperty 属性保存在控件标签对的中间,以上的DropDownList控件的Items属性就是这样保存的(就是那堆ListItem);

 

InnerProperty 属性和其它属性一起保存为控件标签对中的嵌套内容,DataGrid用这种方式把一堆东西包在标签里,这时,属性要用标签声明,内含属性值。

[PersistenceMode(PersistenceMode.InnerProperty)]
public   virtual  TableItemStyle ItemStyle
{
      
get
      
{
            .
      }

}


第二个议题,属性的持久性
众 所周知,网页是基于无状态机制的,就是说,页面Response后就不管理保存页面内容的状态,ASP.net提供ViewState机制来保存页面的状 态,ViewState是实现方式是送出页面中的一个名为__ViewState的input type='hidden',也就是说它和Session不同,它保存在页面中......不废话了,反正我们要保存控件的状态(就是它的各个属性),而 不至于在回传的过程中丢失,我们采用的方案就是ViewState,下面简单的举个例子说明ViewState在Property中的用法

  [
  DefaultValue(
2 ),
  TypeConverter(
typeof (Int32Converter)),
  Category(
" Behavior " ),
  Description(
" Duration of complete once transition.(s) "
  ]
  
public   int  Duration
  
{
   
get
   
{
    
object b = ViewState["Duration"];
    
return (b==null)?2:(int)b;
   }

   
set
   
{
    
if(value < 1)
    
{
     
throw new ArgumentOutOfRangeException("Duration");
    }

    ViewState[
"Duration"= value;
   }

  }


像这种方式直接使用ViewState是最简便的,我们不必改写LoadViewState、SaveViewState方法,至于怎么自定义ViewState管理,在后面的文章中将有介绍。

关于属性的东西太多了,这次就写到这,下篇将写写怎样订制属性与IDE的交互。

 

今天你写控件了吗?----ASP.net控件开发系列(五)

TypeConverter

     在本系列的上篇文章中,和大家控讨了控件开发与propertyGrid的关系,不知现在大家现在对propertygrid有没有一个较全面的了解,也不知大家有没有做个工程,把propertyGrid拉进去鼓捣鼓捣?

“另起炉灶”


     现在我们来思考一个问题:假于,propertygrid没有把属性和事件分成两个分页来显示,会产生什么效果?
     那还用说,太乱了。
     那如果你设计的控件有很多的属性,而一些关联性很强,或都是操作一个方面的,那么我们可以把它们分门别类,摆到一起,怎么做呢?
 我们可以给这个控件类指定以下Attribute:

 [PropertyTab( typeof (YourPropertyTab), PropertyTabScope.Component)]
 
public   class  YourControlClass
 
{
  
 }

其中,前面一个参数指定处理PropertyTab的类,后一个参数说明要应用在什么时候,Component为当前组件专用,Document当前文档专用,Global只能由父给件显式去除,Static不能去除。
 

internal   class  YourPropertyTab : PropertyTab 
 
{
  
internal YourControlType target;

    
public override string TabName 
  
{
   
get 
   
{
    
return "选项卡的名字";
   }

  }

  
public override Bitmap Bitmap 
  
{
   
get
   
{
    
return new Bitmap(base.Bitmap, new Size(16,16));//这里是使用保存为嵌入资源的和YourPropertyTab类同名的.bmp文件
   }

  }


  
public override bool CanExtend(object o) 
  
{
   
return o is YourControlType;//什么时候用这个Tab
  }


          
public override PropertyDescriptorCollection GetProperties(object component, Attribute[] attrs) {
              
return GetProperties(null, component, attrs);
         }


 
  
/// 主要的逻辑.  在这里定义如何实现分Tab显示
  
  
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object component, Attribute[] attrs)
  
{
   YourControlType uc 
= component as YourControlType;

   
if (uc == null
   
{
    
//以下代码实现不是YourControlType时,使用本身类型的逻辑。 
    TypeConverter tc = TypeDescriptor.GetConverter(component);
    
if (tc != null
    
{
     
return tc.GetProperties(context, component, attrs);
    }

    
else 
    
{
     
return TypeDescriptor.GetProperties(component, attrs);
    }

   }

    
   target 
= uc;
   ArrayList propList 
= new ArrayList();
   
//..建立一个属性List
   propList.Add(new YourPropertyDescriptor(this);
   PropertyDescriptor[] props 
= (PropertyDescriptor[])propList.ToArray(typeof(PropertyDescriptor));    
   
return new PropertyDescriptorCollection(props);
  }

  
//我们还要建立自定义的PropertyDescriptor供GetProperties方法使用。
  private class YourPropertyDescriptor : PropertyDescriptor 
  
{
   YourPropertyTab owner;
   
public NumPointsPropertyDescriptor(YourPropertyTab owner) ://注意这里的参数
    base("PropertyName"new Attribute[]{CategoryAttribute.Data, RefreshPropertiesAttribute.All})//第二个参数是指定属性改变时,与         //其它属性的联动,整个属性页是否刷新,All-刷新,Default-不,Repaint-重画属性窗口
   {
    
this.owner = owner;     
   }


   
public override Type PropertyType//属性的类型 
   {
    
get 
    
{
     
return typeof(int);
    }

   }

   属性关联对象是什么类型
   
public override Type ComponentType
   
{
    
get 
    
{
     
return typeof(YourControlType);
    }

   }


   
public override bool IsReadOnly {get{return false;}}
  
   
public override object GetValue(object o) //和关联对象的什么属性相关
   {
    
return ((YourControlType)o).Proterty_1;
   }


   
public override void SetValue(object o, object value) //和关联对象的什么属性相关
   {
    YourControlType uc 
= o as YourControlType;
    uc.Property_1 
= (int)value;    
   }


   
public override void ResetValue(object o){}//望文生义


   
public override bool CanResetValue(object o) //望文生义
   {
    
return false;
   }

   
///Does this property participate in code generation?
   public override bool ShouldSerializeValue(object o) 
   
{
    
return false;
   }

  }

 
 }



类型转换器


public   class  YourConverter : ExpandableObjectConverter  {
 
do something,example: override CovertTo function
    }

public   class  YourControl
{
 [TypeConverter(
typeof(YourConverter))]
 
public YourPropertyClass ExpandableProperty
 
{
  .
 }

}

 6、为属性提供一个设计期值的下拉列表,正如我上篇文章所述。
 7、 System.Web.UI.WebControls namespace & System.ComponentModel namespace下已经有了这些转换器:UnitConverter,BooleanConverter,CharConverter, EnumConverter,CollectionConverter,ArrayConverter,BaseNumberConverter,ByteConverter, ReferenceConverter,CultureInfoCOnverter,DateTimeConverter,DecimalConverter, DoubleConverter,ExpandableObjectConverter,GuidConverter,Int16(32/64)Converter, sByteConverter,SingleConverter,StringConverter,TimeSpanConverter,TypeListConverter, UInt16(32/64)Converter,ObjectConverter,PropertyConverter,DataBindingCollectionConverter, DataFieldConverter,DataMemberConverter,DataSourceConverter,FontNamesConverter, FontUnitConverter,TargetConverter,ValidatedControlConverter,CursorConverter...... (WinForm的我就不列出来了)

下面,我们就重点来看看如何实现不同类型和String怎么样来转换。
 下面这个实例应该就得达到这个目的。

     public   class  YourTypeConverter : TypeConverter  {//类型转换器需直接或间接继承自TypeConverter类,
      
//上面例子继承自ExpandableObjectConverter也是间接继承TypeConverter类

        
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {//源类型能不能转换成转换器关联类型
            if (sourceType == typeof(string)) {
                
return true;
            }

            
return base.CanConvertFrom(context, sourceType);//我们实现了和String的转换,所以为String的话为True,然后,简单调用基类的方法就行。
        }


        
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {//string能不能转换成目标类
            if ((destinationType == typeof(string)) ||
                (destinationType 
== typeof(InstanceDescriptor))) {//System.ComponentModel.Design.Serialization.InstanceDescriptor
        
//提供创建对象的实例所需的信息,此处返回转换器关联类型的实例
                return true;
            }

            
return base.CanConvertTo(context, destinationType);//只有实现了ConverTo目标类型才能为TRUE(我们在后面实现了string),如果String可以转换成目标类型,        //那么也会为TRUE,因为这里我们可以将之转成String,再调用String的转换。
        }


        
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {//从一种类型转换成我们的类型
            if (value == null{
                
return new YourType();//空值来,默认实现
            }


            
if (value is string{//如果是string,定制转化逻辑
                string s = (string)value;
                
if (s.Length == 0{
                    
return new YourType();
                }

  
//以下的例子实现"20,30"转换成new YourType(20,30),分两步,分隔,变Int
                string[] parts = s.Split(culture.TextInfo.ListSeparator[0]);

                
if (parts.Length != 2{
                    
throw new ArgumentException("Invalid YourType""value");
                }


                TypeConverter intConverter 
= TypeDescriptor.GetConverter(typeof(Int32));
                
return new YourType((int)intConverter.ConvertFromString(context, culture, parts[0]),
                    (
int)intConverter.ConvertFromString(context, culture, parts[1]));
            }


            
return base.ConvertFrom(context, culture, value);//还是要调用一下基类的方法。
        }


        
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {//我们的类型转换成其它类型
            if (value != null{//先确定源类型是不我们的要转的类型。
                if (!(value is YourType)) {
                    
throw new ArgumentException("Invalid YourType""value");
                }

            }


            
if (destinationType == typeof(string)) {//转成string的逻辑
                if (value == null{
                    
return String.Empty;
                }


                YourType yourType 
= (YourType)value;
  
//以下的例子将yourType的两个int属性变成"属性值1,属性值2"的字符串。
                TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(Int32));
                
return String.Join(culture.TextInfo.ListSeparator,
                    
new string[] {
                                     intConverter.ConvertToString(context, culture, yourType.Value_1),
                                     intConverter.ConvertToString(context, culture, yourType.Value_2)
                                 }
);
            }

            
else if (destinationType == typeof(InstanceDescriptor)) {
  
//以下的例子实现如果源类型本身就是这个类型时,我们的处理逻辑,
  
//这里就用了System.Reflection namespace下的方法来分别调用YourType的无参构造函数和有两个参数的构造函数。
                if (value == null{
                    
return null;
                }


                MemberInfo mi 
= null;//通过对YourType MetaData的访问得到它的构造函数
                object[] args = null;

                YourType yourType 
= (YourType)value;
                
if (yourType.IsEmpty) {
                    mi 
= typeof(YourType).GetConstructor(new Type[0]);
                }

                
else {
                    Type intType 
= typeof(int);
                    mi 
= typeof(YourType).GetConstructor(new Type[] { intType, intType });
                    args 
= new object[] { yourType.Value_1, yourType.Value_2 };
                }


                
if (mi != null{
                    
return new InstanceDescriptor(mi, args);//根据选择的构造子和参数建立实例
                }

                
else {
                    
return null;
                }

            }


            
return base.ConvertTo(context, culture, value, destinationType);//还是调用基类的方法。
        }

    }


大家看到在上面的代码中我们老是在override方法中最后调用基类的方法,这么做是省去实现转换失败时的返回等逻辑的实现,避免出现莫名其妙的错误。
现 在,我们回过头来看看上面的那张图,我们使用了继承自ExpandableObjectConverter的转换器,而且,我们可以看到,我们既可以分开 来为属性的子属性赋值,也可以总的为属性赋值,如图中所示,"100,50,50"这个字符串值发映的就是被TypeConverter转换了的值,在需 要的时候,TypeConverter也会将它转换为YourType.Property_1= 100,YouType.Property2=50......这样的实际类型的实例及赋予它正确的值。

 

今天你写控件了吗?----ASP.net控件开发系列(六)

UITypeEdit

“我要红桃”


  假如,你现在在做一个“扑克”控件,扑克牌有个属性--花色,你想在用户选择花色这个属性后,属性窗口呈现的不仅仅是文字,还有一个小小的花色图标来表示 花色,“红桃”就有个小“红桃”图标在前面显示,“黑桃”就有个“黑桃”图标在前面显示,就像你选择其它控件的BackColor时,颜色前还有个小方色 块来表示选定的颜色,多体贴人的设计啊。
 现在,我们就来做这件事:

public   class  Squeezer
{
 .
 
 
public CardTypes CardType
 
{
  
 }

}

[Editor(
typeof (CardTypesEditor),  typeof (System.Drawing.Design.UITypeEditor))]
public   class  CardTypes
{
 ..
}


 
public   class  CardTypesEditor : UITypeEditor
 
{
  
public override bool GetPaintValueSupported(ITypeDescriptorContext context) 
  
{
   
return true;//支持画小图
  }


  
public override void PaintValue(PaintValueEventArgs pe) //定义根据值画小图的逻辑
  {
   
string bmpName = null;
   CardTypes C 
= (CardTyes)pe.Value;
   
switch(C.Value)
   
{
    
case CarderTypes.HongTao:
     bmpName 
= "红桃.bmp";//图片必须是嵌入的资源,大小为16*16,类型为BMP
     break;
    
   }


   Bitmap b 
= new Bitmap(typeof(GradeEditor), bmpName);

   pe.Graphics.DrawImage(b, pe.Bounds);

   b.Dispose();   
  }

 }


在上面的代码中,我们通过EditorAttribute来使花色类和一个Editor关联,再通过这个Editor来实现画示意小图的功能。

好了,现在你的创造力可能又在鼓动你思考一个新问题了,我不想让让用户仅仅通过一个简单的只呈现值的下拉列表(通过EnumConverter实现的)来选择属性的值,我想实现像BackColor、Dock这样的非常友善的交互给用户使用,好吧,我们来进入下一步。
首 先我们要制做一个合适的小窗口(CardTypesEditorControl)来定制交互时的界面,这个窗体继承自 System.Window.Forms.UserControl或System.Window.Forms.From,总之它就是一个WinFrom窗 体,这个窗体怎么做,我这里就不展开论述了,只是你要在这个窗体类中聚合(不是组合,这里是引用,由Editor传过来)一个 IWindowsFormsEditorService,已便更好的交互,并且,能控件何时关闭打开的这个下列式的窗口,比如说在鼠标按钮的Up事件中 edSvc.CloseDropDown(),这样用户点击鼠标进行了选择之后就能关闭窗体,返回值。
好,我们来看我们的Editor怎么扩充:

  CardTypes target;
  CardTypesEditorControl ui;
  
  
public  CardTypesUIEditor(CardTypes target)
  
{
   
this.target = target;
  }

  
// 通过Editor能到用户操作的值的逻辑实现
   public   override   object  EditValue(ITypeDescriptorContext context, IServiceProvider sp,  object  value) 
  
{
   
// 得到IDE的交互服务
   IWindowsFormsEditorService edSvc = (IWindowsFormsEditorService)sp.GetService(typeof(IWindowsFormsEditorService));

   
if (edSvc == null
   
{
    
return value;
   }


   
if (ui == null
   
{
    ui 
= new CardTypesEditorControl();//建立属性操作窗体实例
   }

   
   ui.SelectedCardTypes 
= (CardTypes)value;//原始值,SelectedCardTypes属性只是一个例子,你可以用任意实现自己的逻辑。
   ui.EditorService = edSvc;//传服务过去,见代码上面的说明
   ui.Target = (CardTypes)context.Instance;//得到连接对象的实例
   edSvc.DropDownControl(ui);//把窗体显示为一个下接式窗体,可选的值还有edSvc.ShowDialog(ui),
      
//这样的话以一个弹出窗体的形式显示窗体,也许你更喜欢这种方式。
  
   
return ui.SelectedCardTypes;//我们在窗体逻辑中更改SelectedCardTypes值,在操作窗体关闭时得到这个值。
  }

  
// 窗口出现样式,可选值还有Modal,在属性后出出现一个省略号,点击弹出模式窗体或有窗口的对话框,就像CollectionEditor,
  
// None,不出现任何操UI,老老实实填值
   public   override  System.Drawing.Design.UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) 
  
{
   
return System.Drawing.Design.UITypeEditorEditStyle.DropDown;
  }


呵呵,是不是现在有得花样给你玩了?
在EditValue 方法中用一句return UrlBuilder.BuildUrl( (IComponent) context.Instance, null, (string) value, Caption, Filter,Options)你就可以做出一个URL编辑器;用ColorEditor.ColorUI colorUI = new ColorEditor.ColorUI(this);colorUI.Start(edSvc, value);edSvc.DropDownControl(colorUI);这样的几句话就能实现一个颜色选择交互界面。(注意代码没有处理null 等异常情况)

 

今天你写控件了吗?----ASP.net控件开发系列(七)

ComponentEditor

 “第二选择”

 上篇中,关于Editor说了那么多,完了吗?没有,上篇仅仅介绍了操作属性的UITypeEditor而已。还记得DataGrid的属性窗口的下方的“属性生成器...”吗?




当我们点击“属生生成器...”后,IDE弹出一个窗体,提供我们全方位的操作DataGrid属性的交互界面,这个界面比PropertyGrid提供更方便易用的,更符合DataGrid“国情”。所以,用户有了属性窗格之外的第二个选择。
 那么这个“属性生成器...”是什么呢?它也是一个Editor,只是它不是一个UITypeEditor,而是一个ComponentEditor,对,它是一个组件编辑器,它不是用来编辑某个属性的,而是用来操作整个控件的。

下面我们就以实例来看看要实现组件编辑器,要做哪些工作?

控件主体类

    [
    Designer(
typeof (MyDesigner)),
    Editor(
typeof (MyComponentEditor),  typeof (ComponentEditor))
    ]
    
public   class  MyControl :WebControl  {
 
    }

在 这里我们用到了两个Attribute来描述我们的控件主体类:Designer和Editor,第一个为控件主体类关联一个设计器,这里之所以要用到设 计器,因为要方便的调用组件编辑器要借助Designer类。第二个Attribute为控件主体类关联了一个编辑器,大家可以看到它的第二个参数变成了 ComponentEditor而不是UITypeEditor。

编辑器窗体类

     public   class  MyComponentEditorForm : System.Windows.Forms.Form  {
 
        
private MyControl _myControl;

        
public myControlComponentEditorForm(myControl component) {
            InitializeComponent();

            _myControl 
= component;
  
//用_myControl的属性初始化本窗体上的操作控件(如一些TextBox,还以我以前讲到的PropertyGrid的值)。
        }

 
//以下是用户点击确定完成编辑的逻辑纲要
        private void okButton_Click(object sender, System.EventArgs e) 

 
#region 这里使用PropertyDescriptor来为Component赋值与直接用 _myControl.Property_1 = textBox1.Text 这样的逻辑来赋值有一个好处,就是支持操作的Undo功能
            PropertyDescriptorCollection props 
= TypeDescriptor.GetProperties(_myControl);

            
try {
                PropertyDescriptor property_1 
= props["Property_1"];
                
if (textProperty != null{
                    textProperty.SetValue(_myControl, textBox1.Text);
                }

            }

            
catch {
            }

  

            DialogResult 
= DialogResult.OK;
            Close();
 
#endregion

        }


 
    }


编辑器类

     public   class  MyComponentEditor : WindowsFormsComponentEditor  {
 
//操作控件主逻辑
        public override bool EditComponent(ITypeDescriptorContext context, object component, IWin32Window owner) {
            MyControl control 
= component as MyControl;
            
if (Control == null{
                
throw new ArgumentException("操作对象检查,只能是特定的类""component");
            }


            IServiceProvider site 
= control.Site;//每 个控件都有一个Site属性,使用它的GetService方法使得在设计环境下,控件能得到各种设计期服务。如 IComponentChangeService、IDesignerEventService、IDesignerHost、 IDesignerOptionService等等
            
     IComponentChangeService changeService 
= null;//IComponentChangeService规定当组件被增删改时设计界面的修改,并提供方法来引发 ComponentChanged 或 ComponentChanging 事件。不由.net fw实现,由VS.net实现。

            DesignerTransaction transaction 
= null;//DesignerTransaction提供一种方法来对一系列的设计时操作进行分组,从而提高性能并使得大多数类型的更改都能撤消。

            
bool changed = false;

            
try {
                
if (site != null{
                    IDesignerHost designerHost 
= (IDesignerHost)site.GetService(typeof(IDesignerHost));//规定了支持设计器事务、管理组件和设计器、得到设计器信息。由VS.net实现。
                    transaction = designerHost.CreateTransaction("事务分组名");//DesignerTransaction由IDesignerHost得到

                    changeService 
= (IComponentChangeService)site.GetService(typeof(IComponentChangeService));
                    
if (changeService != null{
                        
try {
                            changeService.OnComponentChanging(control, 
null);//第二个参数为MemberDescriptor它是EventDescriptor和PropertyDescriptor类的基类表示正在更改的成员。如果此更改与单个成员无关,则它将为空引用。
                        }

                        
catch (CheckoutException ex) {//此处的CheckoutException是签入VSS失败
                            if (ex == CheckoutException.Canceled)
                                
return false;
                            
throw ex;
                        }

                    }

                }


                
try {
    
//以下代码实现调用一个编写好的编辑器窗口
                    MyComponentEditorForm form = new MyComponentEditorForm(control);
                    
if (form.ShowDialog(owner) == DialogResult.OK) {//from.ShowDialog(owner)指定from为owner的一个模态窗口。
                        changed = true;
                    }

                }

                
finally {
                    
if (changed && changeService != null{//如果点击了编辑器窗口的确定,引发已经更改事件
                        changeService.OnComponentChanged(Control, nullnullnull);//由于更改不与单个属性有关,所以后面的三个参数都为null
                    }

                }

            }

            
finally {
                
if (transaction != null{
                    
if (changed) {//一切正常的话,提交设计器事件
                        transaction.Commit();
                    }

                    
else {
                        transaction.Cancel();
                    }

                }

            }


            
return changed;
        }

    }


当我们做了这些事后,已经可以使用属性编辑器了,我们可以看到在属性窗格的最后面的属性页面按钮已经可用,我们可以点击这个按钮打开属性编辑器

Designer

“WYSWYG”


 也许你会说:记得DataGrid的属性编辑器可以用属性窗格下方的超链接和右键菜单打开啊,为什么这里还不行?
要回答这个问题,我们就得用到Designer

  设计器是用来管理设计时控件呈现行为的类。WebForm和WinForm的核心设计器都来自 System.ComponentModel.Design.ComponentDesigner,所以两者都有相同的架设,不过两者的引擎却是完全不同 的,WebForm使用IE做为引擎,WinForm使用GDI+做为引擎。由于我对WinForm接触不多,所以以下的论述以WebForm控件的 Designer开发为内容。
 书接上文,我们还是还是先来看看怎么完成上面的问题。要使用右键打开组件编辑器等功能可以通过定制设设计器动词来实现。
设计器动词
 设计器动词是设计界面中的命命,设计器都提供了一个设计器动词集合DesignerVerbCollection Verbs{get;}
下面来看我们的设计器类如何定制Verbs:
  

   public   class  MyControlDesigner : ControlDesigner  {

        
private DesignerVerbCollection designerVerbs;

        
public override DesignerVerbCollection Verbs {
            
get {
                
if (designerVerbs == null{
                    designerVerbs 
= new DesignerVerbCollection();
                    designerVerbs.Add(
new DesignerVerb("属生编辑"new EventHandler(this.OnControlPropertyBuilder)));//增加一个动词,关联一个方法
                }


                
return designerVerbs;
            }

        }


        
private void OnControlPropertyBuilder(object sender, EventArgs e) {
            MyComponentEditor compEditor 
= new MyComponentEditor();
            compEditor.EditComponent(Component);
        }

 
    }


 现在你可以看到,我们在属性窗格的下方和上下文菜单中看到打开编辑器的命令了。
Designer的主要功能其实是实现控件在设计期能“所见即所得”,所以我们对设计期还要有更多的了解,让我们来看以下代码:
 

        public   override   void  Initialize(IComponent component)  {
            
if (!(component is MyControl)) {
                
throw new ArgumentException("Component must be a MyControl control.""component");
            }

            
base.Initialize(component);
        }

  
public   override   string  GetDesignTimeHtml()  {
            MyControl control 
= (MyControl)Component;

            
string designTimeHtml = String.Empty;
            
try {
                designTimeHtml 
= base.GetDesignTimeHtml();
            }

            
catch (Exception e) {
                designTimeHtml 
= GetErrorDesignTimeHtml(e);
            }


            
return designTimeHtml;
        }


        
protected   override   string  GetEmptyDesignTimeHtml()  {
            
return CreatePlaceHolderDesignTimeHtml("右键点击设置控件的属性. ");
        }


        
protected   override   string  GetErrorDesignTimeHtml(Exception e)  {
            
return CreatePlaceHolderDesignTimeHtml("生成错误.");
        }


对于这些覆写方法,我们都可以望文生义得知它的意义。
不过有几点是要注意的:
1、如果控件是复合控件(就是多个基本控件合成的控件。),你最好让设它的设计器在GetDesignerTimeHtml方法中先调用一下它的含有确保子控件不为null的方法。
2、 ControlDesigner基类的GetDesignTimeHtml方法会调用控件的RenderControl方法返回HTML字符串,所以默认 情况下,控件在设计期的样子和运行期的样子会差不多。那么我们有没有办法让设计期和运行期不同呢?当然可以,最简单的办法是 overrideGetDesignTimeHtml方法实现自己的呈现逻辑,不过你也可以使用另外一个技巧,那就是运行期是会调用 OnPerRender方法再调用Render方法,而默认实现下设计期是没有调用OnPerRender方法的,所以,你可以利用这种差别,方便的使两 者同中有异。
另外模板控件的设计类我们留待模板控件专门的文章中再讲述。

 

今天你写控件了吗?----ASP.net控件开发系列(八)

怎样更改默认的控件分析逻辑
 ------用PersistChildren(false)和ControlBuilder来定制ASP.net对控件标签对中的内容的分析

“我的地盘,我做主”


 写到这里,我把foobar播放的音乐换成了周杰伦的歌,虽然不是“我的地盘”。
 我们来回顾一下第三篇中的一段代码:

< asp:DropDownList  id ="DropDownList1"  runat ="server"  Font-Bold ="True" >
 
< asp:ListItem  Value ="1" > 1 </ asp:ListItem >
 
< asp:ListItem  Value ="2" > 2 </ asp:ListItem >
</ asp:DropDownList >

 我不知大家有没有注意到一个现象,<asp:ListItem>内含的内容不能有页面对象,那么如果有了分析器如何来处理呢?。这就透露了一个信息给我们,对于页面元素的分析处理,我们是插得上手的!(噢,比动感地带还好。)
 那么我们怎么来干预控件元素的分析呢?
 1、ParseChildrenAttribute
 2、ControlBuilderAttribute
 ParseChildren 用来指定控件的分析逻辑,它有bool一个参数,true表示控件标签中的内容解释为属性,解析器用一组内定的控件生器来解析嵌套的属性、子属性、模板、 集合属性。你还可以用ParseChildren(true,"PropertyName")来指定嵌套内容传入哪个属性,WebControl内已经声 明为true。那么如果为false,怎么分析呢?在这种情况下,解析器用与控件相关的ControlBuilder来解释控件开始和结标签的内容,为将 里面的内容解释为对象,文本也会解释为LiteralControls,然后通过控件的AddParsedSubObject方法添加到控件中 Controls中。
 好,回过头来看ListItem,ListItem不是从WebControl继承而来,同时,它也没有声明为ParseChildren(true),那么它是怎么实现自己的分析逻辑的呢?答案就是它使用了ControlBuilderAttribute

[ControlBuilder( typeof (ListItemControlBuilder)), ]
public   sealed   class  ListItem : IStateManager, IParserAccessor, IAttributeAccessor
{
 
}

再来看ListItemControlBuilder类

public   class  ListItemControlBuilder : ControlBuilder
{
 
public ListItemControlBuilder()
 
{
 }

 
public override bool AllowWhitespaceLiterals()
 
{
        
return false;//除去控件标签对嵌套内容首尾空白
 }

 
 
public override bool HtmlDecodeLiterals()
 
{
        
return true;//删除HTML编码
 }

 

}


这里,我们可以不用ControlBuilder来使标签中禁用HTML编码,我们可以重载一个从WebControl继承下的控件的AddParsedSubObject方法

 

         protected   override   void  AddParsedSubObject( object  obj)  {
            
if (obj is LiteralControl) {
                Text 
= ((LiteralControl)obj).Text;
            }

            
else {
                
throw new ArgumentException(
                    
"The inner content must contain static text ");
            }

  
// do anything you want.
        }


请注意,这种做法,效率是不如ControlBuilder方法的,因为它会在每次请求实现逻辑时都执行一次,而前者只在代码产生前的解析期被执行一次。
另外,比WebControl继承下来的控件,要实现用ControlBuilder控制生成内容对象,要记得声明ParseChilder(false)。

 在上一篇文章中,我们已经接触了TypeConverter,现在我们全面的来看看这个Interpreter:
 1、TypeConverter从System.ComponentModel.TypeConverter类派生。
 2、用使用[TypeConverter(typeof(YourTypeConverter))]绑定到属性。
 3、在设计期和运行期都要用到TypeConverter,因为,两个时期都有特定类型-String之间的转换。
 4、功能一:把属性转换成字符串显示在属性浏览器,把属性浏览器的设的值转换成属性需要的类型。
 5、功能二:为子属性提供一个展开/折叠的UI。如下图:
 要实现这个功能非常之简单,你先让属性通过TypeConverterAttribute关联到转换器类System.ComponentModel.ExpandableObjectConverter,或者继承自这个类的转换器。
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public  ListItem Items
{
 
get
 
{
  
 }

}


[PersistenceMode(PersistenceMode.EnCodedInnerDefaultProperty)]
public   string  Text
{
 
}


[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public  ListItem Items
{
 
get
 
{
  
 }

}


[PersistenceMode(PersistenceMode.EnCodedInnerDefaultProperty)]
public   string  Text
{
 
}


 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值