复合控件和事件(2)——属性,页面要回发,属性要保存
上一篇:复合控件与事件(1)——基础入门,组合也是一种封装
在上一篇中,我们介绍了复合控件的一些基础知识,并且利用“组合”的观念将各“类”控件简单地组合在了一起,我们通过public或者protected访问权限或者通过属性将我们的控件暴露给外部,并通过点运算符在外部对其进行调用。这样的做法虽然可行,但却不一定是我们想要的,如果你和我一样想要一个使用起来像微软提供的WebControl一样便捷的看上去更像整体的控件的话,那就请继续阅读吧。
微软控件和我们的现在的(文章1中所做出来的)控件有什么差别呢?哪些是我们需要做的呢?只有弄清楚这两个问题我们才知道我们接下来要做的工作。
打开工具箱,我们可以看到我们的控件和微软的控件都摆放在上面,拖拽的方式我们已经与其无异了,但是打开属性面板我们发现我们拥有的多是来自其父类的属性,事件列表里面也都是一些父类的事件,有没有想过在这些地方可以直接调用到子控件的属性和事件呢?(而不是和文章一中用点运算符去对待它们。)
本文就和大家一同将属性添加进来。
我们知道,控件就是一个特殊的类,于是你可能很容易就想到如下的代码:
让我补充一个测试代码用来输出
我们可以看见页面运行后出现了Hello world!(Text1),在点完Button后,文字不见了。
想像一下我们的Label控件的Text会不会这样呢?很显然是不会的,页面再刷,只要Text不被重新赋值或者改写,将不会改变它的值。因此属性的这种传统写法就显得力不从心了。那问题出在哪里呢?问题就在这个页面回发中,页面回发后,在服务器发生了什么呢?一个相同的页面被new了出来,然后回传给了客户端,因此你看到了同样的页面,但是我们希望我们在点按钮前的页面和按钮后的页面是一致的,也就是你对回发前的部分乃至全部修改你希望将它们一直保持下来。而一个全新的页面就像你买了一件新衣服一样,永远不是你之前穿过的那一件了(假设被你用蓝笔划过),但是ASP.NET的机制让我们不得不得到一件新衣服,就像你把朋友的衣服弄没了,你又买了一个新的一样,你为了不让他发现,又拿了蓝笔在上面划过,于是他没有发觉!同样的道理,只要将你在这件“衣服”上留下的所有特征保存起来,你就可以所谓地克隆出一件一样的衣服了,那么回发也就不会导致一个new的让人诧异的结果了。如何做呢?看下面改进后的代码:
这样就可以了么?当然!看看测试代码(对比照片):
Hello world!(Text1)
Hello world!(Text2)
值就是通过ViewState被保存下来的,当然如果你觉得ViewState的效率不高的话可以换用其他你觉得高的缓存,这里只是告诉你页面回发的时候需要找个介质来把状态暂时地保存起来,等新的页面实例生成后,记得把状态还给人家。呵呵。
以上说了这么多是控件自己的属性。那么如何给子控件的属性赋值呢?
其实这更简单,这就是你如何看待子控件和你的复合控件的关系的问题了。比较正确的理解应该是,复合控件是“关联”子控件的,(也就UML中的那个箭头从复合控件中指向了子控件),在复合控件的编写过程中,子控件实际就是一个被用到的Object,那么这个Object的对象自然为客户程序(这里就是指复合控件的那个cs文件)所用了。你或许会问,那如何将它缓存呢?回顾刚才的代码块1:
注意我的注释,也许你之前的理解会不小心变成对我们的属性Text1没有保存状态而表示遗憾,那么现在你要清晰真凶是text1而不是Text1了。这样也更帮助我们认识为何用ViewState了。了解了这个,我们就可以很容易的写出下面的代码:
而我们关于缓存的疑问可以这么被问出来:“this.ddl.Text是否能在PostBack后保存住呢?”很显然MS的控件处理了这些,正如我们用ViewState来保存状态一样,微软也用这个特性来保存值在页面回发时候的视图状态的。
我在示例中添加了“数据源”并让创建的时候指定一个属于该数据源的值赋值给我们DDLText,在回发后,这个值没有重新再传,而是希望我们的this.ddl.Text能够保存状态,将它知道的告诉我们。(构造函数中只是绑定了数据,并没指定谁是“老大”)
测试代码:
很容易看出了页面this.ddl.Text是支持状态保持的。(想知道是不是用ViewState缓存的,可以将所有与Text2的都注释掉(因为只有它用到了ViewState),运行后再切换到源代码看,可以看到<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJMjExNTc5ODc3ZGTIDtb6UHScxcNJ3YuXDOITD+htVg==" />,说明MS的这个属性也是用ViewState来保存的。不信???那用跟踪再看看(其实我自己也不相信,也许是其他什么状态保存的,那么多字,凭什么是我ddl.Text呢?))(Trace的开关,请点页面右键属性,在属性面板里面有Trace设置为true,再运行就可以了。)
红色标识的地方我想应该可以证实这一点了。
不过不管它是用什么方法保存的,目的已经达到了,事实上大部分控件的属性类似ddlText的方式设置就可以了。另外我们需要在构造函数中补充一句this.ViewState["Text2"] = string.Empty;理由就是如果我们没有设置就去读取的话,会报错的。呵呵,这个习惯可以用于所有的Object,当然,视具体的类型而让等号右边的初始值有所不同,比如可以是new myObject();
属性设置差不多了吗?差不多了,但是属性一多,在属性面板里面,似乎排列地很难找,我们还是归类一下,加上元数据(具体请参考资料:http://www.cnblogs.com/Clingingboy/archive/2006/08/08/470980.html);
代码:(全CompositeControl2.cs)
CompositeControl2.aspx.cs
CompositeControl2.aspx.designer.cs
在上一篇中,我们介绍了复合控件的一些基础知识,并且利用“组合”的观念将各“类”控件简单地组合在了一起,我们通过public或者protected访问权限或者通过属性将我们的控件暴露给外部,并通过点运算符在外部对其进行调用。这样的做法虽然可行,但却不一定是我们想要的,如果你和我一样想要一个使用起来像微软提供的WebControl一样便捷的看上去更像整体的控件的话,那就请继续阅读吧。
微软控件和我们的现在的(文章1中所做出来的)控件有什么差别呢?哪些是我们需要做的呢?只有弄清楚这两个问题我们才知道我们接下来要做的工作。
打开工具箱,我们可以看到我们的控件和微软的控件都摆放在上面,拖拽的方式我们已经与其无异了,但是打开属性面板我们发现我们拥有的多是来自其父类的属性,事件列表里面也都是一些父类的事件,有没有想过在这些地方可以直接调用到子控件的属性和事件呢?(而不是和文章一中用点运算符去对待它们。)
本文就和大家一同将属性添加进来。
我们知道,控件就是一个特殊的类,于是你可能很容易就想到如下的代码:
private string text;
public string Text
{
get
{
return text;
}
set
{
text = value;
}
}
public string Text
{
get
{
return text;
}
set
{
text = value;
}
}
让我补充一个测试代码用来输出
if (!IsPostBack)
{
//在页面第一次加载的时候才进行赋值,页面回发将不重新赋值。
this.CompositeControl2_1.Text1 = "Hello world!(Text1)";
}
//输出当前控件属性中的值
WriteLine(this.CompositeControl2_1.Text1);
在页面上加一个标准->Button,用以触发页面回发。{
//在页面第一次加载的时候才进行赋值,页面回发将不重新赋值。
this.CompositeControl2_1.Text1 = "Hello world!(Text1)";
}
//输出当前控件属性中的值
WriteLine(this.CompositeControl2_1.Text1);
我们可以看见页面运行后出现了Hello world!(Text1),在点完Button后,文字不见了。
想像一下我们的Label控件的Text会不会这样呢?很显然是不会的,页面再刷,只要Text不被重新赋值或者改写,将不会改变它的值。因此属性的这种传统写法就显得力不从心了。那问题出在哪里呢?问题就在这个页面回发中,页面回发后,在服务器发生了什么呢?一个相同的页面被new了出来,然后回传给了客户端,因此你看到了同样的页面,但是我们希望我们在点按钮前的页面和按钮后的页面是一致的,也就是你对回发前的部分乃至全部修改你希望将它们一直保持下来。而一个全新的页面就像你买了一件新衣服一样,永远不是你之前穿过的那一件了(假设被你用蓝笔划过),但是ASP.NET的机制让我们不得不得到一件新衣服,就像你把朋友的衣服弄没了,你又买了一个新的一样,你为了不让他发现,又拿了蓝笔在上面划过,于是他没有发觉!同样的道理,只要将你在这件“衣服”上留下的所有特征保存起来,你就可以所谓地克隆出一件一样的衣服了,那么回发也就不会导致一个new的让人诧异的结果了。如何做呢?看下面改进后的代码:
public string Text2
{
get
{
return this.ViewState["Text2"].ToString();
}
set
{
this.ViewState["Text2"] = value;
}
}
{
get
{
return this.ViewState["Text2"].ToString();
}
set
{
this.ViewState["Text2"] = value;
}
}
这样就可以了么?当然!看看测试代码(对比照片):
private void WriteLine(string str)
{
if (str != null)
{
this.Response.Write(str + "<br>");
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
//在页面第一次加载的时候才进行赋值,页面回发将不重新赋值。
this.CompositeControl2_1.Text1 = "Hello world!(Text1)";
this.CompositeControl2_1.Text2 = "Hello world!(Text2)";
}
//输出当前控件属性中的值
WriteLine(this.CompositeControl2_1.Text1);
WriteLine(this.CompositeControl2_1.Text2);
}
结果:{
if (str != null)
{
this.Response.Write(str + "<br>");
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
//在页面第一次加载的时候才进行赋值,页面回发将不重新赋值。
this.CompositeControl2_1.Text1 = "Hello world!(Text1)";
this.CompositeControl2_1.Text2 = "Hello world!(Text2)";
}
//输出当前控件属性中的值
WriteLine(this.CompositeControl2_1.Text1);
WriteLine(this.CompositeControl2_1.Text2);
}
Hello world!(Text1)
Hello world!(Text2)
值就是通过ViewState被保存下来的,当然如果你觉得ViewState的效率不高的话可以换用其他你觉得高的缓存,这里只是告诉你页面回发的时候需要找个介质来把状态暂时地保存起来,等新的页面实例生成后,记得把状态还给人家。呵呵。
以上说了这么多是控件自己的属性。那么如何给子控件的属性赋值呢?
其实这更简单,这就是你如何看待子控件和你的复合控件的关系的问题了。比较正确的理解应该是,复合控件是“关联”子控件的,(也就UML中的那个箭头从复合控件中指向了子控件),在复合控件的编写过程中,子控件实际就是一个被用到的Object,那么这个Object的对象自然为客户程序(这里就是指复合控件的那个cs文件)所用了。你或许会问,那如何将它缓存呢?回顾刚才的代码块1:
private string text1; //数据丢失的根源
public string Text1 //不是数据丢失的根源
{
get
{
return text1;
}
set
{
text1 = value;
}
}
public string Text1 //不是数据丢失的根源
{
get
{
return text1;
}
set
{
text1 = value;
}
}
注意我的注释,也许你之前的理解会不小心变成对我们的属性Text1没有保存状态而表示遗憾,那么现在你要清晰真凶是text1而不是Text1了。这样也更帮助我们认识为何用ViewState了。了解了这个,我们就可以很容易的写出下面的代码:
//直接返回子控件的属性
public string DDLText
{
get
{
return this.ddl.Text;
}
set
{
this.ddl.Text = value;
}
}
public string DDLText
{
get
{
return this.ddl.Text;
}
set
{
this.ddl.Text = value;
}
}
而我们关于缓存的疑问可以这么被问出来:“this.ddl.Text是否能在PostBack后保存住呢?”很显然MS的控件处理了这些,正如我们用ViewState来保存状态一样,微软也用这个特性来保存值在页面回发时候的视图状态的。
我在示例中添加了“数据源”并让创建的时候指定一个属于该数据源的值赋值给我们DDLText,在回发后,这个值没有重新再传,而是希望我们的this.ddl.Text能够保存状态,将它知道的告诉我们。(构造函数中只是绑定了数据,并没指定谁是“老大”)
//CompositeControl2.cs
/// <summary>
/// 构造函数,为本示例提供了数据源
/// </summary>
public CompositeControl2()
{
ddl.DataSource = GetData();
ddl.DataBind();
}
……
//直接返回子控件的属性
public string DDLText
{
get
{
return this.ddl.Text;
}
set
{
this.ddl.Text = value;
}
}
……
private ListItemCollection GetData()
{
ListItemCollection lis = new ListItemCollection();
lis.Add(new ListItem("Hello world!(DDLText)A", "aaa"));
lis.Add(new ListItem("Hello world!(DDLText)B", "bbb"));
lis.Add(new ListItem("Hello world!(DDLText)C", "ccc"));
lis.Add(new ListItem("Hello world!(DDLText)D", "ddd"));
return lis;
}
/// <summary>
/// 构造函数,为本示例提供了数据源
/// </summary>
public CompositeControl2()
{
ddl.DataSource = GetData();
ddl.DataBind();
}
……
//直接返回子控件的属性
public string DDLText
{
get
{
return this.ddl.Text;
}
set
{
this.ddl.Text = value;
}
}
……
private ListItemCollection GetData()
{
ListItemCollection lis = new ListItemCollection();
lis.Add(new ListItem("Hello world!(DDLText)A", "aaa"));
lis.Add(new ListItem("Hello world!(DDLText)B", "bbb"));
lis.Add(new ListItem("Hello world!(DDLText)C", "ccc"));
lis.Add(new ListItem("Hello world!(DDLText)D", "ddd"));
return lis;
}
测试代码:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
//在页面第一次加载的时候才进行赋值,页面回发将不重新赋值。
this.CompositeControl2_1.Text1 = "Hello world!(Text1)";
this.CompositeControl2_1.Text2 = "Hello world!(Text2)";
this.CompositeControl2_1.DDLText = "Hello world!(DDLText)C"; //这样写是因为Text属性要求值已经存在于下拉列表框中
}
//输出当前控件属性中的值
WriteLine(this.CompositeControl2_1.Text1);
WriteLine(this.CompositeControl2_1.Text2);
WriteLine(this.CompositeControl2_1.DDLText);
/*DDLText的状态也将在我们的复合控件中被保存:)*/
}
结果:{
if (!IsPostBack)
{
//在页面第一次加载的时候才进行赋值,页面回发将不重新赋值。
this.CompositeControl2_1.Text1 = "Hello world!(Text1)";
this.CompositeControl2_1.Text2 = "Hello world!(Text2)";
this.CompositeControl2_1.DDLText = "Hello world!(DDLText)C"; //这样写是因为Text属性要求值已经存在于下拉列表框中
}
//输出当前控件属性中的值
WriteLine(this.CompositeControl2_1.Text1);
WriteLine(this.CompositeControl2_1.Text2);
WriteLine(this.CompositeControl2_1.DDLText);
/*DDLText的状态也将在我们的复合控件中被保存:)*/
}
很容易看出了页面this.ddl.Text是支持状态保持的。(想知道是不是用ViewState缓存的,可以将所有与Text2的都注释掉(因为只有它用到了ViewState),运行后再切换到源代码看,可以看到<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJMjExNTc5ODc3ZGTIDtb6UHScxcNJ3YuXDOITD+htVg==" />,说明MS的这个属性也是用ViewState来保存的。不信???那用跟踪再看看(其实我自己也不相信,也许是其他什么状态保存的,那么多字,凭什么是我ddl.Text呢?))(Trace的开关,请点页面右键属性,在属性面板里面有Trace设置为true,再运行就可以了。)
窗体集合 |
|||||||||
---|---|---|---|---|---|---|---|---|---|
名称 | 值 | ||||||||
__VIEWSTATE | /wEPDwUJMjExNTc5ODc3ZGTIDtb6UHScxcNJ3YuXDOITD+htVg== | ||||||||
CompositeControl2_1$ctl00 | Hello world!(DDLText)C | ||||||||
Button1 | PostBack | ||||||||
__EVENTVALIDATION | /wEWBgLXnci3AQKjppO3BAKjppe3BAKjpuu3BAKjpo+3BAKM54rGBkO/ipmvmKnOlKB/J6wPGQTJiRbG |
不过不管它是用什么方法保存的,目的已经达到了,事实上大部分控件的属性类似ddlText的方式设置就可以了。另外我们需要在构造函数中补充一句this.ViewState["Text2"] = string.Empty;理由就是如果我们没有设置就去读取的话,会报错的。呵呵,这个习惯可以用于所有的Object,当然,视具体的类型而让等号右边的初始值有所不同,比如可以是new myObject();
属性设置差不多了吗?差不多了,但是属性一多,在属性面板里面,似乎排列地很难找,我们还是归类一下,加上元数据(具体请参考资料:http://www.cnblogs.com/Clingingboy/archive/2006/08/08/470980.html);
代码:(全CompositeControl2.cs)
CompositeControl2.aspx
CompositeControl2.aspx.cs
CompositeControl2.aspx.designer.cs
posted on 2007-07-03 01:24 volnet(可以叫我大V) 阅读(3138) 评论(3) 编辑 收藏 举报