How to embed one control within another?

  • Thread starter Thread starter Sky
  • Start date Start date
S

Sky

What I have currently:
I have a user control called mod_container.aspx that is basically two
divs -- the top a toolbar, that expands/collapse the second div which can
contain other modules/controls (eg a newsfeed, quick phonelist, login, etc.)

But the controls end up too 'deep': it appears that it would be
lighter/better if I could just enherit the uc_login.aspx directly off of the
uc_container.aspx so that all modules have the same look/feel in terms of
perimeter...

But -- and my question -- if you enherit one user control from another the
first uc_controls html is never used -- it just reads the second/descendants
html...and therefore, no container wrapper html appears...

How would I do this?

One direction I've looked is to somehow take the uc_login.aspx's html, and
before rendering it, somehow inject above it the html of the container (the
two divs...) and then moveNode(deep) the whole original structure into
it....
Sounds heavy and not clean so am suspicious of it... Secondly -- I only
found a way to get a handle on sub-controls...not plain old html elements.
Any ideas?
 
Hi,

you are trying to implement visual inheritance which isn't support by
ASP.NET. but, you still can create your own base class that inherit from
UserControl, override the render method and add HTML. every usercontrol
that will inherit from your UserControl will embed the base usercontrol
class HTML.

public class MyUserControl : System.Web.UI.UserControl
{
public MyUserControl() : base()
{
//
// TODO: Add constructor logic here
//
}
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
writer.Write("<b> Base Class Text </b>");
base.Render (writer);
}

}



Natty Gur[MVP]

blog : http://weblogs.asp.net/ngur
Mobile: +972-(0)58-888377
 
Dear Natty:
Thanks for the answer -- I think I see what you are getting at -- but for
verification -- at what point does the Render() function draw it's sub
Controls, and can this be controlled?

In other words how do I ensure this behaviour:


protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
writer.Write("<div id=OuterDiv>");
writer.Write("<div id=Header>My Container Header</div>");
writer.Write("<div id=Stuff>");
//-------------------------------------------------
//Now render all sub controls within this second div...
//Does one loop through it's children like this:
//[bad proxycode but I hope you see what I mean]
while (i=0;i<this.Controls.length;i++){this.Controls.Render();}
//-------------------------------------------------
writer.Write("</div>");
writer.Write("</div>");
base.Render (writer);
}


An important point: this manual manipulation of controls -- does it wig out
the Control tree so that click events stop working, and viewstate gives up?
I've seen that
happen in my forays -- can't remember what caused it -- but it seems that
sometimes if you manipulate the Control tree too hard, MS can't figure out
what control is what
and stops tracking it -- or gives errors.


Oh! Did you see my previous question in this thread just before this one?
I've tried to make enherited controls -- I've made one user control called
'base' and then
a second one that enherits from it in the *.cs file -- and it holds together
at run time. But ever time I try to open and close the second usercontrol is
states that it can't find
all the pieces... ever seen such behaviour and know the cause/solution?
 
Hi again,

Yes you can achieve it. but you need to use Controls Addat method to
place your code in front of the derived control code :

protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
System.Web.UI.HtmlControls.HtmlGenericControl oGen = new
System.Web.UI.HtmlControls.HtmlGenericControl();
oGen.InnerHtml = "Start <br/>";
this.Controls.AddAt(0,oGen);

// the control data will render here

base.Render (writer);
writer.Write("<br/><b> End Base Class Text </b>");

}
2) I don't see any reason for server side events to stop working.

3) I try it out on my machine (VS 2003) and it working.



Natty Gur[MVP]

blog : http://weblogs.asp.net/ngur
Mobile: +972-(0)58-888377
 
Dear Natty:
Uh...still missing that "little something before it goes Click! in the
brain....(I might need to change my coffee brand!)

a) I have an *.cs base class that is called uc_mod_container.cs The ascx
part of the file is never going to be used so any html I put here with the
IDE will be never seen. That I get. So we will be doing the HTML stuff in
it's Render() event. Get that, I think.

b) I have a *.cs enherited class that is called uc_mod_phonelist.aspx that
we want to be a standalone app that can be wrapped by different wrappers
(therefore enherit from different uc_mod_container.cs depending on
project)...

d) Because uc_mod_phonelist.aspx does not have an explicit Render() command
it reverts to base.Render() (the uc_mod_container.cs) which starts drawing
divs everywhere -- and then injects the controls into it.

In other words:
uc_mod_phonelist.aspx would have already content that looks (proxy) like:

