记得一直都习惯于在*.aspx.cs页面中直接使用control的ID来取值赋值,例如:
string name = textBoxName.Text;
这里textBoxName就是定义在TextBox control中的ID:
<asp:TextBox runat="server" ID="textBoxName" />
但是,忽然有一天,碰到了意外情况。首先第一个特征是当我输入t字符准备让VS自动弹出control列表让我选择的时候,发现,不起作用了。因为VS时不时的会罢工,所以也并未在意。于是手工输入。结果是,编译时报错说:
Error 1 The name 'textBoxName' does not exist in the current context C:\Documents and Settings\peisong_xiong\My Documents\Visual Studio 2008\Projects\WebApplication1\WebApplication1\Default.aspx.cs 18 27 WebApplication1
查看*.aspx文件,内容如下:
<asp:Repeater ID="Repeater1" runat="server"> <ItemTemplate> <asp:TextBox runat="server" ID="textBoxName" /> </ItemTemplate></asp:Repeater>
WHY?因为Page.Controls中,并不会包含<*Template>里的control。至于为什么,根据我的猜测,动态添加的控件,由于存在runtime时可能生成也可能不生成的逻辑条件,例如上例中的Repeater中可能一个数据都没有,结果就是没有ItemTemplate,自然也就没有textBoxName。另外一种情况是,如果你的模板中有多条数据,那么就会有同一个控件的多个instance出现,这时候怎么可能静态分配一个ID呢?(感谢玄天尊的小屋的提醒 : ) )另外一种情况,比如你代码里这么写:
if(showMenu){ // Generate Menus HyperLink menu = new HyperLink(); menu.ID = "Menu1"; // ... other menus}
也就是说,如果showMenu是false,那么一个菜单项都不会生成,结果你引用Menu1.xxx属性怎么会不出错呢?
Solution
.NET提供了Object.FindControl方法,同时也可以通过Object.Controls[i]访问控件列表。例如Page控件下含有Form控件,Form控件下含有Repeater1控件等等以此类推。因此要找到某个确切的控件,需要用递归的方式遍历整个控件树。代码如下:
public static T FindControl<T>(Control root, string id) where T : Control { if (root.ID == id) { return root as T; } foreach (Control ctl in root.Controls) { T control = FindControl<T>(ctl, id); if (control != null) { return control as T; } } return null; }
万能用法当然是调用FindControl<T>(this, id); 这里this代表Page。但是从性能上着想,最好是从其Parent Control ID开始调用。回到上面那个例子,当然就是如此调用了:
TextBox tbName = FindControl<TextBox>(Repeater1, "textBoxName");