Developing ASP.NET Custom Control With C# Builder

  • 20884
  • 0

摘要: Developing ASP.NET Custom Control With C# Builder

Developing ASP.NET Custom Control With C# Builder

 

      不知有多少人还记得当初撰写Windows 3.1 程序时的情况,100 程序码, 只为在画面上秀出一个视窗, 如果要在视窗上加上一个TextBox 那么又再得加上几程序码, 其步骤之繁杂足以让许多程序设计师放弃往Windows 上移动。随着物件导向设计的进化脚步,这些折磨人的工作也慢慢的简化 借着Framework( MFCOWL)的帮助,只要1-20 程序码就能完成以往那1-200 程序码的工作。但是如同大部份的进化模式一样,齿一旦转动 向前走就是唯一的道。人们开始发现, 光是Framework 够的,她们需要大生产的工具才能以快的速设计出应用程序, 于是乎Visual Basic 出现!!正式的敲开RAD 时代的大门, 程序设计师只要动动鼠、设设属性, 程序码都用写就可以做到原本要写1-20 的应用程序才能完成的功能,也用一直重复执程序调测画面,因为所即所得正是RAD 的特色之一。Visual Basic 开启一个崭新时代,也启动Framework 之战后的另一场开发工具战役,强敌DELPHI 装备着同样的武器及强大的资库支援加入战场,并以自身独有的执优势停的往Visual Basic 先天的直译弱点攻击。直至今日,DELPHI Visual Basic 地互有消长, Windows 上的战争也慢慢趋向平淡。随着Internet 的普遍化,企业开始要求将原本在Windows 上的系统转移到Web 上, 这时人们开始发现,以往用建构小型Web 系统的CGIASPPHP的生产力不足以满足移转大型系统的需求,于是进化齿再一次的转动,RAD Web 的面容出现在人们眼前,再一次证明舞动鼠也可以写Web 程序,她的名字是AS P.NET。当然,将ASP.NET RAD 相提并似乎有点对称,因为RAD 属于开发工具层面,而ASP.NET 则是语言层面, 可否认的是这者共同实现人们以RAD 方式撰写Web 程序的梦想。既然是RAD 那么舞动鼠、拉拉元件、设设属性也是常态, 但期望着开发者为你预先建好所有元件是实际的, 总有些时候我们得自己动手做些元件,这是本文的主要目的,让我们开始这一趟程吧。

 

Whats ASP.NET Custom Control

 

在一开始, 元件这个名词的涵意指的是一个具备好封装、低藕合性、可独运作的程序单元, 随着元件化设计模式的普及化,这种过于广泛的定义慢慢被割成部份。一部份仍旧沿用元件之名,但其涵意已挶限在UI 介面上,另一部份则使用Control 之名, 指的是仅具备UI 的元件。在ASP.NET 上, 程序设计师大部份撰写的是后者, 也就是Custom C ontrol,她可以被安装在RAD 开发工具的元件盘中,使用者可藉由鼠拖放使用她。管是元件还是Co ntrol,其基本的要素是相同的,那就是Pr opertiesMethodsEventsProperties 指的是Control/Component 态,Control.ControlState 代表着该Control 目前的态是处于初始化或是绘制中,Methods 代表的是某一个动作, Control.RenderControl 会将该Control 绘制到HtmlTextWriter 中,Events 则是代表着目前元件的态发生了什么变化, 或是用通知某个动作即将执或是执完毕。这三个要素共同组成一个Control/Compon ent,也间接的提供RAD 工具一个通道Control/Component 互动。使用.NET相容语言实作这三个要素是件简单且再自然过的事,因此重点就在于如何撰写ASP.NET Custom Control ASP.NET 要求所有的Custom Control 必须继承至System.Web.UI.Control 亦或是其子System.Web.UI.WebControls.WebControl者之间的差别在于System.Web.UI.Control 仅具备一个Custom Control 所需的基本功能,WebControl 则同时具备Custom Control 的基本功能及Rending Style 的能 大部份的情况下设计者都会选择直接继承至WebControl 或是其子(TextBoxCheckBox),这样做可以下自撰写Rending Style 的工作。当设计者需要自Rending Style 或是需要此能时,那么直接继承至Control 就是明智的选择。

 

First Custom Control : NumberEdit

 

在许多线上购物的网页中常会遇到要输入信用卡号码的对话 某些设计较好的网页会限制该对话仅能接收字部份的输入, 这一节中所撰写的就是这一种Control 。在开始之前! 首先得先解这是如何做到的?无庸置疑!这个Control 一定是TextBoxHTML 中的TextBox 拥有一个名为onKeyPress JavaScript 事件, 此事件会在使用者键入某个字元至TextBox 时触发,只要截这个事件,那么限制使用者的输入就是问题,程序1 是完整的JavaScript 程序码:(程序1)

