在母版页里使用FindControl的困惑
在母版里使用FindControl
通常在内容页里会使用不止一个Content控件,在这种情况下. 我们可能需要让不同Content的里的控件进行“通信”,本节就来介绍这些问题。
例如下面是一个简单的母版simple.master。
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="simple.master.cs" Inherits="simple" %>
<!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>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:contentplaceholder id="ContentUp" runat="server">
</asp:contentplaceholder>
<asp:contentplaceholder id="ContentDown" runat="server">
</asp:contentplaceholder>
</div>
</form>
</body>
</html>
在这个母版里使用了两个ContentPlageHolder控件,其ID分别为ContentUp和ContentDown,这里的名称表示将来我想将内容页分为上半部和下半部。
为了使用该母版,我建立了一个 内容页 FindControl_Masterpage.aspx,代码如下:
<%@ Page Language="C#" Trace="true" MasterPageFile="~/simple.master" AutoEventWireup="true" CodeFile="FindControl_Masterpage.aspx.cs" Inherits="FindControl_Masterpage" Title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentUp" Runat="Server">
<asp:Label runat="server" ID="lblInfo"></asp:Label>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentDown" Runat="Server">
<asp:TextBox ID="txtInfo" runat="server"></asp:TextBox>
<asp:Button ID="btnSubmit" runat="server" Text="Submit" OnClick="btnSubmit_Click" ></asp:Button>
</asp:Content>
这里的内容页很简单,在ContentUp里放置了一个ID为lblInfo的标签,在ContentDown里放置了一个ID问txtInfo的文本框和ID为btnSubmit的按钮。
现在我想在文本框里输入文本,然后单击按钮,让Label标签显示输入的文本,所以在btnSubmit_Click的事件里输入代码如下:
protected void btnSubmit_Click(object sender, EventArgs e)
{
lblInfo.Text = txtInfo.Text;
}
上面的方法在内容页运行时工作的很好,但是有时候我们的要求可能并不是这么简单。比方说,我在Content1中定义有一个ObjectDataSource,在Content2中定义有一个TextBox,我想将ObjectDataSource的SelectParameter设定到TextBox上,那么此时就需要获取TextBox控件。您可能很容易编写如下代码:
protected void btnSubmit_Click(object sender, EventArgs e)
{
Label lblLabel = (Label)Page.FindControl("lblInfo");
lblLabel.Text = txtInfo.Text;
}
我么的想法是利用Page的FindControl控件查扎ID为lblInfo的标签,然后设置其Text属性。然而很遗憾,如果您使用上面代码,在运行时将出现如下的提示错误:
也许有人认为这是因为Label和Button控件在不同的Content所致,但是虽然TextBox和Button在同一个Content,那么你编写如下代码同样会出现上面的问题:
protected void btnSubmit_Click(object sender, EventArgs e)
{
TextBox txtTextBox = (TextBox)Page.FindControl("txtInfo");
txtTextBox.Text = "hello,world";
}
那么为什么会出现上面错误呢?前面介绍了母版页和内容页运行原理时曾经说过,用户最终请求的是内容页,母版页和内容页在之中处理请求时,母版页会融合到内容页里,我们可以利用跟踪技术查看这种结果,此时在内容页里添加Trace属性如下:
<%@ Page Language="C#" Trace="true" … …%>
这样在运行FindControl_MasterPage.aspx时,我们可以看到下面的跟踪效果
从图中可以看到,在页面Page控件树中,ContentUp和ContentDown直接在Page的控件树下,而Label和TextBox,Button又分别在ContentUp和ContentDown下,那么为什么FindControl找不到Label,TextBox和Button呢?
这时,我们需要重新查看一下FindControl的定义,MSDN给出的解释如下:在页命名容器中搜索带指定标识符的服务器控件,但是请注意其备注的说明:
FindControl 方法只搜索页的直接或顶级容器;它不在页所包含的命名容器中递归搜索控件。
也就是FindControl可以找到ContentUp和ContentDown但是找不到Label,Text和Button。如果更显示的表示如下图
FindControl不会在ContentUp/ContentDown查找其子控件,这就是为什么会出现上面错误的原因。
当然,这里的讨论并不完全准确,如果您仔细查看页面跟踪结果可以发现ContentUp和ContentDown应该算是aspnetForm的子控件,但是为什么它就可以查找aspnetForm下的子控件呢?
这里设计到控件的定义,事实上FindControl只查找未实现INamingContainer 接口的控件。而aspnetForm则未实现InamingContainer接口,就是这个原因。(判断一个控件是否实现InamingContainer接口简单的说就是在页面上能否重复使用,例如TextBox实现了InamingContainer结果,那么在一个Web页面上如下的代码是正确的
<asp:TextBox ID="Textbox1" runat="server"></asp:TextBox>
<asp:TextBox ID=" Textbox2" runat="server"></asp:TextBox>
而Form未实现,所以如果你在一个Web页面上同时使用了两个Form控件,如下
<form id="form1" runat="server"></form>
<form id="form2" runat="server"></form>
则这种写法是错误的。
既然FindControl可以在控件内部嵌套搜索,所以我们就很容易获取TextBox,Label和Button,如下:
获取
protected void btnSubmit_Click(object sender, EventArgs e)
{
ContentPlaceHolder cphUp;
Label lblLabel;
cphUp = (ContentPlaceHolder)Master.FindControl("ContentUp");
lblLabel = (Label)cphUp.FindControl("lblInfo");
ContentPlaceHolder cphDown;
cphDown=(ContentPlaceHolder)Master.FindControl("ContentDown");
TextBox txtTextBox;
Button btnButton;
txtTextBox = (TextBox)cphDown.FindControl("txtInfo");
if (cphUp != null && cphDown!=null)
{
if (lblLabel != null && txtTextBox != null)
lblLabel.Text = txtTextBox.Text;
}
}
这样就可以实现在内容页上获取控件通信了。