在Asp.Net中动态添加控件

11

我正在学习ASP.NET,但是由于我以前一直是PHP开发人员,现在要学习ASP.NET,有些困难。也许是我试图强行将框架用于某些其不适合的情况,因此我想学习如何用“正确的方式”进行。:-)

我的问题是如何在运行时以编程方式向页面添加控件。据我所知,您需要在page_init中创建控件,否则它们会在下一个PostBack时消失。但很多时候,我面临的问题是不知道在page_init中添加哪些控件,因为这取决于上一个PostBack中的值。

一个简单的场景可能是在设计器中添加了一个下拉控件的表单。下拉列表设置为AutoPostBack。当PostBack发生时,我需要根据下拉列表控件的选定值呈现一个或多个控件,并且最好让这些控件表现得像已经通过设计添加的那样(即“当提交回来时,表现正常”)。

我是否走错了方向?


我认为这里很多人都引导你走了错误的道路;虽然他们的答案在技术上是正确的,但我真的不会这样做。相比于MultiView或其他更优雅的解决方案,这种方法过于复杂了。 - Domenic
9个回答

9
我同意这里其他观点的说法“如果可以避免动态创建控件,那就避免吧...”(by @Jesper Blad Jenson aka),但是我曾经想出了一个关于动态创建控件的技巧。
问题变成了先有鸡还是先有蛋。你需要ViewState来创建控件树,而你需要已经创建好的控件树才能获取ViewState。嗯,这几乎是正确的。有一种方法可以在树的其余部分被填充之前仅仅获取到你的ViewState值。那就是通过重写LoadViewState(...)SaveViewState(...)
在SaveViewState中存储您希望创建的控件:
protected override object SaveViewState()
{
    object[] myState = new object[2];
    myState[0] = base.SaveViewState();
    myState[1] = controlPickerDropDown.SelectedValue;

    return myState
}

当框架调用你的“LoadViewState”重写时,你将获得从“SaveViewState”返回的确切对象:
protected override void LoadViewState(object savedState) 
{
    object[] myState = (object[])savedState;

    // Here is the trick, use the value you saved here to create your control tree.
    CreateControlBasedOnDropDownValue(myState[1]);

    // Call the base method to ensure everything works correctly.
    base.LoadViewState(myState[0]);
}

我已经成功地使用这种方法创建了ASP.Net页面,其中一个DataSet被序列化到ViewState中以存储整个数据网格的更改,使用户可以通过PostBack进行多次编辑,并最终在单个“保存”操作中提交所有更改。

这似乎是个好主意。我会考虑一段时间,看能否将其融入我的思维方式中,或者反过来。 :-) - mlarsen

3
您必须将控件添加到OnInit事件中,这样视图状态就会被保留。不要使用if(ispostback),因为控件必须每次都添加,即使是在postback中也是如此!
视图状态的(反)序列化发生在OnInit之后和OnLoad之前,因此如果在OnInit中添加动态添加的控件,则您的视图状态持久性提供程序将看到它们。
但在您描述的情况下,可能多视图或简单的隐藏/显示(可见属性)将是更好的解决方案。
这是因为在OnInit事件中,当您必须读取下拉列表并添加新控件时,视图状态尚未被读取(反序列化),因此您不知道用户选择了什么!(您可以执行request.form(),但那感觉有点不对)。

2

在解决这个问题一段时间后,我总结出了以下几条准则,它们似乎起到了作用,但也可能因情况不同而不适用。

  • 尽可能使用声明式控件
  • 尽可能使用数据绑定
  • 理解ViewState的工作原理
  • Visibility属性很有用
  • 如果必须在事件处理程序中添加控件,请使用Aydsman的技巧,在重写的LoadViewState中重新创建控件。

真正理解ViewState 是必读的。

通过实例理解动态控件 展示了一些使用数据绑定而非动态控件的技巧。

真正理解动态控件 还澄清了可以用来避免动态控件的技术。

希望这能帮助有同样问题的人。


+1 特别是“可见性属性可以大有作为”,让我意识到我可以让我的问题更简单。 - Alex KeySmith

1

如果您确实需要使用动态控件,则以下内容应该有效:

  • 在 OnInit 中,重新创建与上一次请求完成时页面上的完全相同的控件层次结构。(如果这不是最初的请求,当然)
  • 在 OnInit 之后,框架将从上一个请求中加载视图状态,现在所有控件都应处于稳定状态。
  • 在 OnLoad 中,删除不需要的控件并添加必要的控件。此时还必须以某种方式保存当前控件树,以在下一个请求期间使用第一步。您可以使用会话变量来指示如何创建动态控件树。我甚至曾经在会话中存储整个 Controls 集合(请放下你的火炬,这只是为了演示)。

重新添加您不需要并且在 OnLoad 中将被删除的“过时”控件似乎有点古怪,但 Asp.Net 实际上并没有考虑到动态控件创建。如果在视图状态加载期间未保留完全相同的控件层次结构,则各种难以找到的错误将开始潜伏在页面中,因为旧控件的状态将加载到新添加的控件中。

阅读 Asp.Net 页面生命周期的相关知识,特别是视图状态的工作原理,问题将变得清晰明了。

编辑:这是一篇非常好的文章,关于视图状态的行为以及在处理动态控件时应考虑的事项:<链接>


0

好的。如果你可以避免动态创建控件,那就这么做 - 否则,我会使用 Page_Load 而不是 Page_Init,但是不要将东西放在 If Not IsPostBack 中,而是直接在方法中设置。


0

我认为这里的答案在于使用MultiView控件,例如下拉列表可以在多视图之间切换。

你甚至可以将多视图的当前视图属性数据绑定到下拉列表的值上!


0

啊,这就是 ASP.NET Web Forms 的泄漏抽象的问题所在。

也许你会对看看 ASP.NET MVC 感兴趣,这是用于创建 stackoverflow.com 网站的技术?对于你来说,这应该更容易适应,因为你来自 PHP(因此,在处理 HTML 和 JavaScript 时需要踩油门)的背景。


是的,ASP.NET MVC看起来非常有前途,而且更接近我习惯的工作方式。不幸的是,我的同事们都有WinForms开发背景,我不确定我能否说服他们采用ASP.NET MVC。 - mlarsen

0
唯一正确的答案是由Aydsman给出的。LoadViewState是添加动态控件的唯一位置,其中它们的视图状态值将在重新创建时恢复,并且您可以访问视图状态以确定要添加哪些控件。

0
我在书籍“Pro ASP.NET 3.5 in C# 2008”中的动态控件创建部分看到了这个:

如果您需要多次重新创建控件,则应在Page.Load事件处理程序中执行控件创建。 这样做的另一个好处是允许您在动态控件中使用视图状态。 即使视图状态通常在Page.Load事件之前恢复,如果您在Page.Load事件处理程序中为控件创建控件,则ASP.NET将在Page.Load事件处理程序结束后应用任何视图状态信息。 这个过程是自动的。

我没有测试过这个,但你可以研究一下。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接