Link to home
Start Free TrialLog in
Avatar of troycomp
troycomp

asked on

Composite control inside a composite control

Maybe i'm attacking the problem all wrong but i'm working on a project where on a few pages the user can add a control (outer control) as many times as they wish. BUT inside of this control they can add a row of data (inner control) under that control as much as they want. Meaning outer control 1 could have 5 innner controls, outer control 2 could have 3 inner controls etc.. I tried user controls but they are a real pain. Gridview has to be bound to a data source and there is no data source. Im using composite controls and i can add the outer controls fine but when i add inner controls to the outer controls, it only shows the inner controls of the clicked outer control. If i add an inner control to another outer control, the inner controls disappear of all the other outer controls. But if i add an inner control to the outer control, all the inner controls return for the outer control. Its not losing the controls just not showing them ll at once. Gurus please help.
//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 runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Button ID="addBtn" runat="server" Text="Add" onclick="Button1_Click" /><br />
        <asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
    </div>
    </form>
</body>
</html>
 
//default.aspx.cs
 
using System;
using System.Configuration;
using System.Data;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
 
public partial class _Default : System.Web.UI.Page 
{
    private int outerCount = 1;
 
    private bool addBtnClicked = false;
 
    protected void Page_PreInit(object sender, EventArgs e)
    {
        Control myControl = GetPostBackControl(this.Page);
 
        if ((myControl != null))
        {
            if ((myControl.ClientID.ToString() == "addBtn"))
            {
                addBtnClicked = true;
            }
        }
    }
 
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            ViewState["outerCount"] = 1;
        }
        if (addBtnClicked)
        {
            AddOneToCount();
        }
        else
        {
            bool ok = Int32.TryParse(ViewState["outerCount"].ToString(), out outerCount);
            if (!ok)
            {
            }
        }
 
        for (int i = 0; i < outerCount; i++)
        {
            OuterControl oc = new OuterControl();
            PlaceHolder1.Controls.Add(oc);
            LiteralControl literalBreak = new LiteralControl("<br />");
            PlaceHolder1.Controls.Add(literalBreak);
        }
 
    }
    public void AddOneToCount()
    {
        bool ok = Int32.TryParse(ViewState["outerCount"].ToString(), out outerCount);
        if (ok)
        {
            outerCount++;
            ViewState["outerCount"] = outerCount;
        }
 
    }
 
    protected void Button1_Click(object sender, EventArgs e)
    {
 
    }
 
    public static Control GetPostBackControl(Page thePage)
    {
        Control myControl = null;
        string ctrlName = thePage.Request.Params.Get("__EVENTTARGET");
        if (((ctrlName != null) && (ctrlName != string.Empty)))
        {
            myControl = thePage.FindControl(ctrlName);
        }
        else
        {
            foreach (string Item in thePage.Request.Form)
            {
                Control c = thePage.FindControl(Item);
                if (((c) is System.Web.UI.WebControls.Button))
                {
                    myControl = c;
                }
            }
 
        }
        return myControl;
    }
 
}
 
//outer control
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
 
/// <summary>
/// Summary description for CompositeControl
/// </summary>
public class OuterControl : WebControl, INamingContainer
{
    private int rowCounter = 1;
    Panel pnlAddRow = new Panel();
 
    public OuterControl()
	{
        pnlAddRow.Attributes.Add("runat", "server");
        pnlAddRow.ID = "pnl";
 
        RowCounter = 1;
 
        AddRows();
 
    }
 
    public int RowCounter
    {
        get
        {
            EnsureChildControls();
            object o = ViewState["rowCount"];
            if (o == null)
            {
                return -1;
            }
 
            rowCounter = (int)o;
            return rowCounter;
        }
        set
        {
            EnsureChildControls();
            ViewState["rowCount"] = value;
        }
    }
 
    private void AddRows()
    {
        for (int i = 0; i < rowCounter; i++)
        {
            InnerControl ic = new InnerControl();
            pnlAddRow.Controls.Add(ic);
        }
    }
 
    protected override void CreateChildControls()
    {
        this.Controls.Add(new LiteralControl(@"<table border=""4"">"));
        this.Controls.Add(new LiteralControl(@"<tr>"));
        this.Controls.Add(new LiteralControl(@"<td>I'm the outer control. Click the button to add an inner control --></td>"));
        this.Controls.Add(new LiteralControl(@"<td>"));
        Button addButton = new Button();
        addButton.Attributes.Add("runat", "server");
        addButton.ID = "btn1";
        addButton.Text = "Add";
        addButton.Click += new EventHandler(this.addButton_Click);
        this.Controls.Add(addButton);
 
        this.Controls.Add(new LiteralControl(@"</td></tr>"));
        this.Controls.Add(new LiteralControl(@"<tr><td>"));
        this.Controls.Add(pnlAddRow);
        this.Controls.Add(new LiteralControl(@"</td></tr></table>"));
 
        base.CreateChildControls();
    }
 
