7

I want to create control that will allow user to design his own website structure. I imagined that inside UpdatePanel is control(s) with TextBox ( for page name) and Button 'add below'. It would looks like:

|   "Questions" |
| [ add below ] |

|     "Tags"    |
| [ add below ] |

|    "Badges"   |
| [ add below ] |

now when user click on button in "Tags" element there should appear new one, between "Tags" and "Badges", with editable name, so user can name it "Users" for example. It should be done without full postback ( to avoid page blinks).

Now is my problem: I cannot load those controls (at last not all of them) in onInit since they dont exist, but I have to attend to theirs click event so I should attaching event listener what should be done during Init phase. How can I achieve described functionality?

I played around it for too much time and I am confused. I would be grateful for any tips.

zgorawski
  • 2,597
  • 4
  • 30
  • 43

3 Answers3

15

This is a tricky situation because you are dealing with dynamic controls you need to populate them on page init in order to persist viewstate, also events for controls added inside an update panel during button clicks do not seems to get registered until the next postback, so the normal button click event only fires once every other time you click on a newly added control. There may be some other way to work around this than the way I did it. If anyone knows I would like find out.

My solution is to keep a list of what you have added dynamically, and store it in a session variable (because the viewstate is not loaded during page init). Then on the page init load any controls you have previously added. Then handle the click event during the page load with some custom code instead or the normal click event.

I have created a sample page to help test this.

Here is the code for the aspx page (default.aspx):

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

<!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 id="Head1" runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
    <p>This only updates on full postbacks: <%= DateTime.Now.ToLongTimeString() %></p>
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>
            <asp:PlaceHolder ID="PlaceholderControls" runat="server"></asp:PlaceHolder>
        </ContentTemplate>
    </asp:UpdatePanel>
    </form>
</body>
</html>

And here is the code for the code behind page (default.aspx.cs):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class _Default : System.Web.UI.Page
{
    /// <summary>
    /// this is a list for storing the id's of dynamic controls
    /// I have to put this in the session because the viewstate is not
    /// loaded in the page init where we need to use it
    /// </summary>
    public List<string> DynamicControls
    {
        get
        {
            return (List<string>)Session["DynamicControls"];
        }
        set
        {
            Session["DynamicControls"] = value;
        }
    }

    protected void Page_Init(object sender, EventArgs e)
    {
        PlaceholderControls.Controls.Clear();

        //add one button to top that will cause a full postback to test persisting values--
        Button btnFullPostback = new Button();
        btnFullPostback.Text = "Cause Full Postback";
        btnFullPostback.ID = "btnFullPostback";
        PlaceholderControls.Controls.Add(btnFullPostback);

        PlaceholderControls.Controls.Add(new LiteralControl("<br />"));

        PostBackTrigger FullPostbackTrigger = new PostBackTrigger();
        FullPostbackTrigger.ControlID = btnFullPostback.ID;

        UpdatePanel1.Triggers.Add(FullPostbackTrigger);
        //-----------------------------------------------------------------------




        if (!IsPostBack)
        {
            //add the very first control           
            DynamicControls = new List<string>();

            //the DynamicControls list will persist because it is in the session
            //the viewstate is not loaded yet
            DynamicControls.Add(AddControls(NextControl));

        }
        else
        {
            //we have to reload all the previously loaded controls so they
            //will have been added to the page before the viewstate loads
            //so their values will be persisted
            for (int i = 0; i < DynamicControls.Count; i++)
            {
                AddControls(i);
            }
        }


    }

    protected void Page_Load(object sender, EventArgs e)
    {

        if (!IsPostBack)
        {
            //we have to increment the initial
            //control count here here be cause we cannot persit data in the viewstate during 
            //page init

            NextControl++;

        }
        else
        {
            HandleAddNextClick();           

        }

    }

    /// <summary>
    /// this function looks to see if the control which caused the postback was one of our 
    /// dynamically added buttons, we have to do this because the update panel seems to interefere
    /// with the event handler registration.
    /// </summary>
    private void HandleAddNextClick()
    {
        //did any of our dynamic controls cause the postback if so then handle the event
        if (Request.Form.AllKeys.Any(key => DynamicControls.Contains(key)))
        {
            DynamicControls.Add(AddControls(NextControl));
            NextControl++;
        }
    }



    protected void btnAddNext_Command(object sender, CommandEventArgs e)
    {
        //this is intentionally left blank we are handling the click in the page load
        //because the event for controls added dynamically in the click does
        //not get registered until after a postback, so we have to handle it 
        //manually.  I think this has something to do with the update panel, as it works 
        //when not using an update panel, there may be some other workaround I am not aware of

    }

    /// <summary>
    /// variable for holding the number of the next control to be added
    /// </summary>
    public int NextControl
    {
        get
        {
            return  ViewState["NextControl"] == null ? 0 : (int)ViewState["NextControl"];
        }
        set
        {
            ViewState["NextControl"] = value;
        }
    }


    /// <summary>
    /// this function dynamically adds a text box, and a button to the placeholder
    /// it returns the UniqueID of the button, which is later used to find out if the button
    /// triggered a postback
    /// </summary>
    /// <param name="ControlNumber"></param>
    /// <returns></returns>
    private string AddControls(int ControlNumber)
    {
        //add textbox
        TextBox txtValue = new TextBox();
        txtValue.ID = "txtValue" + ControlNumber;
        PlaceholderControls.Controls.Add(txtValue);

        //add button
        Button btnAddNext = new Button();
        btnAddNext.Text = "Add Control " + ControlNumber;
        btnAddNext.ID = "btnAddNext" + ControlNumber;
        int NextControl = ControlNumber + 1;
        btnAddNext.CommandArgument = NextControl.ToString();

        btnAddNext.Command += new CommandEventHandler(btnAddNext_Command);
        PlaceholderControls.Controls.Add(btnAddNext);

        //add a line break
        PlaceholderControls.Controls.Add(new LiteralControl("<br />"));

        return btnAddNext.UniqueID;

    }    
}

