ASP.NET自定义控件 第四天 折叠面板自定义控件
2008-12-23 09:26
375 查看
1. 引言
在前几次任务里我们开发一个星级控件并逐渐为其增加一新的特性,在本次任务里,我们将开发一个较复杂的自定义控件,该自定义控件需要实现折叠面板的功能。用户可以向面板控件中自由添加控件,该控件呈现出来后会根据用户设置决定是否显示折叠按钮,如果允许则用户可以点击按钮展开/折叠按钮以显示或隐藏面板,并且可以在服务器端捕捉到展开/折叠事件以进行更多的控制,该控件运行效果图如下:2. 分析
我们在确定该控件最终能够使用HTML呈现出来之后,接下来要考虑的是为该自定义控件选择一个合适的基类。从要实现的功能来看,该控件分为两部分,一部分是包含展开/折叠按钮的标题行,另一部分是包含用户放置按钮的容器,很显然这个容器我们可以使用Panel类,为了保留用户直接定义Panel中子标记的特性,使自定义控件直接继承自Panel类,根据设置决定是否显示标题行,并且根据面板状态显示恰当的展开或折叠图标。由于Panel可以看作一个容器控件,那么当页面上使用多个该控件时会产生什么样的结果,容器中的子控件还能保证是唯一的吗?可以尝试编写一个继承自Panel类的自定义控件CustomerControl并向其控件集合(Controls属性)中添加一个ID为txt的文本框,当在页面上放置两个这样的自定义控件时会生成了两个文本框(毫无疑问,因为是两个自定义控件),但是它们的id属性均为txt,类似于以下代码:
<span id="cc1"><input name="txt" type="text" id="txt" /></span> <span id="cc2"><input name="txt" type="text" id="txt" /></span> |
<span id="cc1"><input name="cc1$txt" type="text" id="cc1_txt" /></span> <span id="cc2"><input name="cc2$txt" type="text" id="cc2_txt" /></span> |
为了使折叠控件更灵活,允许开发人员设置是否可以展开/折叠控件,同时编写属性使得用户可以不使用服务器端提交方式,仅在客户端执行展开/折叠操作。还需要即使在用户禁止视图状态的情况下仍然能够记住控件的展开/折叠状态,因此需要使用控件状态保存状态设置,最后提供一些额外的属性帮助设置标题的样式。
由此得出,该自定义控件应具有以下属性:
属性 | 描述 |
---|---|
EnableDropDown | 是否允许展开/折叠 |
EnableClientScript | 是否允许使用客户端脚本执行展开/折叠动作 |
ShowExpanded | 初始状态是否被展开 |
Cpation | 标题 |
CaptionBackColor | 标题背景色 |
CaptionForeColor | 标题前景色 |
.NET中的事件基于委托,在ASP.NET中可以使用EventHandler委托(与此相关还定义了泛型委托)定义事件,事件参数包含了与事件有关的数据。有关委托和事件及泛型的知识请参阅相关书籍。 |
实现IPostBackEventHandler接口以处理回发事件。
将子控件事件作为顶层事件公开。
使用冒泡法将事件沿包含层次向上传播到合适的位置引发。
在本次任务中我们将使用第二种方式,另外两种方式将在以后的任务中介绍。为了将子控件事件作为顶层事件公开,需要经过以下几个步骤:
为自定义控件定义事件
为了确保事件在引发时已经被订阅编写辅助方法检查事件是否为空(null)
编写子控件事件处理程序,根据需要生成事件参数引发事件(调用第2步中辅助方法)并执行其他的操作。
以上是对面板控件的分析,接下来我们将按照分析的结果实现该控件。
3. 实现
3.1 在解决方案ControlLibrary类库中添加ExtendPanel类,并根据分析定义相关属性:public class ExtendPanel : Panel, INamingContainer { [Themeable(false)] public bool EnableDropDown { get { object o = ViewState["EnableDropDown"]; if (o == null) return false; return (bool)o; } set { ViewState["EnableDropDown"] = value; } } // 是否使用客户端脚本 [Themeable(false)] public bool EnableClientScript { get { object o = ViewState["EnableClientScript"]; if (o == null) return false; return (bool)o; } set { ViewState["EnableClientScript"] = value; } } // 是否被展开 [Themeable(false)] public bool ShowExpanded { get { object o = ViewState["ShowExpanded"]; if (o == null) return true; return (bool)o; } set { ViewState["ShowExpanded"] = value; } } //标题 [Themeable(false)] public string Caption { get { object o = ViewState["Caption"]; if (o == null) return "Panel"; return (string)o; } set { ViewState["Caption"] = value; } } //标题背景色 public Color CaptionBackColor { get { object o = ViewState["CaptionBackColor"]; if (o == null) return Color.SkyBlue; return (Color)o; } set { ViewState["CaptionBackColor"] = value; } } //标题前景色 public Color CaptionForeColor { get { object o = ViewState["CaptionForeColor"]; if (o == null) return Color.White; return (Color)o; } set { ViewState["CaptionForeColor"] = value; } } } |
3.2 接下来定义布尔型私有变量用于标识当前面板是展开状态还是折叠状态,为了避免视图状态的影响,将该属性值存储在控件状态中:
private bool _panelDisplayed; protected override void OnInit(EventArgs e) { base.OnInit(e); Page.RegisterRequiresControlState(this);//注册控件状态 } protected override object SaveControlState() { Pair p = new Pair(); p.First = base.SaveControlState(); p.Second = _panelDisplayed; return p; } protected override void LoadControlState(object savedState) { if (savedState == null) return; Pair p = (Pair)savedState; base.LoadControlState(p.First); _panelDisplayed = (bool)p.Second; } |
protected override void CreateChildControls() { if (EnableDropDown)//如果允许下拉则创建标题条 { base.CreateChildControls(); CreateControlHierarchy(); } else { base.CreateChildControls();//如果不允许下拉,则显示原有控件 } } |
protected virtual void CreateControlHierarchy() { Table t = new Table(); TableRow row1 = new TableRow(); t.Rows.Add(row1); TableCell cell1 = new TableCell(); row1.Cells.Add(cell1); cell1.Text = " " + Caption; TableCell cell2 = new TableCell(); row1.Cells.Add(cell2); cell2.HorizontalAlign = HorizontalAlign.Right; TableRow row2 = new TableRow(); t.Rows.Add(row2); TableCell body = new TableCell(); body.ID = "Body"; row2.Cells.Add(body); body.ColumnSpan = 2; Control[] rg = new Control[Controls.Count]; Controls.CopyTo(rg, 0); foreach (Control ctl in rg) body.Controls.Add(ctl); Controls.Clear(); Controls.Add(t); |
3.5 接下来在该方法中判断是否使用客户端展开/折叠动作(EnableClientScript)属性,如果该属性为false,即表明要执行服务器提交动作,因此向标题行单元格中添加服务器端图片按钮,并处理该控件点击事件(稍后会实现该事件处理程序):
WebControl img; if (!EnableClientScript) { img = new ImageButton(); ((ImageButton)img).Click += new ImageClickEventHandler(OnClick); cell2.Controls.Add(img); } |
else { img = new System.Web.UI.WebControls.Image(); img.ID = "Icon"; cell2.Controls.Add(img); // 添加样式 img.Attributes["onmouseover"] = "this.style.cursor = \"hand\";"; img.Attributes["onmouseout"] = "this.style.cursor = \"\";"; img.Attributes["onclick"] = "__toggle()"; if (!Page.ClientScript.IsClientScriptBlockRegistered("__toggle")) { string js = BuildScript(body); Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "__toggle", js, true); } } |
private string BuildScript(TableCell body) { StringBuilder sb = new StringBuilder(); sb.AppendLine("function __toggle() {"); sb.AppendFormat(" var body = document.getElementById(\"{0}\");\r\n", body.ClientID); sb.AppendLine(" var display = body.style.display;"); sb.AppendLine(" if (display == \"\") {"); sb.AppendLine(" body.style.display = \"none\";"); sb.AppendLine(" } else {"); sb.AppendLine(" body.style.display = \"\";"); sb.AppendLine(" }"); sb.AppendLine("}"); return sb.ToString(); } |
ShowChildControls(_panelDisplayed); ShowImageButton(_panelDisplayed); }
3.8 实现ShowChildControls方法以显示或隐藏面板内容:
private void ShowChildControls(bool display) { if (Controls.Count != 1) return; Table t = (Table)Controls[0]; TableRow r = t.Rows[1]; TableCell body = r.Cells[0]; body.Style["display"] = (display ? "" : "none"); }
3.9 在ControlLibrary类库中添加Image目录,将collapse.bmp和expand.bmp图片放置到该目录中,设置生成动作为嵌入并在AssemblyInfo.cs中添加资源文件的注册,实现ShowImageButton根据面板状态显示恰当的图标:
private void ShowImageButton(bool display) { if (Controls.Count != 1) return; Table t = (Table)Controls[0]; TableRow r = t.Rows[0]; TableCell icon = r.Cells[1]; //设置相应图片 System.Web.UI.WebControls.Image img = (System.Web.UI.WebControls.Image)icon.Controls[0]; string imageName = "ControlLibrary.Image.expand.bmp"; if (display && !EnableClientScript) imageName = "ControlLibrary.Image.collapse.bmp"; img.ImageUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(), imageName); }
3.10 接下来进行服务器端点击事件的处理,首先在ExtendPanel类中使用泛型委托声明事件,该泛型委托意味着使用PanelClickEventArgs作为事件参数类型,并且该类必须继承自EventArgs类:
public event EventHandler<PanelClickEventArgs> PanelClick;
定义PanelClientEventArgs事件参数类并添加BeingClosed属性标识面板状态:
public class PanelClickEventArgs : EventArgs { public bool BeingClosed { get; set; } }
3.11 根据自控件事件的分析,在ExtendPanel类中定义辅助方法检查事件是否已被订阅:
protected virtual void OnPanelClick(PanelClickEventArgs args) { if (PanelClick != null) PanelClick(this, args); }
3.12 触发服务器端图片按钮点击事件时,在事件处理方法OnClick中生成事件参数类后调用OnPanelClick方法,并且更新面板和图标显示,如此订阅的事件处理程序就可以参与到图片按钮点击过程中:
private void OnClick(object sender, ImageClickEventArgs e) { PanelClickEventArgs args = new PanelClickEventArgs(); args.BeingClosed = _panelDisplayed; OnPanelClick(args); //更新显示状态 _panelDisplayed = !_panelDisplayed; ShowChildControls(_panelDisplayed); ShowImageButton(_panelDisplayed); }
3.13 最后重写Render方法呈现控件,该方法调用PreparentControlForRendering方法将控件的样式应用的创建的表格上并根据属性设置标题的前景色和背景色:
protected override void Render(HtmlTextWriter writer) { PrepareControlForRendering(); base.Render(writer); } protected virtual void PrepareControlForRendering() { if (Controls.Count != 1) return; // 应用样式 Table t = (Table)Controls[0]; t.CopyBaseAttributes(this); if (ControlStyleCreated) t.ApplyStyle(ControlStyle); t.CellPadding = 1; t.CellSpacing = 0; //设置标题样式 TableRow row1 = t.Rows[0]; row1.BackColor = CaptionBackColor; row1.ForeColor = CaptionForeColor; }
3.14 在解决方案的Web网站中创建测试页,声明并定义自定义面板控件,测试运行结果。
4. 总结
在本次任务里我们创建了一个可以折叠/展开的自定义面板控件,应用EventHandler泛型委托和自定义事件类将图片的点击事件公开为面板的顶层事件,这样使用者就可以订阅该事件进行自定义处理。该面板控件两个比较重要的属性是EnableDropDown和EnableClientScript属性,根据前者决定是否需要显示附加的标题栏,后者决定是生成JavaScript脚本以在客户端进行展开/折叠操作,还是使用图片按钮处理服务器点击事件。如果点击事件提交到服务器端处理,则使用自定义事件类保存面板状态并由图片按钮的点击事件引发面板顶层事件,使用户能够自定义点击事件的处理。在下次任务里,我们将介绍事件处理的另外一种方式——使用IPostEventHandler接口,在原有星级控件的基础上增加评分的功能,允许用户自由选择评分并引发服务器端事件,以能够得到用户选择的分数。
ASP.NET自定义控件系列文章
前言
第一天 简单的星级控件
第二天 带有自定义样式的星级控件
第三天 使用控件状态的星级控件
第四天 折叠面板自定义控件
第五天 可以评分的星级控件
第六天 可以绑定数据源的星级控件
第七天 开发具有丰富特性的列表控件
第八天 显示多个条目星级评分的数据绑定控件
第九天 自定义GridView
第十天 实现分页功能的DataList
全部源码下载
本系列文章PDF版本下载
相关文章推荐
- ASP.NET - 自定义控件【第四天 折叠面板自定义控件】
- ASP.NET MVC搭建项目后台UI框架—3、面板折叠和展开
- 使用Javascript,CSS和Ajax创建ASP.NET自定义控件
- 向ASP.NET自定义控件中嵌入CSS资源
- Asp.net自定义控件之单选、多选控件
- ASP.NET - 自定义控件处理页面事件(控件与页面数据交互)的方法
- (转)ASP.NET自定义控件组件开发 第一章 待续
- Asp.net 2.0 自定义控件开发[创建自定义右键PopupMenu控件]
- Asp.net 2.0 自定义控件开发[创建自定义浮动菜单FloadMenu控件][示例代码下载]
- 创建ASP.NET WEB自定义控件——例程3
- 开发asp.net自定义控件
- asp.net 用户控件和自定义控件总结
- asp.net 自定义控件实现无刷新上传图片,立即显示缩略图,保存图片缩略图
- Asp.net 自定义控件 => 抛出“未知的服务器标记” 异常 解决办法
- 开发asp.net自定义控件(asp.net学习笔记三) 选择自 cashcho 的 Blog
- ASP.NET自定义控件组件开发
- 详解ASP.NET自定义控件开发实例
- asp.net 快速引用用户自定义控件技巧一例!
- asp.net 关于模版,自定义控件的使用