游民家园

leafyoung v.s. dotnet

ASP.NET动态创建控件之绝境求生

在ASP.NET中动态创建一个控件总是不那么顺利,特别是当对页面的Life Cycle不是那么了然的情况下!这里简单描述一下要求,然后提供一个解决方案,大家看看有没有更好的Idea,如果有的话就是我的大幸了,呵呵!

要求:页面上有一个Add按钮,每点击一次该按钮,页面上动态创建一个WebPartZone!
提醒:WebPartZone只能在OnInit或之前才能创建,否则报异常!

大家都知道,按钮的点击事件是在RaisePostbackEvent时触发的,这意味着点击事件在OnLoad阶段之后才执行,远远落后于OnInit阶段,而且ViewState在OnLoad时才准备好,OnInit以及之前的阶段根本就不能使用ViewState!如果试图在按钮点击事件里面创建WebPartZone等控件,唯一的后果就是页面出错;而如果在OnInit里面创建控件,由于ViewState没有准备好,那么有些数据比如当前需要创建的个数(存在ViewState里面)就无法获得!

目前对这个问题我还没有找到什么好的解决方案,经过实验,勉强得出一个不怎么优雅的方案,就是利用HiddenField保存数据,然后直接使用Request.Form["XXX"]在OnInit阶段取得数据;而判断是否点击按钮也是通过Request.Form是否存在对应数据来判断的!废话不多说了,大家看看代码吧!

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    
<title>Untitled Page</title>
</head>
<body>
    
<form id="form1" runat="server">
        
<asp:ScriptManager ID="ScriptManager1" runat="server" />
        
<div>
            
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
            
<br />
            
<asp:Button ID="btnAdd" runat="server" Text="Add" OnClick="btnAdd_Click" />&nbsp;
            
<asp:HiddenField ID="hfCount" runat="server" Value="0" />
        
</div>
    
</form>
</body>
</html>

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class _Default : System.Web.UI.Page 
{
    
private int _count = 0;

    
protected override void OnInit(EventArgs e)
    
{
        
base.OnInit(e);

        
// 取得以前已创建控件的个数
        if (!String.IsNullOrEmpty(this.Request["hfCount"]))
        
{
            _count 
= Convert.ToInt32(this.Request["hfCount"]);
        }


        
// 假如按下“Add”按钮,那么count加一
        string target = this.Request["btnAdd"];
        
if (target == "Add")
        
{
            _count
++;
        }


        
// 动态创建控件
        for (int i = 0; i < _count; i++)
        
{   // 这里以TextBox为例,实际上需要创建的是WebPartZone
            TextBox newTextbox = new TextBox();
            newTextbox.ID 
= "TXT" + i.ToString();
            
this.PlaceHolder1.Controls.Add(newTextbox);
        }

    }


    
protected void Page_Load(object sender, EventArgs e)
    
{
        hfCount.Value 
= _count.ToString();
    }


    
protected void btnAdd_Click(object sender, EventArgs e)
    
{
        
// 不能在此添加WebPartZone控件,只能在OnInit或之前,否则报异常
    }

}

希望大家能提出更好的解决方案,我拭目以待,呵呵@_@

posted on 2007-04-04 11:27 游民一族 阅读(3565) 评论(10)  编辑 收藏 网摘

评论

#1楼 2007-04-04 11:50 臭石头      

关注,我也常碰到这个问题   回复  引用  查看    

#2楼 2007-04-04 12:12 [David.S]      

园子里这个问题的原理已经说的很好了,你是用QueryString来存放状态的,可以试试用ViewState存放是否创建了控件。   回复  引用  查看    

#3楼[楼主] 2007-04-04 12:27 游民一族      

@[David.S]
???你看文章了么?是我写的不够清楚还是你理解得有问题呢?!郁闷

还有,我从来就不关心原理的,我是极端实用主义者,哈哈◎_@
  回复  引用  查看    

#4楼 2007-04-04 16:20 Clark Zheng      

mark,有时间再研究   回复  引用  查看    

#5楼 2007-04-04 19:34 Justin      

关注,没有好的办法暂时   回复  引用  查看    

#6楼 2007-04-06 21:30 3Q[未注册用户]

我也遇到过同样的问题,但是没有想到用这样的方法去解决!谢谢!受教了!   回复  引用    

#7楼 2007-04-25 21:56 清晨风      

如果控件是动态加在容器控件(Panel、PlaceHolder)里的话,好象是可以的,所以你上面的这样写
protected void btnAdd_Click(object sender, EventArgs e)
{
TextBox newTextbox = new TextBox();
newTextbox.ID = "txt";
this.PlaceHolder1.Controls.Add(newTextbox);
}
是可以的。
  回复  引用  查看    

#8楼[楼主] 2007-04-27 09:37 游民一族      

@清晨风
拜托看清楚再发言:"提醒:WebPartZone只能在OnInit或之前才能创建,否则报异常!"
  回复  引用  查看    

#9楼 2008-01-16 14:15 Zetmandd[未注册用户]

用處很大,也遇到同樣的問題.感謝!!   回复  引用    

#10楼 2008-08-10 12:46 昏天黑地[未注册用户]

可以尝试使用PageStatePersister的派生类,比如HiddenFieldPageStatePersister类,它的构造函数接受一个Page类型的参数,可以把你的页面传进去。然后调用其Load方法。这样从它的ViewState属性里可以得到一个Pair类型的对象。然后使用Page类的LoadViewState方法把这个Pair对象的相应属性传进去。经过试验,我发现可以使用((Pair)pairObject.Second).First获得Page.LoadViewState方法能够接受的ArryList类型的数据。
示意性的代码类似下面这样。
HiddenFieldPageStatePersister psp = new HiddenFieldPageStatePersister(this.Page);
psp.Load();
if (psp.ViewState != null)
{
Pair pairObject = (Pair)((Pair)psp.ViewState).Second;
this.LoadViewState(pairObject .First);
}
我试过了能行,执行完这些代码后ViewState里就有东西了。本质上这种方法是手动在Init之前加载一遍ViewState的方法。但因为没有做进一步研究,还不清楚是否会带来什么副作用。是否使用楼主自己斟酌,这里只提供一个参考。
另外,如何知道相应按钮被点击的逻辑似乎没有什么其它办法。最直接的方式就是从Request里面取,我觉得即直接又简单。不过有一点值得说明的就是最好使用相应按钮的UniqueID来查询Request.Form对象(类似Request.Form[someButton.UniqueID]),这样即使你的代码放在很深的控件嵌套层次中也照样可以工作。
  回复  引用    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 699465




相关文章:

相关链接: