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

.NET ProgrammingASP.NETC#

Avatar of undefined
Last Comment
raterus

8/22/2022 - Mon
raterus

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>

:-)
ASKER
troycomp

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.
raterus

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.
Your help has saved me hundreds of hours of internet surfing.
fblack61
ASKER
troycomp

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
raterus

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?
ASKER
troycomp

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...
Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
ASKER CERTIFIED SOLUTION
raterus

Log in or sign up to see answer
Become an EE member today7-DAY FREE TRIAL
Members can start a 7-Day Free trial then enjoy unlimited access to the platform
Sign up - Free for 7 days
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.
See how we're fighting big data
Not exactly the question you had in mind?
Sign up for an EE membership and get your own personalized solution. With an EE membership, you can ask unlimited troubleshooting, research, or opinion questions.
ask a question
ASKER
troycomp

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!!!!!!!!!!!!!!!!!!!!!
ASKER
troycomp

Raterus was a major help to me. He is the best.!!!!!!!!!!!
raterus

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.
This is the best money I have ever spent. I cannot not tell you how many times these folks have saved my bacon. I learn so much from the contributors.
rwheeler23