者们可以看到程序中只接受keycode<48(ASCII 0)keycode>57(ASCII 9)之间的输入,现在只要将这段JavaScript 结合TextBox 就可以完成这一节的NumberEditor Control (程序2 )(程序2)

using System; using System.Text; using System.Web.UI; using System.Web.UI.WebControls;

namespace SmartSuite.Web.Editors

{ public class NumberEditor:TextBox {

private void RenderJavaScript(HtmlTextWriter output)

{ StringBuilder sb=new StringBuilder(); sb.AppendFormat(" "); output.Write(sb.ToString());

}

protected override void AddAttributesToRender(HtmlTextWriter writer)

{ base.AddAttributesToRender(writer); writer.AddAttribute("OnKeyPress",String.Format("return

{0}_KeyPress_Handle(this);",base.ID)); }

protected override void Render(HtmlTextWriter output)

{ RenderJavaScript(output); base.Render(output);

}

} }

NumberEditor 选择直接继承至TextBox 这可以下从头撰写一个TextBox 的时间。让我们从Render 函式开始讨起, 这个函式是由此Control Container Contro l(容器)所呼叫,

大多情况下这个Conatiner Control 就是Page ControlPage Control 在呼叫子Control Render 函式时会传入一个HtmlTextWriter 物件, Control 可以用这个物件绘出Control 所对应的HTML 程序码。NumberEditor 这个物件绘出JavaScript 程序码, 接着接父(TextBox)绘出预设的TextBox HTML 程序码。为JavaScript onKeyPress 结上NumberEditor 程序中覆载AddAttributesToRender 函式完成这个动作,这个函式是WebControl 所独有的,她负责绘出HTML Tag 中的參數部份,Style ValueOnKeyPressOnBlur 等等之

Debuging Custom Control

管是使用那种工具撰写何种程序, 除错都是最重要的动作, 程序是人写的, 怎可能出错呢?Custom Control 除错与除错一般程序大致相同,只要在原先的ASP.NET Project Group 中加入Custom Control Project 即可完成准备动作(1)

(1)

接着只需设定想要程序停下的中断点即可(2 ) (2)

具实用性的Custom Control : Spin Edit

Spin Edit Windows 中是相当常用的Control 通常是用调整一个对话中的字,但是这种Control 在网页中并 这一节中将开发运于网页上的Spin Edit Control。在开始之前!者们得先解一个重点,Spin Edit并非是一个单一Control ,她是由一个TextBox Button 所组成的,在网页上,为使TextBox Button 能够对齐,还得在其背后加上一个Table(程序3) (程序3)

using System; using System.Text; using System.Drawing; using System.Web;

using System.Web.UI; using System.Web.UI.WebControls; using System.Web.Util;

namespace SmartSuite.Web.Editors

{ public class SpinEdit:TextBox {

private const string _updownFontFamily= "font-family:Webdings;font-size:9pt;margin:0px;padding:0px;border:0px;margin-top:-0.4em;margin-right:-0.1em;"; private const string _upbuttonStyle= "width:18px;px;height:14px;vertical-align:middle;text-align:center;padding:0px;top:4;position:relative;"; private const string _downbuttonStyle= "width:18px;px;height:14px;vertical-align:middle;text-align:center;padding:0px;top:-3;position:relative;"; private const string SPIN_EDIT_SCRIPT_ID="SPIN_EDIT_SCRIPT";

private string BuildScriptHandler()

{ StringBuilder sb=new StringBuilder(); sb.Append(" "); return sb.ToString();

}

private string MakeButton(string kind)

{ StringBuilder sb=new StringBuilder(); if(kind=="1")

sb.AppendFormat( "5

",base.ID,kind,_upbuttonStyle,_updownFontFamily);

else

sb.AppendFormat( "6

",base.ID,kind,_downbuttonStyle,_updownFontFamily);

return sb.ToString(); }

private TextBox clone()

{ TextBox spinEdit = new TextBox(); spinEdit.Width = base.Width;

spinEdit.Height = base.Height; spinEdit.BackColor = base.BackColor;

spinEdit.BorderColor = base.BorderColor; spinEdit.BorderStyle = base.BorderStyle; spinEdit.BorderWidth = base.BorderWidth; spinEdit.Columns = base.Columns; spinEdit.CssClass = base.CssClass; spinEdit.Font.CopyFrom(base.Font); spinEdit.ForeColor = base.ForeColor; spinEdit.Text = base.Text; spinEdit.TabIndex = base.TabIndex; spinEdit.AccessKey = base.AccessKey; spinEdit.AutoPostBack = base.AutoPostBack; spinEdit.Enabled = base.Enabled; spinEdit.EnableViewState = base.EnableViewState; spinEdit.MaxLength = base.MaxLength; spinEdit.ReadOnly = base.ReadOnly; spinEdit.Rows = base.Rows; spinEdit.TextMode = base.TextMode; spinEdit.ToolTip = base.ToolTip; spinEdit.Visible = base.Visible; spinEdit.Wrap = base.Wrap; spinEdit.ID = base.UniqueID; spinEdit.ReadOnly = base.ReadOnly;

return spinEdit; }

protected override void OnPreRender(EventArgs e)

{ base.OnPreRender (e); if(!Page.IsClientScriptBlockRegistered(SPIN_EDIT_SCRIPT_ID))

Page.RegisterClientScriptBlock(SPIN_EDIT_SCRIPT_ID,BuildScriptHandler()); }

protected override void Render(HtmlTextWriter writer)

{ Table table = new Table(); table.CopyBaseAttributes(this); table.CellPadding = 0; table.CellSpacing = 0; table.BorderWidth = 0; table.BorderStyle = BorderStyle.None; table.Style.Add("Border-Collapse", "collapse");

TableRow tableRow = new TableRow(); TableCell tableCell = new TableCell(); tableCell.Controls.Add(clone()); tableCell.RowSpan = 2; tableRow.Cells.Add(tableCell);

tableCell = new TableCell(); tableCell.RowSpan = 1; tableCell.Text=MakeButton("1");

tableRow.Cells.Add(tableCell); table.Rows.Add(tableRow); tableCell.VerticalAlign = VerticalAlign.Bottom; tableRow = new TableRow(); tableCell = new TableCell(); tableCell.Text=MakeButton("0"); tableRow.Cells.Add(tableCell); tableCell.VerticalAlign = VerticalAlign.Top;

table.Rows.Add(tableRow); table.RenderControl(writer); table.Rows.Clear();

} } }

程序与NumberEditor Control 并没有有太大的同,唯一值得注意的是Render 函式,者们可以发现,SpinEdit虽然继承至TextBox,但是却在Render 函式中建立了另一个TextBox 绘出。如同前面所提的,SpinEdit 并非是单一Control 她是由一个TextBox Button 共同组成的,所以Render 函式中建立了一个Table,接着将TextBox Button 绘入这个Table 之中,使用Table 的主要原因是为使TextBox Button 能够 对齐。另外者可能也发觉到,在Render 函式中所建TextBox 会复制SpinEdit 的属性,这使得位于Render 函式中的TextBox 可以如使用者期望般呈现于网页上。

Refactoing JavaScript

细心的者或许已经发觉前面Control 有着一些缺点,第一个缺点是当使用者放三个SpinEdit 或是NumberEdit Page 上时, JavaScript 程序码会重复绘出三次, 这一点对于较复杂的页面來說小的负担。另一个缺点是SpinEdit 中使用TextBox 这使得访者可以在这个TextBox 中输入非字的字元, 这会引发JavaScript 的错误。改善第一个缺点的方法很简单, 只要使用Page 物件所提供的RegisterClientScriptBlock 函式就能解决, 第二个问题则可以经由将TextBox 改成NumberEditor 改善,程序4 5 是重构后的程序码。(程序4)

using System; using System.Text; using System.Web.UI; using System.Web.UI.WebControls;

namespace SmartSuite.Web.Editors

{ public class NumberEditor:TextBox {

private const string SCP_NUMBER_ONLY="{29FD7A41-49FD-4fc4-AFA9-6A0B875A1A51}"; private void RegisterNumberOnlyScript() {

StringBuilder sb=new StringBuilder(); sb.Append(" "); if(!Page.IsClientScriptBlockRegistered(SCP_NUMBER_ONLY))

Page.RegisterClientScriptBlock(SCP_NUMBER_ONLY,sb.ToString()); }

protected virtual void RegisterValidatorScript() { RegisterNumberOnlyScript(); }

protected override void OnPreRender(EventArgs e)

{ base.OnPreRender(e); RegisterValidatorScript();

}

protected override void AddAttributesToRender(HtmlTextWriter writer)

{ base.AddAttributesToRender(writer); writer.AddAttribute("OnKeyPress","return NumberEdit_KeyPress_Handle(this);");

} } }

(程序5)

程序的变化并多,除SpinEdit 将父别由TextBox 改成NumberEditor 之外,就只剩下呼叫RegisterClientScriptBlock函式的部份。为了不重复绘出一模一样的JavaScript 在注册JavaScript 之前程序呼叫另一个IsClientScriptBlockRegistered确定Script 是否已经 注册 笔者选择使用GUID 做为ID 來識别特定的JavaScri pt

分发Custom Controls

Control 的设计者可以选择种方式分发Control 至客户端,一种是最简单的方式, 只要将Assemply DLL 复制到客户端, 再安装到RAD 环境中即可。另一种方式则是采用Strongly Assembly 方式,将Assembly 注册到GAC 中,这种方式通常是用分发 最终版本的Control管是用那一种方式,程序6 Assemply Information 都是必要的: AssemplInfo.cs (程序6)

using System.Web.UI; …………

[assembly: TagPrefix("SmartSuite.Web.Editors","swe")]

这个宣告代表着当使用者拖放Control Page 中时,ASP.NET会以swe 做为该Control 的前导字, 如程序7 所示。 (程序7)

虽然ASP.NET 会抱怨载入一个TagPrefix Control,但是就Control 开发者而言, 加上这样的宣告可以让使用者加清楚自己正在使用那些Control,这比起ASP.NET 预设产生的cc1 cc2的漂

Install Control to IDE

所有的.NET RAD IDE 都会支援将Control 安装至其元件盘中, 本文以C# Buidler 。使用者可以使用Component|Install .NET Component 安装本文中的个元件(34) (3)

(4)

在图4 的画面中点选Select an Assemb ly紐來讀Control Assembly D LL(5 )

(5)

完成后就可以在元件盘上看到这Control (6)

 (6)

C# Builder Install Component 部份有一些Bug 如果者安装后看到这些Control 请点选Install .NET Components 中的Reset 按键重置元件盘后(这会清空除预设安装外的元件)再重新安装一次。笔者已将这个Bug 反应给Borland 相信很快的就会有修正程序。

Customize Control Icon

前面虽然已经顺的将Control 安装到IDE ,但是使用者肯定欣赏这种作法, 因为这些Control 使用预设的图示, 这造成使用者辨认上的困扰。要为Control 加上一个独有的图示并困难,只要准备一个16x16 大小的Bitmap 后将她与Control Assembly 一起编译即可。在做这个工作时有一点须特别注意,通常Bitmap 档案都会放在Control Root 錄下,者们必须Project Default Namespace(7) (7) 接着修改NumberEditor.cs 來連Bitmap C ontrol(程序8)

 

(程序8)

………

[ToolboxBitmap(typeof(NumberEditor), "SmartSuite.Web.Editors.NumberEditor.bmp")]

public class NumberEditor:TextBox ………

编译后再次安装这些Control 就可以看到同的图示(8)

(8)

GAC

.NET Framework 要求元件必须是Strongly Assembly 型态才能安装到GAC 中, 将元件安装到GAC 中的好处就像是以往将DLL 放入System32 錄中相同, 所有程序可以共用这一个元件, 需要在每一个程序的目錄中都放上一个Control DLL 这可以减少分发的档案与大小。要将本文中的Control 安装到GAC 中,我们得先为她们产生一个Key file 这可以使用.NET Framework 提供的sn.exe 工具达到。

(产生Key file)

sn –k SmartSuite.keys

接着在AssemblyInfo.cs 结这个Key file(程序9 ) (程序9)

using System.Reflection; using System.Runtime.CompilerServices; using System.Web.UI;

[assembly: AssemblyTitle("SmartSuite.Web.Editors")]

[assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("")] [assembly: AssemblyCopyright("")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] .......

[assembly: AssemblyVersion("2.0.0.0")] ........

[assembly: AssemblyDelaySign(false)]

[assembly: AssemblyKeyFile("..\\..\\SmartSuite.keys")]

[assembly: AssemblyKeyName("")] [assembly: TagPrefix("SmartSuite.Web.Editors","swe")]

重新编译后用位于系统管中的Microsoft .NET Framework 1.1 设定安装至GAC (组件快取)就可以 :之前用到这个元件的专案可能必须重新调整,因为原先的Register 部份并未含有Public Key

后记

在这一期文章中笔者以范为主轴,与者讨ASP.NET Custom Control 的设计方式,日后有机会笔者会持续讨Designer SupportControl Rending ViewState SessionCache 等深入的课题,下次再見了