ASP.NET 自定义控件:何时调用 LoadPostData()?

发布于 2024-09-09 03:00:47 字数 590 浏览 3 评论 0原文

我开发了一个扩展 ListBox 的自定义控件。这个想法是,控件“记住”客户端发生的对其元素的修改,例如作为 AJAX 请求的结果。

它的工作方式是控件还呈现隐藏输入,并且 AJAX 请求的结果存储在隐藏输入中。这将被回发,并且控件的 LoadPostData() 方法会查找隐藏输入,如果隐藏输入有数据,则从中创建 ListItem 集合。

只要用户从列表框中进行选择,此功能就可以完美运行。。如果没有,则不会调用 LoadPostData() 方法,因此不会创建新的 ListItem 集合。 (我已经使用调试器建立了这一点。)

我假设仅当 POST 数据集合包含与控件的 UniqueID(即 HTML 中的“name”属性)相对应的数据时才会调用 LoadPostData 方法。如果用户尚未从列表框中进行选择,则列表框的 UniqueID 的发布数据中不会包含任何内容,并且不会调用 LoadPostData()。这是正确的吗?

任何人都可以建议我如何确保每次回发都会调用我的自定义 ListBox 的 LoadPostData() 方法,无论用户是否做出选择?

预先感谢 - 我真的被这个问题困扰了。

大卫

I have developed a custom control that extends ListBox. The idea is that the control 'remembers' modifications to its elements which occurred client-side, e.g. as a result of an AJAX request.

The way it works is that the control also renders a hidden input, and the result of the AJAX request is stored in the hidden input. This is posted back, and the control's LoadPostData() method looks for the hidden input, and if the hidden input has data, creates the ListItem collection from it.

This works perfectly so long as the user has made a selection from the list box. If they have not, the LoadPostData() method doesn't get called, and consequently the new ListItem collection is not created. (I've established this using the debugger.)

I assume that the LoadPostData method is only called if the POST data collection includes data corresponding to the control's UniqueID (i.e. 'name' attribute in HTML). If the user hasn't made a selection from the list box, nothing is included in the post data for the list box's UniqueID and LoadPostData() isn't called. Is that correct?

Can anyone suggest how I can ensure that my custom ListBox's LoadPostData() method is called every postback regardless of whether the user has made a selection?

Thanks in advance - I'm really stuck with this one.

David

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

但可醉心 2024-09-16 03:00:47

我有点晚了,但是,为了将来的参考,我是如何完成类似的事情的……

我的控件是一个使用节点模板的树。我处理的问题是如何捕获客户端对节点展开/折叠状态的更改。最终的工作是:

在 CreateChildControls 中将隐藏字段添加到我的根控件的控件集合中。

protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
{
    ...
    _cdExpanded = new HiddenField();
    _cdExpanded.ID = "cdExpanded";
    this.Controls.Add(_cdExpanded);
    ...
}

在 OnInit 调用中

protected override void OnInit(EventArgs e)
{
    ...
    Page.RegisterRequiresPostBack(this);
    ...
}

在 LoadPostData 中查找帖子集合中与隐藏字段的 UniqueID(不是 ClientID)相匹配的值:

public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
{
    ...
    string cdExpanded = postCollection[_cdExpanded.UniqueID];
    ...
}

在各个节点的类中,我有代码通过调用来填充切换按钮的 onclick 事件JavaScript 函数将基本控件的 ID 和各个节点作为参数。

    string ToggleScript
    {
        get
        {
            return "ToggleNode('" + this.ClientID + "', '" + _TreeRoot.ClientID + "');";
        }
    }
    protected override void Render(HtmlTextWriter writer)
    {
        ...
        if (this.HasChildren)
        {
            writer.AddAttribute("onclick", ToggleScript);
        }
        ...
    }

这使得通过 getElementById 查找隐藏字段相当容易:

function ToggleNode(nodeID, treeID) {
var cdExpanded = document.getElementById(treeID + "_cdExpanded");
...
}

