本文内容
- 概述
- 演示自定义 TreePanel 控件
- 运行结果
- 说明
概述
当我最开始使用 VS 开发 ASP.NET Web 应用程序时,使用最多的就是用户控件(.ascx)。它的方便在于,可以将界面分割成一个个小的功能模块,也就是利用 ASP.NET 自带的控件组合成一个新的控件,然后在需要的界面“拖”进去就行。
这种方式,不仅对后台方便,对前台也是。比如,对于页面,以及页面的任何一个功能区域,我们都可以分成顶部、底部和中间内容部分,那么就可以对 CSS 做统一设置。
现在我使用 Ext.Net,虽然其 Demo 提供了 SimpleTask Demo,演示了如何自定义、封装一个 Ext.Net 控件,看上去很不错。但一是不熟悉,二是使用用户控件(.ascx)习惯了,而且似乎用户控件足够现在的项目了。
但某天,当我偶然用 Yahoo YSLOW 插件测试我的页面时,突然发现我的用户控件竟然出现了好几个 404 错误。虽然页面在运行时完全正常。我将用户控件需要的脚本,以外部文件方式添加到用户控件,不想将外部脚本在页面里引用,将来不好维护,同时,也不想直接将脚本代码嵌入到用户控件里。毕竟,外部文件可以被浏览器缓存,而嵌入代码则不能。因为,页面和用户控件初始化顺序的问题。
404 错误是个恶心的错误。它能让浏览器做很多无用功,消耗响应时间。我还是改程序吧~~~呵呵,针对目前的这个,用户控件的确存在一些问题。
本文演示如何利用 Ext.Net 封装一个自定义控件。它通常由两个文件、三部分组成:控件类文件和外部脚本文件。其中,控件类文件是一个分部类,分别实现控件的 UI 代码和逻辑代码。UI 代码负责创建控件标记;逻辑代码创建控件的客户端事件和服务器端事件。外部脚本文件用于在客户端操作控件。
演示自定义 TreePanel 控件
先看看封装后,在页面中引用自定义控件,代码如下:
<%@ Page Language="C#" %>
<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
<%@ Register Assembly="ExtNetTreePanleCustomControl" Namespace="ExtNetTreePanleCustomControl.MyCustomControl"
TagPrefix="MyControl" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<ext:ResourcePlaceHolder ID="ResourcePlaceHolder1" runat="server" Mode="ScriptFiles" />
<script src="resources/myTreePanel.js" type="text/javascript"></script>
</head>
<body>
<form id="form1" runat="server">
<ext:ResourceManager ID="ResourceManager1" runat="server" InitScriptMode="Linked"
RemoveViewState="true" IDMode="Explicit" />
<MyControl:MyTreePanel ID="MyTreePanel1" runat="server" />
</form>
</body>
</html>
其中,"<MyControl:MyTreePanel ID="MyTreePanel1" runat="server" />" 是自定义的 TreePanel 控件。
-
自定义控件的分部类 MyTreePanel,文件名分别为 MyTreePanelLogic.cs 和 MyTreePanelUI.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Ext.Net.Utilities;
using Ext.Net;
namespace ExtNetTreePanleCustomControl.MyCustomControl
{
[DirectMethodProxyID(IDMode = DirectMethodProxyIDMode.None)]
public partial class MyTreePanel
{
public const string SCOPE = "MyCustomControl.MyTreePanel";
private void InitLogic()
{
this.Listeners.Render.Fn = MyTreePanel.SCOPE + ".init";
this.Listeners.Render.Scope = MyTreePanel.SCOPE;
this.Listeners.CheckChange.Handler = MyTreePanel.SCOPE + ".SelectParentChildNodes(node,checked);";
Ext.Net.Button button = new Ext.Net.Button();
button.ID = "btnGet";
button.Text = "获得勾选";
button.Listeners.Click.Handler = MyTreePanel.SCOPE + ".getSelectedNode(#{" + this.ID + "})";
this.Buttons.Add(button);
button = new Ext.Net.Button();
button.ID = "btnClear";
button.Text = "清除勾选";
button.Listeners.Click.Handler = MyTreePanel.SCOPE + ".clearSelectedNode(#{" + this.ID + "})";
this.Buttons.Add(button);
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml.Serialization;
using Ext.Net;
using Newtonsoft.Json;
namespace ExtNetTreePanleCustomControl.MyCustomControl
{
public partial class MyTreePanel : TreePanel
{
public MyTreePanel()
{
this.ID = "MyTreePanel1";
this.Title = "MyTreePanel";
this.Width = System.Web.UI.WebControls.Unit.Pixel(300);
this.Height = System.Web.UI.WebControls.Unit.Pixel(400);
this.UseArrows = true;
this.AutoScroll = true;
this.Animate = true;
this.EnableDD = true;
this.ContainerScroll = true;
this.RootVisible = false;
this.LoadMask.ShowMask = true;
this.SelectionModel.Add(new Ext.Net.DefaultSelectionModel());
this.BuildTree();
this.InitLogic();
}
private void BuildTree()
{
Ext.Net.TreeNode root = new Ext.Net.TreeNode("Composers");
root.Expanded = true;
this.Root.Add(root);
List<Composer> composers = MyData.GetData();
foreach (Composer composer in composers)
{
Ext.Net.TreeNode composerNode = new Ext.Net.TreeNode(composer.Name, Icon.UserGray);
composerNode.Checked = Ext.Net.ThreeStateBool.False;
composerNode.Leaf = false;
composerNode.Icon = Ext.Net.Icon.Folder;
root.Nodes.Add(composerNode);
foreach (Composition composition in composer.Compositions)
{
Ext.Net.TreeNode compositionNode = new Ext.Net.TreeNode(composition.Type.ToString());
compositionNode.Checked = Ext.Net.ThreeStateBool.False;
compositionNode.Leaf = false;
compositionNode.Icon = Ext.Net.Icon.Folder;
composerNode.Nodes.Add(compositionNode);
foreach (Piece piece in composition.Pieces)
{
Ext.Net.TreeNode pieceNode = new Ext.Net.TreeNode(piece.Title, Icon.Music);
pieceNode.Checked = Ext.Net.ThreeStateBool.False;
pieceNode.Leaf = true;
compositionNode.Nodes.Add(pieceNode);
}
}
}
}
}
}
-
数据
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Web;
using System.Xml;
using Ext.Net;
namespace ExtNetTreePanleCustomControl
{
public class Composer
{
public Composer(string name) { this.Name = name; }
public string Name { get; set; }
private List<Composition> compositions;
public List<Composition> Compositions
{
get
{
if (this.compositions == null)
{
this.compositions = new List<Composition>();
}
return this.compositions;
}
}
}
public class Composition
{
public Composition() { }
public Composition(CompositionType type)
{
this.Type = type;
}
public CompositionType Type { get; set; }
private List<Piece> pieces;
public List<Piece> Pieces
{
get
{
if (this.pieces == null)
{
this.pieces = new List<Piece>();
}
return this.pieces;
}
}
}
public class Piece
{
public Piece() { }
public Piece(string title)
{
this.Title = title;
}
public string Title { get; set; }
}
public enum CompositionType
{
Concertos,
Quartets,
Sonatas,
Symphonies
}
public class MyData
{
public static List<Composer> GetData()
{
Composer beethoven = new Composer("Beethoven");
Composition beethovenConcertos = new Composition(CompositionType.Concertos);
Composition beethovenQuartets = new Composition(CompositionType.Quartets);
Composition beethovenSonatas = new Composition(CompositionType.Sonatas);
Composition beethovenSymphonies = new Composition(CompositionType.Symphonies);
beethovenConcertos.Pieces.AddRange(new List<Piece> {
new Piece{ Title = "No. 1 - C" },
new Piece{ Title = "No. 2 - B-Flat Major" },
new Piece{ Title = "No. 3 - C Minor" },
new Piece{ Title = "No. 4 - G Major" },
new Piece{ Title = "No. 5 - E-Flat Major" }
});
beethovenQuartets.Pieces.AddRange(new List<Piece> {
new Piece{ Title = "Six String Quartets" },
new Piece{ Title = "Three String Quartets" },
new Piece{ Title = "Grosse Fugue for String Quartets" }
});
beethovenSonatas.Pieces.AddRange(new List<Piece> {
new Piece{ Title = "Sonata in A Minor" },
new Piece{ Title = "sonata in F Major" }
});
beethovenSymphonies.Pieces.AddRange(new List<Piece> {
new Piece{ Title = "No. 1 - C Major" },
new Piece{ Title = "No. 2 - D Major" },
new Piece{ Title = "No. 3 - E-Flat Major" },
new Piece{ Title = "No. 4 - B-Flat Major" },
new Piece{ Title = "No. 5 - C Minor" },
new Piece{ Title = "No. 6 - F Major" },
new Piece{ Title = "No. 7 - A Major" },
new Piece{ Title = "No. 8 - F Major" },
new Piece{ Title = "No. 9 - D Minor" }
});
beethoven.Compositions.AddRange(new List<Composition>{
beethovenConcertos,
beethovenQuartets,
beethovenSonatas,
beethovenSymphonies
});
Composer brahms = new Composer("Brahms");
Composition brahmsConcertos = new Composition(CompositionType.Concertos);
Composition brahmsQuartets = new Composition(CompositionType.Quartets);
Composition brahmsSonatas = new Composition(CompositionType.Sonatas);
Composition brahmsSymphonies = new Composition(CompositionType.Symphonies);
brahmsConcertos.Pieces.AddRange(new List<Piece> {
new Piece{ Title = "Violin Concerto" },
new Piece{ Title = "Double Concerto - A Minor" },
new Piece{ Title = "Piano Concerto No. 1 - D Minor" },
new Piece{ Title = "Piano Concerto No. 2 - B-Flat Major" }
});
brahmsQuartets.Pieces.AddRange(new List<Piece> {
new Piece{ Title = "Piano Quartet No. 1 - G Minor" },
new Piece{ Title = "Piano Quartet No. 2 - A Major" },
new Piece{ Title = "Piano Quartet No. 3 - C Minor" },
new Piece{ Title = "Piano Quartet No. 3 - B-Flat Minor" }
});
brahmsSonatas.Pieces.AddRange(new List<Piece> {
new Piece{ Title = "Two Sonatas for Clarinet - F Minor" },
new Piece{ Title = "Two Sonatas for Clarinet - E-Flat Major" }
});
brahmsSymphonies.Pieces.AddRange(new List<Piece> {
new Piece{ Title = "No. 1 - C Minor" },
new Piece{ Title = "No. 2 - D Minor" },
new Piece{ Title = "No. 3 - F Major" },
new Piece{ Title = "No. 4 - E Minor" }
});
brahms.Compositions.AddRange(new List<Composition>{
brahmsConcertos,
brahmsQuartets,
brahmsSonatas,
brahmsSymphonies
});
Composer mozart = new Composer("Mozart");
Composition mozartConcertos = new Composition(CompositionType.Concertos);
mozartConcertos.Pieces.AddRange(new List<Piece> {
new Piece{ Title = "Piano Concerto No. 12" },
new Piece{ Title = "Piano Concerto No. 17" },
new Piece{ Title = "Clarinet Concerto" },
new Piece{ Title = "Violin Concerto No. 5" },
new Piece{ Title = "Violin Concerto No. 4" }
});
mozart.Compositions.Add(mozartConcertos);
return new List<Composer> { beethoven, brahms, mozart };
}
}
}
-
外部脚本文件 myTreePanel.js
Ext.ns("MyCustomControl");
// ------------------MyTreePanel----------------------------------
MyCustomControl.MyTreePanel = {
tr: null,
init: function(tree) {
this.tr = tree;
},
SelectParentChildNodes: function(node, state) {
var tree = node.getOwnerTree();
tree.suspendEvents();
if (node.parentNode != null) {
// 勾选该节点所有子节点
node.cascade(function(node) {
node.attributes.checked = state;
node.ui.toggleCheck(state);
return true;
});
// 勾选该节点父节点
var pNode = node.parentNode;
while (pNode != null) {
if (state) { //如果选中状态无所谓
pNode.ui.toggleCheck(state);
pNode.attributes.checked = state;
pNode = pNode.parentNode;
}
else { //如果未选中状态,需要查看该节点是否所有子节点都未选中
var chk = false;
pNode.eachChild(function(child) {
if (child.attributes.checked || child.getUI().isChecked())
chk = true;
});
pNode.ui.toggleCheck(chk);
pNode.attributes.checked = chk;
pNode = pNode.parentNode;
}
}
}
tree.resumeEvents();
},
getSelectedNode: function(tree) {
var msg = [];
var selNodes = tree.getChecked();
Ext.each(selNodes, function(node) {
msg.push(node.text);
});
Ext.Msg.show({
title: "勾选节点",
msg: msg.join(','),
icon: Ext.Msg.INFO,
minWidth: 200,
buttons: Ext.Msg.OK
});
},
clearSelectedNode: function(tree) {
tree.clearChecked();
}
};
运行结果
说明
有几点值得注意:
- 标记 <ext:ResourcePlaceHolder ID="ResourcePlaceHolder1" runat="server" Mode="ScriptFiles" />,Mode="ScriptFiles" 是为了 MyTreePanel 呈现后,保证页面在正确的时机加载,并执行 MyCustomControl.MyTreePanel.init 方法,初始化其 tr 属性,以便在客户端操作该控件;
- public const string SCOPE = "MyCustomControl.MyTreePanel"; 语句,配合脚本的 Ext.ns("MyCustomControl"); 以及 MyCustomControl.MyTreePanel = { … },在脚本创建命名空间和 MyTreePanel 类。
- 这种方式在很大程度上提高页面性能,便于日后维护该控件。