views:

1378

answers:

6

I need to generate buttons initially based on quite a processor and disk intensive search. Each button will represent a selection and trigger a postback. My issue is that the postback does not trigger the command b_Command. I guess because the original buttons have not been re-created. I cannot affort to execute the original search in the postback to re-create the buttons so I would like to generate the required button from the postback info.

How and where shoud I be doing this? Should I be doing it before Page_Load for example? How can I re-construct the CommandEventHandler from the postback - if at all?

   namespace CloudNavigation
{
    public partial class Test : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (IsPostBack)
            {
                // how can I re-generate the button and hook up the event here
                // without executing heavy search 1
            }
            else
            {
                // Execute heavy search 1 to generate buttons
                Button b = new Button();
                b.Text = "Selection 1";
                b.Command += new CommandEventHandler(b_Command);
                Panel1.Controls.Add(b);
            }
        }

        void b_Command(object sender, CommandEventArgs e)
        {
            // Execute heavy search 2 to generate new buttons
            Button b2 = new Button();
            b2.Text = "Selection 2";
            b2.Command += new CommandEventHandler(b_Command);
            Panel1.Controls.Add(b2);
        }
    }
}
A: 

Does your ASPX have the event handler wired up?

<asp:Button id="btnCommand" runat="server" onClick="b_Command" text="Submit" />
tsilb
+1  A: 

The buttons need to be created before the load event, or state won't be wired up correctly. Re-create your buttons in Init() instead.

As for how to do this without re-running the search, I suggest you cache the results somewhere. The existence of a result set in the cache is how your button code in the Init() event will know it needs to run.

Alternatively, you could place the buttons on the page statically. Just put enough there to handle whatever the search returns. If you're thinking that maybe that would be way too many items, then ask your self this: will your users really want to sort through that many items? Maybe you should consider paging this data, in which case static buttons aren't as big a deal any more.

Joel Coehoorn
A: 

I agree with Joel about caching the search results. As for the buttons you can create them dynamically at the init or load phases of the page lifecycle but be aware that if you remove a button and then add it back programmatically you will mess up your state.

In one of my projects we have a dynamic form that generates field son the fly and the way we make it work is through an array that is stored in the cache or in the viewstate for the page. The array contains the buttons to display and on each page load it re-creates the buttons so that state can be loaded properly into them. Then if I need more buttons or a whole new set I flag the hide value in the array and add a new set of values in the array for the new set of corresponding buttons. This way state is not lost and the buttons continue to work.

You also need to ensure that you add a handler for the on_click event for your buttons if you create them programmatically which I think I see in your code up at the top.

Middletone
A: 

What happens when the postback event handling tries to find the control it dosen't exists on the collection. Checkout Denis DynamicControlsPlaceholder @ http://www.denisbauer.com/ASPNETControls/DynamicControlsPlaceholder.aspx

Hope it helps Bruno Figueiredo http://www.brunofigueiredo.com

Bruno Shine
A: 

Here is a sample with custom viewstate handling (note that buttons have EnableViewState = false):

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            // Execute heavy search 1 to generate buttons
            ButtonTexts = new ButtonState[] { 
                new ButtonState() { ID = "Btn1", Text = "Selection 1" } 
            };
        }
        AddButtons();
    }

    void b_Command(object sender, CommandEventArgs e)
    {
        TextBox1.Text = ((Button)sender).Text;

        // Execute heavy search 2 to generate new buttons
        ButtonTexts = new ButtonState[] { 
            new ButtonState() { ID = "Btn1", Text = "Selection 1" }, 
            new ButtonState() { ID = "Btn2", Text = "Selection 2" } 
        };
        AddButtons();
    }

    private void AddButtons()
    {
        Panel1.Controls.Clear();
        foreach (ButtonState buttonState in this.ButtonTexts)
        {
            Button b = new Button();
            b.EnableViewState = false;
            b.ID = buttonState.ID;
            b.Text = buttonState.Text;
            b.Command += new CommandEventHandler(b_Command);
            Panel1.Controls.Add(b);
        }
    }

    private ButtonState[] ButtonTexts
    {
        get
        {
            ButtonState[] list = ViewState["ButtonTexts"] as ButtonState[];
            if (list == null)
                ButtonTexts = new ButtonState[0];
            return list;
        }
        set { ViewState["ButtonTexts"] = value; }
    }

    [Serializable]
    class ButtonState
    {
        public string ID { get; set; }
        public string Text { get; set; }
    }
Panos
+3  A: 

The b_Command Event Handler method is not being executed because on post back buttons are not being recreated (since they are dynamically generated). You need to re-create them every time your page gets recreated but in order to do this you need to explicitly cache information somewhere in state.

If this a page-scoped operation easiest way is to store it in the ViewState (as strings - if you start loading the ViewState with objects you'll see performance go down) so that you can check it on next load (or any other previous event) and re-create buttons when reloading the page. If the operation is session-scoped, you can easily store an object (array or whatever) in session and retrieve it on next Load (or Init) to re-create your controls.

This scenario means that you need just to store some info about your button in your b_Command EventHandler instead of creating and adding buttons since if you do so you'll lose relative information in the next postback (as it is happening now).

so your code would become something like:

namespace CloudNavigation
{
    public partial class Test : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (IsPostBack)
            {
                this.recreateButtons();
            }
            else
            {
                // Execute heavy search 1 to generate buttons
                Button b = new Button();
                b.Text = "Selection 1";
                b.Command += new CommandEventHandler(b_Command);
                Panel1.Controls.Add(b);
                //store this stuff in ViewState for the very first time
            }
        }

        void b_Command(object sender, CommandEventArgs e)
        {
            //Execute heavy search 2 to generate new buttons
            //TODO: store data into ViewState or Session
            //and maybe create some new buttons
        }

        void recreateButtons()
        {
            //retrieve data from ViewState or Session and create all the buttons
            //wiring them up to eventHandler
        }
    }
}

If you don't want to call recreateButtons on page load you can do it on PreLoad or on Init events, I don't see a difference since you'll be able to access ViewState/Session variables everywhere (on Init viewstate is not applied but you can access it to re-create your dynamic buttons).

Someone will hate this solution but as far as I know the only way to retain state data server-side is ViewState - Session - Page.Transfer or client-side cookies.

JohnIdol