然后 JavaScript 根据发生的事件的需要修改隐藏字段的值。当我们返回服务器时,我能够解析该字段的内容,并在再次渲染之前根据需要修改控件状态。 (注意:我实际上使用 3 个隐藏字段来跟踪不同的事件,但概念是相同的)

希望这对将来的其他人有帮助......

I am a little late jumping in on this but, just for future reference, here is how I accomplished something similar…

My control is a tree that uses templates for the nodes. The issue where I was dealing with this was how to capture the client side changes to the expanded/collapsed state of the nodes. What ended up working was:

In CreateChildControls add the hidden field to the controls collection of my root control.

protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
{
    ...
    _cdExpanded = new HiddenField();
    _cdExpanded.ID = "cdExpanded";
    this.Controls.Add(_cdExpanded);
    ...
}

In OnInit call

protected override void OnInit(EventArgs e)
{
    ...
    Page.RegisterRequiresPostBack(this);
    ...
}

In LoadPostData look for a value in the post collection that matches the UniqueID (not ClientID) of the hidden field:

public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
{
    ...
    string cdExpanded = postCollection[_cdExpanded.UniqueID];
    ...
}

Within the classes for the individual nodes I have code which populates the onclick events of my toggle buttons with a call to a JavaScript function which takes the ID of the base control and the individual nodes as arguments.

    string ToggleScript
    {
        get
        {
            return "ToggleNode('" + this.ClientID + "', '" + _TreeRoot.ClientID + "');";
        }
    }
    protected override void Render(HtmlTextWriter writer)
    {
        ...
        if (this.HasChildren)
        {
            writer.AddAttribute("onclick", ToggleScript);
        }
        ...
    }

This makes it so that finding the hidden field is fairly easy via getElementById:

function ToggleNode(nodeID, treeID) {
var cdExpanded = document.getElementById(treeID + "_cdExpanded");
...
}

The JavaScript then modifies the value of the hidden field as needed for the event that occurred. When we get back to the server I am able to parse out the contents of this field and modify the control state as necessary before it gets rendered again. (Note: I actually use 3 hidden fields for tracking different events but the concept is the same)

Hope this helps others in the future…

仙气飘飘 2024-09-16 03:00:47

仅当选择一个项目时才工作听起来真的很奇怪。检查 LoadPostData 是否被调用的快速方法是启用跟踪并将以下内容放入 IPostBackDataHandler.LoadPostData(...)。

Page.Trace.Write("My control", "LoadPostData");

如果是这种情况,您应该确保您拥有以下内容:

Page.RegisterRequiresPostBack(this) in OnInit

这是完整的示例控件。

using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel.Design;
using System.ComponentModel;
using System.Web.UI.Design;

namespace Controls
{
    public sealed class ExtendedListBoxDesigner : ControlDesigner
    {

        public override string GetDesignTimeHtml()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<div>My designer</div>");
            return sb.ToString();
        }

    }

    [DesignerAttribute(typeof(ExtendedListBoxDesigner), typeof(IDesigner))]
    public class ExtendedListBox : ListBox, INamingContainer, IPostBackDataHandler 
    {
        bool IPostBackDataHandler.LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
        {
            Page.Trace.Write("ExtendedListBox", "LoadPostData");
            return true;
        }


        protected override void OnInit(EventArgs e)
        {
            Page.RegisterRequiresPostBack(this);
            base.OnInit(e);
        }

        protected override void RenderContents(HtmlTextWriter writer)
        {
            base.RenderContents(writer);
            writer.Write(string.Format("<input type=\"hidden\" name=\"{0}_dummy\" value=\"alwaysPostBack\">", this.ID));
        }

    }
}

页面看起来像这样。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ControlSamlpe._Default" Trace="true" %>
<%@ Register Assembly="Controls" Namespace="Controls" TagPrefix="sample" %>
<!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>
    <sample:ExtendedListBox runat="server" ID="extListBox"></sample:ExtendedListBox>
    <asp:Button runat="server" ID="go" />
    </div>
    </form>
</body>
</html>

当您单击“开始”时,您应该在跟踪中看到“LoadPostData”。

Sounds really odd to be working only when an item is selected. A quick way to check if LoadPostData is being invoked would to enable tracing and put the following in IPostBackDataHandler.LoadPostData(...).

Page.Trace.Write("My control", "LoadPostData");

If that is the case, you should make sure that you've got the following:

Page.RegisterRequiresPostBack(this) in OnInit

Here is a full sample control.

using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel.Design;
using System.ComponentModel;
using System.Web.UI.Design;

namespace Controls
{
    public sealed class ExtendedListBoxDesigner : ControlDesigner
    {

        public override string GetDesignTimeHtml()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<div>My designer</div>");
            return sb.ToString();
        }

    }

    [DesignerAttribute(typeof(ExtendedListBoxDesigner), typeof(IDesigner))]
    public class ExtendedListBox : ListBox, INamingContainer, IPostBackDataHandler 
    {
        bool IPostBackDataHandler.LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
        {
            Page.Trace.Write("ExtendedListBox", "LoadPostData");
            return true;
        }


        protected override void OnInit(EventArgs e)
        {
            Page.RegisterRequiresPostBack(this);
            base.OnInit(e);
        }

        protected override void RenderContents(HtmlTextWriter writer)
        {
            base.RenderContents(writer);
            writer.Write(string.Format("<input type=\"hidden\" name=\"{0}_dummy\" value=\"alwaysPostBack\">", this.ID));
        }

    }
}

and the page looks like this.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ControlSamlpe._Default" Trace="true" %>
<%@ Register Assembly="Controls" Namespace="Controls" TagPrefix="sample" %>
<!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>
    <sample:ExtendedListBox runat="server" ID="extListBox"></sample:ExtendedListBox>
    <asp:Button runat="server" ID="go" />
    </div>
    </form>
</body>
</html>

When you click on go, you should see "LoadPostData" in the trace.

迷途知返 2024-09-16 03:00:47

我已经确定,除非发布数据包含与控件的 UniqueID 同名的项目,否则不会调用 LoadPostData() 方法。 [编辑:在 Init() 期间调用 Page.RegisterRequiresPostback 克服了这个问题。] 我可以理解为什么,但它是相当有限的。

我通过在 LoadPostData() 方法期间根本不处理它来克服这个问题。相反,我在 OnLoad() 中调用的方法中处理了它。

使用此方法时需要记住两件事:

1) 您不再有权访问作为参数传递给 LoadPostData() 方法的 postCollection NameValueCollection 对象。这意味着您必须从 Request.Form 集合中提取发布数据,这是一项稍微困难的工作。
2) 由于 OnLoad() 发生在 ViewState 处理代码之后,因此您需要在创建 ListItems 后手动设置 SelectedValue。如果不这样做,如果列表框是通过 AJAX 填充的并且用户进行了选择,则该选择将会丢失。

我希望这对将来的人有帮助。

I've established that the LoadPostData() method is not called unless the post data contains an item with the same name as the control's UniqueID. [Edit: calling Page.RegisterRequiresPostback during Init() overcomes this.] I can see why, but it is quite limiting.

I have overcome the problem by not handling it during the LoadPostData() method at all. Instead, I have handled it in a method which I call in OnLoad() instead.

Two things need to be borne in mind when using this approach:

1) You no longer have access to the postCollection NameValueCollection object which is passed in to the LoadPostData() method as an argument. This means you have to extract the post data from the Request.Form collection, which is slightly harder work.
2) Since OnLoad() occurs after the ViewState processing code, you will need to manually set the SelectedValue after you create the ListItems. If you don't, if the listbox is populated via AJAX and the user makes a selection, the selection will be lost.

I hope this helps someone in the future.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文