    private void addButton_Click(Object sender, EventArgs e)
    {
        RowCounter++;
        AddRows();
    }
 
}
 
//inner control
 
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
 
/// <summary>
/// Summary description for InnerControl
/// </summary>
public class InnerControl : WebControl, INamingContainer
{
	public InnerControl()
	{
		//
		// TODO: Add constructor logic here
		//
	}
 
    protected override void CreateChildControls()
    {
        this.Controls.Add(new LiteralControl(@"<table>"));
        this.Controls.Add(new LiteralControl(@"<tr>"));
        this.Controls.Add(new LiteralControl(@"<td>I'm the inner control!!</td>"));
        this.Controls.Add(new LiteralControl(@"</tr></table>"));
 
        base.CreateChildControls();
    }
}

Open in new window

Avatar of raterus
raterus
Flag of United States of America image

There are a lot of things I don't like here.

For one, why are you going to the effort in Page_PreInit to find the postback controls?  Why not raise an event on postback events like standard asp.net controls?

two, what's all this junk in Page_Load?  Page_Load should be as simple as this, always!

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            //do something

        }
     }

if you feel you need to run code each time the page loads, then I imagine you are not doing something right.

three, you are still using dynamic controls within your page.  The whole point of a custom control is to declare it in your .aspx page,

e.g.

<my:control ... >
  <my:subcontrol .../>
</my:control>

:-)
Avatar of troycomp
troycomp

ASKER

One: Im using Page_PreInit to determine which button was pressed. The user can add or delete a control so i need to know which one was clicked so i can do the appropriate action. This is working fine.

Two: First i'm initializing my viewstate counter if its not a postback. next if the "add" button was clicked i'm incrementing my control counter, outerCount. then im building my controls based on the outerCount variable.

Three: yes they are dynamic cause i have no idea how many times the user will add or delete a control.

Did you run the code i attached? I couldnt attach my project cause of the file extensions. the inner and outer controls are in the app_code folder.

I dont know any other way to build a control besides dynamically based on the user telling me how many they want. I know for a fact im using the composite controls wrong. I need guidance.
One: still, event handlers are THE way to go here.  Events Handlers have the sender object, which can be casted as needed to be a reference of the calling control.  You can use arguments stored within the control and controls around it to get the specific control.

Two:  ehh, we'll get to that

Three: Like in a GridView or a Repeater, you don't know how many rows will be generated based on many conditions.  Same concept involved here.  Maybe you can even use a Repeater here with a datasource bound to the number of controls they want?

I did not see any code attached?  Zip it up and attach it please.
I have attached a file with all the sample code. Experts-exchange blocks .cs files so its a .txt file. Your a guru so you know what to do with it :) I really need guidance on this.

As for Two: i cant wait to hear your advice

Also dont hold back. Be critical. No one learns if they are not told what they're doing wrong. I have thick skin
Compositecode.txt
So give me an idea of exactly what you're after?  User needs x number of controls created, and you need to display each one, correct?
If you have sucessfully built a project from the code i sent you will see. Click the top Add button as much as you wish and it will create an outer control each time. Click the Add button inside the outer control and inner controls will be created. I need for ALL controls (inner and outer ) to show at the same time based on the number of times the user has clicked the outer and inner Add buttons. And only God know how many times they will do that. . There is no way the user will know the controls exist only if they click the one they want to type in. And to the user its not a control, its a bunch of boxes and drop downs etc...
ASKER CERTIFIED SOLUTION
Avatar of raterus
raterus
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
You are 100% right. I'm going to do more reading on composite controls. BUt your example is leading me in the right direction. Thank you for your help and advice. I also love this:

Listen to advice and accept instruction,
and in the end you will be wise. -- Proverbs 19:20

Thanks you Raterus!!!!!!!!!!!!!!!!!!!!!
Raterus was a major help to me. He is the best.!!!!!!!!!!!
It was my pleasure, believe it or not, I haven't written a custom control in years, it was good to pull this useless knowledge out of my head.  I even wrote it in C# even though I use vb.net all the time.  It was good practice for me.