<div><hr/><br/><datagrid runat=server/><etc.><other simple html tags that
are not controls....></div>

and we have to wrap it all up so that the final output looks like

<div id=outer><div id=header>Click here to expand/collapse second
div</div><div>{all the html and controls shown above}</div></div>


BUT (and my question):
The code you demonstrated would inject any SubControls we may have injected
into uc_phonelist (as I see it only the datagrid qualifies as a
control)...this doesn't address the HTML that's already there and that we
already put into the uc_phonelist.aspx via the IDE. Or does it? These html
elements are not Controls, but are just [what?]. Plus they already there, so
AddAt would duplicate them if we doing something like that, right?

Second nagging question: If uc2 is enherited from uc1 -- so they are the
SAME control, no? SubControl of uc1 is NOT uc2? Correct?


Sorry for being such a gormless twit/slowwwww at understanding this
morning...
but don't give up on me: your help is really invaluable, and I'm pretty sure
we are nearly there...

Best,
Sky
 
To expand on the last entry -- what I was saying is that (after I type it
all up, I get the following...and it "don't look right..."):

Trying to make the code as clear as possible here:

protected override void Render(System.Web.UI.HtmlTextWriter writer)
{

//Preamble:

//We are inside of uc_mod_base.cs here...

//at the top of the page there are the following protected vars:

//protected System.Web.UI.WebControls.ImageButton ID_ExpandBtn;

//protected System.Web.UI.WebControls.ImageButton ID_EditBtn;



//Declare local vars:

System.Web.UI.HtmlControls.HtmlGenericControl oDivOuter;

System.Web.UI.HtmlControls.HtmlGenericControl oDivUpper;

System.Web.UI.HtmlControls.HtmlGenericControl oDivLower;

System.Web.UI.HtmlControls.HtmlTable oTable;

System.Web.UI.HtmlControls.HtmlTableRow oRow;

System.Web.UI.HtmlControls.HtmlTableCell oCell;

System.Web.UI.WebControls.ImageButton oImg;

//Create the Outer div, that will contain two inner divs:

oDivOuter = new System.Web.UI.HtmlControls.HtmlGenericControl();

oDivOuter.TagName = "div";

this.Controls.Add(oDivOuter);

//Create the Upper-Inner div that will contain a table

//to hold icons, and container "Toolbar/Title"

oDivUpper = new System.Web.UI.HtmlControls.HtmlGenericControl();

oDivUpper.TagName = "div";

oDivUpper.ID = "ID_Header";

oDivUpper.Attributes.Add("class","HEADER GRADIENT");

oDivOuter.Controls.Add(oDivUpper);


oTable=new System.Web.UI.HtmlControls.HtmlTable();

oTable.ID="ID_SubjectRow";

oDivUpper.Controls.Add(oRow);

oRow = new System.Web.UI.HtmlControls.HtmlTableRow();

oTable.Rows.Add(oRow);

oCell = new System.Web.UI.HtmlControls.HtmlTableCell();

oCell.Attributes("style","margin:0;padding:0;width:1;");

oRow.Cells.Add(oCell);

//Put the Expand/Collapse Icon in cell 1:

oImg = ID_ExpandBtn = new System.Web.UI.WebControls.ImageButton();

oImg.ID = "ID_ExpandBtn";

oImg.Click += OnClick_ExpandMe;

//How do I do this? oImg.AutoPostBack = ???

oImg.Attributes.Add("style","cursor:hand;width:16px;height:16px;margin:0;pad
ding:0;margin-left:4px;border:0;");

oImg.Attributes["title"]= this._UID + ":" +
"Expanded:"+this._Vars.Expanded.ToString();

if (!this._Vars.Expanded)

{

ID_ExpandBtn.ImageUrl =
XLib.Resources.cImages.ICON_NAV2_RIGHT(XLib.Enums.eImgPathType.URL);

}

else

{

ID_ExpandBtn.ImageUrl =
XLib.Resources.cImages.ICON_NAV2_DOWN(XLib.Enums.eImgPathType.URL);

}

oRow.Controls.Add(oImg);

//Put the Container's Title in Cell2:

oCell = new System.Web.UI.HtmlControls.HtmlTableCell();

oCell.InnerHtml = this.Subject; //Set text of Container TitleBar

oRow.Cells.Add(oCell);

//Put the Edit Button in Cell3:

oCell = new System.Web.UI.HtmlControls.HtmlTableCell();

oCell.InnerHtml = this.Subject; //Set text of Container TitleBar

oCell.Attributes.Add("style","margin:0;padding:0;width:1;");

oCell.Visible = this._Vars.Editable;

oRow.Cells.Add(oCell);


oImg = ID_EditBtn = new System.Web.UI.WebControls.ImageButton();

oImg.ID = "ID_EditBtn";

oImg.Click += OnClick_EditMe;

oImg.Attributes.Add("style","width:16px;height:16px;margin:0;padding:0;margi
n-left:4px;border:0;");

if (this._Vars.Editable)

{

ID_EditBtn.ImageUrl =
XLib.Resources.cImages.ICON_EDIT(XLib.Enums.eImgPathType.URL);

ID_EditBtn.Attributes["title"]= "Edit this Control";

}

oCell.Controls.Add(oImg);

//Move on to the Inner-Bottom Div -- in which we want to inject sub
controls...

oDivLower = new System.Web.UI.HtmlControls.HtmlGenericControl();

oDivLower.TagName = "div";

oDivLower.ID = "ID_Data";

oDivOuter.Controls.Add(oDivLower);

//Close with a break so that each control lines up vertically within the
uc_page_panel.ascx

oBR = new System.Web.UI.HtmlControls.HtmlGenericControl();

oBR.TagName = "br";

oDivOuter.Controls.Add(oBR);

//All done, except that I STILL have not injected the HTML of the the
module_phonebook.ascx page

//within this code:

//MAKES NO SENSE!!!

//oDivLower.Controls.AddAt(0,this);

//But if I use the Write() function that was mentioned, none of the event
wiring that I just

//did above will work, right?

//To get wireup controls, you do have to add them control by control - you
can't just add a

//Write() and expect it to work, no?

//writer.Write ("<div id=ID_Data runat=server onclick='ID_Data_Click'>);

//this.Controls.AddAt(0,oGen);

//base.Render (writer);

//writer.Write ("</div>);



}
 
Hi Natty:
Was able to analyse your code, and I do understand the notion of injecting
prefix and suffix code around the base.Render(writer); statement.

But that -- although simple enough to accomplish -- completly destroys any
chance of injecting working buttons above and below.

What I am saying is that what I would like to accomplish is the following:

writer.Write("<div stuff above the enherited controls/><asp:button
runat=server onClick=ClickMe......./>");
base.Render(writer);
writer.Write("<asp:button runat=server...../></div>");

but obviously since these were injected after InstantiateControls(), they
are completly 'dead'.
They appear as buttons (maybe) but have never been delegated back to the
forms attached ClickMe1() event...


Parts of the puzzle that I have figured out to doing this include the
following steps...

Create and instantiate controls dynamically within the OnInit() event so
that later InstantiateControls() wires them up correctly to be
parts of the webform's visualstate,etc. wiring.

Just before getting out of OnInit() -- one has to move the descendant
Controls INTO the container we want -- by a combination of
Remove() and AddAt().

So far so good. If we stopped here, we would have mostly all the parts of
visual enheritence.

The only problem is that any controls created dynamically in the descendant
control -- such as the TextBox control you created in the descendant's
OnLoad() -- would not exist at this point, and therefore could not be moved
into place...

So, in the Render() event we have to repeat the Remove/AddAt process
here...(for some reason, it doesn't work at all if you don't do the
Add/Remove
loop in the OnInit()...nothing shows up on page).


At this point with the base classes code below, I have got the controls to
show up in the right place -- and correctly wired...but...well...I'm not
satisfied.

"What?! It works! Move on!" Yeah...well...true...but I don't understand
really why it works. Which means that I will lose a ton of time again on
another
project, repeating all my mistakes...etc.


So I have two questions.
a) In the OnInit() event, when I've created all the new controls dynamically
BUT NOT ADDED them to the page -- and you have only one DropDownList
in yours, does it report that Controls.Count = 2??? More disturbing, if you
look at their ID's..why are they BOTH THE SAME element?!?!

b) Why does this solution fail if I do not do the Loop/AddAt() -- to move
the children controls into the right location -- in BOTH OnInit and later
Render().
It appears to me that it should work fine if I just put it in the Render()
event. no?

c) And how could I improve the loop mechanism? In other words -- if class b
enherits from a -- is there any way that I can distinguish a.class[0] as
being from a rather than b? "wot?!" ...I think if you look at the loop you
will understand:

for (int i=(Controls.Count-1);i>-1;i--)

{

tC = Controls;

if ((tC != ID_DivOuter) && (ID_DivLower.Controls.Contains(tC)==false))

{

try

{

// Controls.Remove(tC);

ID_DivLower.Controls.AddAt(0,tC);

}

catch {

s = "SHIT! " + tC.GetType().ToString();

}


}

}







Ok. The full class that works...but still puzzles me, is here:



namespace WebApplication3

{

using System;

using System.Data;

using System.Drawing;

using System.Web;

using System.Web.UI.WebControls;

using System.Web.UI.HtmlControls;

/// <summary>

/// Summary description for baseUC.

/// </summary>

public class baseUC : System.Web.UI.UserControl

{

private System.Collections.ArrayList _SubControls;

protected System.Web.UI.WebControls.ImageButton ID_ExpandBtn;

protected System.Web.UI.HtmlControls.HtmlGenericControl ID_DivOuter;

protected System.Web.UI.HtmlControls.HtmlGenericControl ID_DivUpper;

protected System.Web.UI.HtmlControls.HtmlGenericControl ID_DivLower;





public bool Expanded = true;

public bool Editable = true;

public string BgColor="";

public string Header="";

public string Subject="";

public string Body = "";

public System.Collections.ArrayList SubControls = new
System.Collections.ArrayList();

public System.Web.UI.Control SubControl = null;

public string SubControlSrc="";









private void Page_Load(object sender, System.EventArgs e)

{

// Put user code to initialize the page here

if (Session[this.UniqueID + "Expanded"]==null){Session[this.UniqueID +
"Expanded"] = Expanded;}

Expanded = (bool)Session[this.UniqueID + "Expanded"];

}

#region Web Form Designer generated code

override protected void OnInit(EventArgs e)

{

//Because the *.ascx file will be ignored, and therefore any and all

//html elements put here, this base classe's html and buttons need to

//created dynamically here:

//Another point: it needs to be created Here -- because if you make and
inject

//them into the page after InitializeComponent runs, you will get buttons --
but

//they won't be wired up into the event handling so are useless.

//Preamble:

//at the top of the page there are the following protected global vars

//so that Render event can re-access what is created here:

//protected System.Web.UI.WebControls.ImageButton ID_ExpandBtn;

//protected System.Web.UI.HtmlControls.HtmlGenericControl ID_DivOuter;

//protected System.Web.UI.HtmlControls.HtmlGenericControl ID_DivUpper;

//protected System.Web.UI.HtmlControls.HtmlGenericControl ID_DivLower;

//Declare local vars:

System.Web.UI.HtmlControls.HtmlGenericControl oDivOuter;

System.Web.UI.HtmlControls.HtmlGenericControl oDivUpper;

System.Web.UI.HtmlControls.HtmlGenericControl oDivLower;

System.Web.UI.HtmlControls.HtmlTable oTable;

System.Web.UI.HtmlControls.HtmlTableRow oRow;

System.Web.UI.HtmlControls.HtmlTableCell oCell;

System.Web.UI.WebControls.ImageButton oImg;

System.Web.UI.HtmlControls.HtmlGenericControl oBR;

System.Web.UI.Control tC;

string s;


//Create the Outer div, that will contain two inner divs:

oDivOuter = ID_DivOuter = new
System.Web.UI.HtmlControls.HtmlGenericControl();

oDivOuter.TagName = "div";

oDivOuter.ID = "DivOuter";

//Create the Upper-Inner div that will contain a table

//to hold icons, and container "Toolbar/Title"

oDivUpper = new System.Web.UI.HtmlControls.HtmlGenericControl();

oDivUpper.TagName = "div";

oDivUpper.ID = "ID_DivUpper";

oDivUpper.Attributes.Add("class","HEADER GRADIENT");

oDivOuter.Controls.Add(oDivUpper);


oTable=new System.Web.UI.HtmlControls.HtmlTable();

oTable.Border = 1;

oTable.ID ="TheTable";

oTable.Style.Add("width","100%");

oTable.Style.Add("background-color","#A0FFA0");

oDivUpper.Controls.Add(oTable);

oRow = new System.Web.UI.HtmlControls.HtmlTableRow();

oRow.ID = "TheRow";

oTable.Rows.Add(oRow);

oCell = new System.Web.UI.HtmlControls.HtmlTableCell();

oCell.Attributes.Add ("style","margin:0;padding:0;width:1;");

oCell.ID = "TheCell";

oRow.Cells.Add(oCell);



//Put the Expand/Collapse Icon in cell 1:

oImg = ID_ExpandBtn = new System.Web.UI.WebControls.ImageButton();

oImg.ID = "TheImg";

oImg.ID = "ID_ExpandBtn";

ID_ExpandBtn.Click += new System.Web.UI.ImageClickEventHandler
(OnClick_ExpandMe);

oImg.Attributes.Add("style","cursor:hand;width:16px;height:16px;margin:0;pad
ding:0;margin-left:4px;border:0;");

oImg.Attributes["title"]= "Expanded:"+Expanded.ToString();



oCell.Controls.Add(oImg);

//Put the Container's Title in Cell2:

oCell = new System.Web.UI.HtmlControls.HtmlTableCell();

oCell.InnerHtml = this.Subject; //Set text of Container TitleBar

oRow.Cells.Add(oCell);



//Move on to the Inner-Bottom Div -- in which we want to inject sub
controls...

oDivLower = ID_DivLower = new
System.Web.UI.HtmlControls.HtmlGenericControl();

oDivLower.TagName = "div";

oDivLower.ID = "ID_DivLower";

oDivLower.Style.Add("background-color","#A0A0FF");

oDivLower.InnerText = "This is the lower div... where the controls should
end up!";

oDivOuter.Controls.Add(oDivLower);

//Close with a break so that each control lines up vertically within the
uc_page_panel.ascx

oBR = new System.Web.UI.HtmlControls.HtmlGenericControl();

oBR.TagName = "br";

oBR.ID = "TheBR";

oDivOuter.Controls.Add(oBR);



int tTest = Controls.Count;

//WHY is tTEST=2 ??? I only have one enherited control visible from this
page at this point

//in time. Plus! both appears to be the same control...

for (int i=0;i<Controls.Count;i++)

{

tC = Controls;

s=(tC.ID);

}

//Move any controls already created in children into this component...

//***DESIGN FLAW***:

//But if you do it here, you won't catch any controls dynamically injected

//later by descendant control

//One other point. It's wierd, but if you remove this code, and rely on
doing

//it all in the Render() event -- you won't get squat. It seems you have to

//do it in 2 'halves'...one here, one repeat job in Render()

int iMax = Controls.Count -1;

for (int i=iMax;i>-1;i--)

{

tC = Controls;

//Remove from Controls -- and inject into one only:

// this.Controls.Remove(tC);

ID_DivLower.Controls.AddAt(0,tC);

}

this.Controls.Add(oDivOuter);

//

// CODEGEN: This call is required by the ASP.NET Web Form Designer.

//

InitializeComponent();

base.OnInit(e);

}


/// <summary>

/// Required method for Designer support - do not modify

/// the contents of this method with the code editor.

/// </summary>

private void InitializeComponent()

{

this.Load += new System.EventHandler(this.Page_Load);

// this.PreRender += new System.EventHandler(this.Page_PreRender);

}



public void OnClick_ExpandMe(object sender,
System.Web.UI.ImageClickEventArgs e)

{

Expanded = ! Expanded;

Session[this.UniqueID + "Expanded"] = Expanded;


}

private void Page_PreRender(object sender, System.EventArgs e)

{


}





protected override void Render(System.Web.UI.HtmlTextWriter writer)

{

//We 'could' use the PreRender event to do this -- but then we would have to
wire in

//all descendant controls to point to it?



if (Expanded)

{

ID_ExpandBtn.ImageUrl =
"http://localhost/xlib/xlib_graphics/btn13/btn13_arrow_1a_down.gif";

ID_DivLower.Style["display"] = "inline";

}

else

{

ID_ExpandBtn.ImageUrl =
"http://localhost/xlib/xlib_graphics/btn13/btn13_arrow_1a_right.gif";

ID_DivLower.Style["display"] = "none";

}

System.Web.UI.Control tC;

string s = "";



for (int i=(Controls.Count-1);i>-1;i--)

{

tC = Controls;

if ((tC != ID_DivOuter) && (ID_DivLower.Controls.Contains(tC)==false))

{

try

{

// Controls.Remove(tC);

ID_DivLower.Controls.AddAt(0,tC);

}

catch {

s = "ERROR! " + tC.GetType().ToString();

}


}

}

/*

* MS SaMPLE:

if (HasControls()) {

for (int i=0; i < Controls.Count; i++) {

Controls.RenderControl(writer);

}

}

*/

base.Render(writer);

}

#endregion

}

}
 
Back
Top