Let me know if this code helps you out at all. I tried to add comments with my understanding of what is going on and how it works.

Chris Mullins
  • 6,677
  • 2
  • 31
  • 40
  • I will definitely check yours solution after work and give you more feedback. Many thanks for sample even if I didnt ask for it! – zgorawski Nov 03 '10 at 14:56
  • Works like a charm :) I forgot / didnt know about few features that you used so mine solution was incomplete. Many thanks! You helped me a lot :) – zgorawski Nov 04 '10 at 07:27
  • glad I could help, I wish there was a way to do it without using the session though, but I don't know of any other easy way to store and retrieve info before the viewstate loads. You could probably use hidden fields or cookies, but those seem like even wore ideas. Maybe there is a way to load the viewstate manually and use it in the page init. – Chris Mullins Nov 04 '10 at 10:57
  • This is not true. Loading a control dynamically in OnLoad will work fine -- *as long as the control tree is shaped the same!* -- because the events will "catch up". I restore dynamic controls from ViewState all the time. I *do not* store the Control, mind, but rather the "information required to know how to rebuild the control tree". The important thing is rebuilding the same tree. To make sure the control-tree is shaped the same using a *placeholder* control (which is loaded in Init, perhaps in markup) is a useful technique: the ViewState reads *as a tree*. –  Jun 21 '12 at 00:43
  • (When I say the placeholder control is loaded in Init, I don't mean that the *dynamic children* are necessarily loaded in Init: creating/adding the *children* can be deferred to Load when the Page/placeholder ViewState is accessible. When the children are Created/Added they will go through their *own* Init/Load process. The extra ViewState doesn't throw off the primary Init/Load because of the tree-like structure in which it is stored!) –  Jun 21 '12 at 00:50
0

This way working for me

   protected void Page_Load(object sender, EventArgs e)
    {
        if (Page.IsPostBack)
        {
            Button_Click(null, null);
        }
    }

In button call

 protected void Button_Click(object sender, EventArgs e)
    {
            Panel1.Controls.Clear();
            this.YourMethod();
        }
    }

Before you call the method clear the control otherwise it will load twice

aas
  • 197
  • 4
  • 17
-1

If you want to dynamic eventhandlers for your controls, then you can try this -

Considering a button control,

Button btnTest = new Button();
btnTest .Click += new EventHandler(btnTest_Click);

You can have your code in the button click event,

void btnTest_Click(object sender, EventArgs e)
{
    //your code
}
pavanred
  • 12,717
  • 14
  • 53
  • 59