Why does my table have no content in ASP.NET? - c#

I have built a table in a class GetData.cs
public Table BuildTable()
{
Table tButtons = new Table();
TableRow tRow = new TableRow();
TableCell tCell = new TableCell();
long lColumn = 0;
long lPreviousColumn = 0;
long lRow = 0;
long lPreviousRow = 0;
long lLanguage = 0;
long lPreviousLanguage=0;
OpenConnection();
ButtonData();
Int32 lRowOrd = aReader.GetOrdinal("RowNumber");
Int32 lColOrd = aReader.GetOrdinal("ColumnNumber");
Int32 lLangOrd = aReader.GetOrdinal("Language");
Int32 lLabelOrd = aReader.GetOrdinal("Label");
while (aReader.Read())
{
lRow = IsDbNull(aReader,lRowOrd);//first get our column number
lColumn = IsDbNull(aReader,lColOrd);//first get our column number
lLanguage = IsDbNull(aReader,lLangOrd);//first get our column number
if (lPreviousRow != lRow)//we have a new row
{
if (lPreviousRow != 0)//then we are working on one and need to save it before moving on
{
tButtons.Rows.Add(tRow);//add the new row to the table
}
lPreviousRow = lRow;//remember the value for next time
tRow = new TableRow();
tRow.Visible = true;
//*******put the category titles in here somewhere
}
if (lPreviousColumn != lColumn)//we have a new column
{
if (lPreviousColumn != 0)//then we are working on one and need to save it before moving on
{
tRow.Cells.Add(tCell);//add the new cell to the row
}
lPreviousColumn = lColumn;//remember the value for next time
//*******add the cell colors
if (lPreviousLanguage != lLanguage)//we have a new column
{
lPreviousLanguage = lLanguage;//remember the value for next time
tCell.Text = IsDbNull(aReader,lLabelOrd,"");
//*******add the languages to properties
}
tCell = new TableCell();
tCell.Visible=true;
}
}
CloseConnection();
tButtons.Visible=true;
return tButtons;
}
In my Default.aspx.cs page I have
GetData Buttons = new GetData();//create a reference to the class
ButtonTable = Buttons.BuildTable();
OutPut.Text = ButtonTable.Rows.Count.ToString();
In Default.aspx
<asp:Table runat="server" ID="ButtonTable" />
<asp:Label runat="server" ID="OutPut" />
Output shows 4 rows, but table is empty.
<table id="ButtonTable" border="0"></table>
What am I doing wrong?

What the heck am I missing?
Apparently, a lot. In your markup, you have declared an instance of a System.Web.UI.WebControls.Table. In your instance of the Page class, this will have a variable name of "ButtonTable". It will also be automatically added to the Page.Controls collection. When the page is going to be rendered, the Controls collection will be iterated and rendered in turn.
In your default.aspx.cs code, you're simply pointing your ButtonTable reference to a different Table control - but you're not affecting the Page.Controls collection. When render time comes, it is the (blank) Table defined in the markup that will be rendered - not the result of your BuildTable call.
All of this is a fairly long winded "you're doing it wrong". The answer to why you want your table building code in a separate class would shed some light on the "right way". But - and I mean no offense - I think you need to study the basics behind ASP.NET before you go any further.
That being said, the most immediate fix (but likely not what you really want) is to add the table to the Controls collection so that it gets rendered:
GetData Buttons = new GetData();//create a reference to the class
ButtonTable = Buttons.BuildTable();
this.Controls.Add(ButtonTable);
OutPut.Text = ButtonTable.Rows.Count.ToString();
Note that it will render separately from your markup defined ButtonTable, and so will be placed after the Output label. That is because it was added after the Output label.

I really suggest you:
Run it line by line in the debugger to see what's going on
Refactor it, the code is hard to read. Adding more comments won't solve that.
Consider what you want to achieve and check if it can fits to databind to a control like ListView.
That said, your code:
GetData Buttons = new GetData();
ButtonTable = Buttons.BuildTable(); // this is what's wrong
OutPut.Text = ButtonTable.Rows.Count.ToString();
Just assigning the control in the page it's not the way to do it. Either add the returned table to the controls collection, or change BuildTable to receive the table it will load the info into. Never directly assign to the control of the asp.net page, once I had to debug code with some very strange issues and a developer had assigned null to a control (not to a property of the control) which messed up during the asp.net render cycle.

ButtonTable = Buttons.BuildTable();
What do you do in there here?

Related

How to assign value to text box control inside the table column in C# coding file?

I am creating asp.net web form. in that i am creating dynamic tables in which particular column is numeric text box control.
i don't know how to assign and get values from the text box control.. my coding as follow..
for (int i = 0; i < my_DataTable.Rows.Count; i++)
{
HtmlTableRow _Row = new HtmlTableRow();
HtmlTableCell Col = new HtmlTableCell();
Col.InnerText = my_DataTable.Rows[i]["itmCode"].ToString();
_Row.Controls.Add(Col);
Col = new HtmlTableCell();
_Row.Controls.Add(Col);
Col.InnerHtml = "<input type='number' value='0'>";
_Row.Controls.Add(Col);
my_Table.Rows.Add(_Row);
}
In a paricular method, i need to assign the value to the text box control also needs to get the value existing value.. so i try follow as below
var no_1 = my_Table.Rows[0].Cells[1].InnerText;
If i check the no_1, it has the textbox, but i don't know how to access the current value and assign new value..
can anyone help me how to achieve this..
One thing you have to keep in mind while working with Dynamic Controls is that whenever a postback has occurred you will lose the dynamically created controls(as the postback calls the Page_load() event so if you don't have them at the load event they will not be generated and hence will not be displayed.). So, it is always better to re-render the controls in the load event.
So, in order to get the value of the dynamically assigned controls (either HTML or Asp.net) here is how i would do that.
First, create a holder which will be used to store the controls in the page either with runat="server"(So, you can access that control in the backend). In your case, that control is my_Table. Then use the Session/ViewState to keep a track of all the created dynamic controls which can be used re-render the controls with their values as:
To add a new control in the page it would be like this:
var cnt = _findRelated("txtDynamic") + 1; //for all the dynamic text boxes i am using the prefix of txtDynamic just to keep SOC.
var nId = $"txtDynamic-{cnt}";
var _ctrl = new HtmlInputText("Integer")
{
Name = nId,
ID = nId,
//Value="Default Value" //uncomment to assign a default value
};
_ctrl.Attributes.Add("runat", "server");
var row = new System.Web.UI.HtmlControls.HtmlTableRow();
var newCell = new HtmlTableCell();
newCell.Controls.Add(_ctrl);
row.Cells.Add(newCell);
my_Table.Rows.Add(row);
Session.Add(cnt.ToString(), _ctrl); //here i am using session to manage the controls but you can also use the ViewState
In the above code i am using HtmlInputText to generate an <input type="number"></input> with it's constructor taking the type string more can be read at:HtmlInputText.
The _findRelated() method is used to get the number of dynamic text controls appended to the Form. It is defined as:
private int _findRelated(string prefix)
{
string reqstr = Request.Form.ToString();
return ((reqstr.Length - reqstr.Replace(prefix, "").Length) / prefix.Length);
}
To set the value of the dynamically added control we can do something like this(if not assigned at the creation):
var cell = my_Table.Rows[_myTable.Rows.Count-1].cells[0]; //here i have assumed it is in the last row and in the first cell you can change the index to be anything.
var txtDynamic = cell.Controls.OfType<HtmlInputText>().FirstOrDefault();//getting the control
txtDynamic.Value = "<Some thing new>"; //setting the value
Now, to get the assigned the value:
var cell = my_Table.Rows[_myTable.Rows.Count-1].cells[0]; //here i have assumed it is in the last row and in the first cell you can change the index to be anything.
var txtDynamic = cell.Controls.OfType<HtmlInputText>().FirstOrDefault();//getting the control
//now use the .Value property of the control to get the value as:
var nValue = txtDynamic.Value;
And as we know the dynamically added controls will be lost on the postback event then we can create a method which will use the controls stored in the Session and re-render them with their values as:
private void _renderControls()
{
try
{
if (Session.Count > 0)
{
for (int k = 0; k < Session.Count; k++)
{
if (Session[k] != null)
{
var _ctrl = new HtmlInputText("Integer") //you can make it dynamic to add different types of input control
{
Name = ((HtmlInputText)Session[k]).ID,
ID = ((HtmlInputText)Session[k]).ID,
Value = ((HtmlInputText)Session[k]).Value
};
if (_ctrl != null)
{
_ctrl.Attributes.Add("runat", "server");
var row = new System.Web.UI.HtmlControls.HtmlTableRow();
var newCell = new HtmlTableCell();
newCell.Controls.Add(_ctrl);
row.Cells.Add(newCell);
my_Table.Rows.Add(row);
}
}
}
}
}
catch (Exception ex)
{
throw ex;
}
}
Now, let's modify the Page_load() event to call this method on every postback as:
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
_renderDynamic(); // this method will be called if there is an postback event to re-render the dynamic controls
}
}
Note:
This is just a sample(there can be a lot better approaches out there).
I have used HtmlInputText with property as Integer to create ainput[type="number"].

How to use a Variable or Function to declare my SqlDataSource ID in HTML

I'm writing a for loop that displays a list of links with some chartfx display. The chartfx needs an sqlDataSource. I'm trying to give the unique ID each time the for loop does one iteration but I can not pass it a value or function. Example below in my code.
getSQLID() is just a function that returns a string which I want to be my ID. This is all done on the aspx page and the function is in the .cs . Any help would be really appreciated thank you.
//name of the contentplace holder on the aspx page
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server" >
//code behind
Control ctrl = LoadControl("WebUserControl.ascx");
Control placeHolderControl = this.FindControl("Content2");
Control placeHolderControl2 = this.FindControl("ContentPlaceHolder1");
ctrl.ID = "something";
if (placeHolderControl != null)
placeHolderControl.Controls.Add(ctrl);
if (placeHolderControl2 != null)
placeHolderControl2.Controls.Add(ctrl);
First of all, recall that server controls declared in the designer like this are attached to your class at compile time. So it doesn't make sense to try to create multiple instances in a loop at runtime, and that's why the values in e.g. the Id tag have to be known at compile time.
One alternative is to create them in the code behind, with something like:
for (int i=0; i<2; ++i)
{
var chart = new Chart();
chart.Id = "chartId" + i;
chart.DataSourceId = "srcid" + i;
var src = new SqlDataSource();
src.Id = "srcid" + i;
Controls.Add(chart); // either add to the collection or add as a child of a placeholder
Controls.Add(src);
}
In your case converting all of those declarative properties to the code behind can be a bit of work (though it is possible). An alternative is to make a user control (ascx) that contains the markup that's now in your aspx page. You would instantiate the controls in your code behind with something like:
for (int i=0; i<2; ++i)
{
var ctrl = LoadControl("~/path/to/Control.ascx");
ctrl.Id = "something_" + i;
Controls.Add(ctrl); // again, either here or as a child of another control
// make the src, hook them up
}

Dynamic Controls - C# - CheckBoxList Parent - Children

First I understand the need to build dynamic controls in the OnInit Section.
However, I read a document from Scott Guthrie?
http://scottonwriting.net/sowblog/archive/2004/10/08/162998.aspx
So I got the impression from these blogs that if you add the control to the container then modify the properties, you can get at the control in the Page_Load.
In a nutshell, I have a table with a fk back to the table creating a hierarchy.
I load 3 usercontrols the page with checkbox lists that relate back through the parent key.
Table like this:
create table myTbl
(
id int identity,
par_id int,
item_desc varchar(25)
other_desc_flag bit
)
What my clients need is the ability to make a checkbox selection.
The child set of textboxes will display based on the parent.
If the txt_flag is set, a checkbox entry will not be populated.
Instead, they want the desc label printed out with a textbox for response.
The user data is not kept in the table above by the way.
So the issue I have is these text responses can be at any level of the custom control.
So I created a class with a 3 items ( id, literal control, and a textbox ).
I then dynamically create the controls based on the selection at any particular level.
I read each level into a dataset, I iterate through the dataset looking for that flag.
I capture an index variable in the rows with this flag and I create a List class to hold the
id, text_desc. I then remove the row from the table in the dataset and bind the remaining items to the checkboxlist.
I then go back to my control and write out dynamically the controls.
But like all those before me, I am doing evil battle against the Page Cycle...lol.
I cannot see the controls. I plan to DataBind() my controls separately on load as I have not gotten to the point where I'm getting the clients reponses from the database. That's for another day. The placeholder with my dynamic controls is OtherPlaceHolder. I have tried setting the Viewstate to true and false.
Any pointers on how to get the # of items on postback into a session variable so I can create the controls on the OnInit section.
Here is the code. This is being run from a Master Page...
On the controls, I changed the < & > to [ ] because this page that attempts to put code into
a controlbox with scrollbars was cutting off that code.
[asp:UpdatePanel ID="UpdPanel" runat="server" EnableViewState="true" UpdateMode="Always"]
[ContentTemplate]
[asp:Table runat="server" ID="ContainerTbl"]
[asp:TableHeaderRow]
[asp:TableHeaderCell ColumnSpan="2" CssClass="tdCell"][asp:Literal ID="LitDesc" runat="server" Text="Level" /][/asp:TableHeaderCell]
[/asp:TableHeaderRow]
[asp:TableRow]
[asp:TableCell runat="server" ID="tblItems" VerticalAlign="top"]
[asp:PlaceHolder runat="server" ID="CtrlPlaceHolder"]
[asp:CheckBoxList ID="cboItems" Visible="false" runat="server" AutoPostBack="true"][/asp:CheckBoxList]
[asp:HiddenField ID="otherCnt" runat="server" /]
[/asp:PlaceHolder]
[asp:PlaceHolder runat="server" ID="OtherPlaceHolder" EnableViewState="false"]
[/asp:PlaceHolder]
[/asp:TableCell]
[/asp:TableRow]
[asp:TableRow]
[asp:TableCell VerticalAlign="top"]
[asp:Label ID="LabMsg" runat="server" CSSClass="tdCell" Font-Bold="true" Visible="false"/]
[/asp:TableCell]
[/asp:TableRow]
[/asp:Table]
[asp:HiddenField ID="hLevel" runat="server" Value="" /]
[/ContentTemplate]
[/asp:UpdatePanel]
private void WriteOutQuestions(List<Questions> qList)
{
int itemCnt = 1;
// clear any controls in other place holder first.
OtherPlaceHolder.Controls.Clear();
Table OTD = new Table();
foreach (Questions qst in qList)
{
// we're going to create the new control and add to
// the placeholder - OtherPlaceholder
// we'll then reference those controls and add the data to those
// controls.
// see dynamic control article: http://scottonwriting.net/sowblog/archive/2004/10/08/162998.aspx
HiddenField hItemId = new HiddenField();
TextBox txtItem = new TextBox();
LiteralControl ltcItem = new LiteralControl();
// add the new controls
string strItemId = "hItem" + Convert.ToString(itemCnt);
string strTxtItem = "txtItem" + Convert.ToString(itemCnt);
string strLtcItem = "ltcItem" + Convert.ToString(itemCnt);
hItemId.ID = strItemId;
hItemId.EnableViewState = true;
txtItem.ID = strTxtItem;
txtItem.EnableViewState = true;
ltcItem.ID = strLtcItem;
ltcItem.EnableViewState = true;
OTD.Controls.Add(OtherDescAddControl(OtherPlaceHolder, hItemId, ltcItem, txtItem));
// now reference the new added controls and set values from Question object...
++itemCnt;
}
OtherPlaceHolder.Controls.Add(OTD);
// now post data to controls...
itemCnt = 1;
foreach (Questions qst in qList)
{
string strItemId = "hItem" + Convert.ToString(itemCnt);
string strTxtItem = "txtItem" + Convert.ToString(itemCnt);
string strLtcItem = "ltcItem" + Convert.ToString(itemCnt);
HiddenField hfld = (HiddenField)OtherPlaceHolder.FindControl(strItemId);
TextBox txtBox = (TextBox)OtherPlaceHolder.FindControl(strTxtItem);
LiteralControl ltx = (LiteralControl)OtherPlaceHolder.FindControl(strLtcItem);
hfld.Value = qst.HFld.ToString();
txtBox.Text = qst.TxtBox;
txtBox.Attributes.Add("class", "txtBox");
ltx.Text = qst.Ltc.ToString();
++itemCnt;
}
//decrement itemCnt and populate box here...
--itemCnt;
HiddenField hfldCnt = (HiddenField)CtrlPlaceHolder.FindControl("otherCnt");
hfldCnt.Value = Convert.ToString(itemCnt);
hfldCnt.Visible = true;
}
On the assumption that when you say "I cannot see the controls" you mean that you're getting null references when you try to access them in the postback (rather than that the HTML doesn't contain them), have you tried using Page.EnsureChildControls()?
Ok If I understood your question right, you want to save your controls and load them back on postbacks. Here is what you can do:
List<HiddenField> HiddenFields = new List<HiddenField>{};
List<TextBox> TextBoxs = new List<TextBox>{};
List<LiteralControl> LiteralControls = new List<LiteralControl>{};
OTD.Controls.Add(OtherDescAddControl(OtherPlaceHolder, hItemId, ltcItem, txtItem));
// do this for all your items that you load to page (add them to your list).
HiddenFields.Add(hItemId);
// when you are done with loading all your controls to page, add your populated Lists to session.
Session["HiddenFields"] = HiddenFields;
//On Page_Init or Page_Load, simpy load them back IF **page is postback**.
If(Page.IsPostBack)
{
LoadControlsFromSession();
}
private void LoadControlsFromSession()
{
HiddenFields = Session["HiddenFields"] as List<HiddenFields>;
// Load all your List objects from session like above.
int counter = 0;
if(HiddenFields != null)
{
foreach(HiddenField hdnField in HiddenFields)
{
//load your objects with the same method you have from your List.
OTD.Controls.Add(OtherDescAddControl(OtherPlaceHolder, HiddenFields[counter], LiteralControls[counter], TextBoxs[counter]));
counter++;
}
}
}
I apologize for lack of clarity.
I believe I did try setting Session variables in my classes when I created the controls and they were null on postback. I tried to access the session variable in the page init and preload and zip.
I did find an interesting workaround to this problem. On page_unload I parsed through the controls into an arraylist of hashtables of the database id & user'sentered text answer.

When does a control in a programmatically added TemplateField have its' ID property set?

I have a TemplateField that is dynamically added to a custom GridView.
void ITemplate.InstantiateIn(System.Web.UI.Control container)
{
switch (_templateType)
{
case ListItemType.Header:
if (this.ParentGridView.ShowDeleteHeaderImage)
{
Image hImg = new Image();
hImg.ImageUrl = this.ParentGridView.DeleteHeaderImageUrl;
hImg.AlternateText = "Mark for Deletion";
container.Controls.Add(hImg);
}
else
{
Label l = new Label();
l.Text = "Del";
container.Controls.Add(l);
}
break;
case ListItemType.Item:
container.Controls.Add(new CheckBox());
break;
case ListItemType.EditItem:
break;
case ListItemType.Footer:
QLImageButton deleteButton = new QLImageButton();
deleteButton.Settings.ImageId = "cmdQLGVDelete";
deleteButton.Settings.ImageUrl = this.ParentGridView.DeleteImageUrl;
deleteButton.CommandName = "Delete";
container.Controls.Add(deleteButton);
break;
}
}
In response to a grid Command (insert/update/delete), a method called GetRowControls is called which iterates through the columns in the particular gridrow, and adds each of its controls to a Dictionary.
Dictionary<string, WebControl> GetRowControls(GridViewRow row)
...
rowControls.Add(ctrl.ID, (WebControl)ctrl);
...
So this works fine for both template field and bound controls added declaratively, as well as dynamic-non template fields added programatically.
However when the control is a TemplateField control added dynamically ctrl.ID is always null and therefore the statement above throws an exception.
I've looked into this with Reflector because I found that when I examined the variable in the immediate window in VS 2005 i.e. ?ctrl, ctrl.ID WOULD list a value. I've since established that this is because in listing ?ctrl in the immediate window, the proprty ClientID is called and ClientID calls EnsureId(), which in turn sets ID.
public virtual string ClientID
{
get
{
this.EnsureID();
string uniqueID = this.UniqueID;
if ((uniqueID != null) && (uniqueID.IndexOf(this.IdSeparator) >= 0))
{
return uniqueID.Replace(this.IdSeparator, '_');
}
return uniqueID;
}
}
So I'm assuming that ClientID, UniqueId and ID are all null - although as above just reading the first two will trigger all to be set. Also note that NamingContainer is not null. It has been set.
So the work around for this is quite simple i.e. check for ctrl.ID==null and if so simply read ctrl.ClientID. And thats what I've done because time wise I've really got to get a wriggle on. But I'm still interested in the answer if anyone knows it off the top of their heads.
Why is the ID value of a child control, of a dynamically added TemplateField, set at a different time from that of other controls?
It is not that they behave differently, but that almost always when you add a control declaratively you set the ID right away. Try adding a label with no ID to a page and browse the control collection and check its ID, it will be null (make sure not to show its clientID since it would get the ID filled):
<asp:Label runat="server">something</asp:Label>
Also note that if you run it like that you get an span with no ID.
Freddy is correct.
Your are responsible for setting IDs inside the InstantiateIn method. And it makes sense that ClientID auto-generates them if not specified otherwise.
The declarative controls get their IDs assigned by a page builder during compilation of a page. If you were to look at one of temp .cs files generated in the "Temporary ASP.NET Files folder", you'd find something like this (pragmas stripped):
//creating a template field, where CopiledBindableTemplateBuilder is the ITemplate
//and its InstantiateIn = #__BuildControl__control9
#__ctrl.ItemTemplate = new System.Web.UI.CompiledBindableTemplateBuilder(
new System.Web.UI.BuildTemplateMethod(this.#__BuildControl__control9),
new System.Web.UI.ExtractTemplateValuesMethod(this.#__ExtractValues__control9));
//and #__BuildControl__control9 calling #__BuildControlButton1
private global::System.Web.UI.WebControls.Button #__BuildControlButton1()
{
global::System.Web.UI.WebControls.Button #__ctrl;
#__ctrl = new global::System.Web.UI.WebControls.Button();
this.Button1 = #__ctrl;
#__ctrl.ApplyStyleSheetSkin(this);
#__ctrl.ID = "Button1"; //<-- here it gets an ID
#__ctrl.Text = "Button";
return #__ctrl;
}

Custom Header in GridView

I've already got my custom header drawing in my GridView using SetRenderMethodDelegate on the header row within the OnRowCreated method. I'm having problems trying to add LinkButtons to the new header row though.
This is what the RenderMethod looks like:
private void RenderSelectionMode(HtmlTextWriter output, Control container)
{
TableHeaderCell cell = new TableHeaderCell();
cell.Attributes["colspan"] = container.Controls.Count.ToString();
AddSelectionModeContents(cell);
cell.RenderControl(output);
output.WriteEndTag("tr");
HeaderStyle.AddAttributesToRender(output);
output.WriteBeginTag("tr");
for(int i = 0; i < container.Controls.Count; i++)
{
DataControlFieldHeaderCell cell = (DataControlFieldHeaderCell)container.Controls[i];
cell.RenderControl(output);
}
}
private void AddSelectionModeContents(Control parent)
{
// TODO: should add css classes
HtmlGenericControl label = new HtmlGenericControl("label");
label.InnerText = "Select:";
selectNoneLK = new LinkButton();
selectNoneLK.ID = "SelectNoneLK";
selectNoneLK.Text = "None";
//selectNoneLK.Attributes["href"] = Page.ClientScript.GetPostBackClientHyperlink(selectNoneLK, "");
//selectNoneLK.Click += SelectNoneLK_Click;
selectNoneLK.Command += SelectNoneLK_Click;
selectAllLK = new LinkButton();
selectAllLK.ID = "SelectAllLK";
selectAllLK.Text = "All";
//selectAllLK.Attributes["href"] = Page.ClientScript.GetPostBackClientHyperlink(selectAllLK, "");
//selectAllLK.Click += SelectAllLK_Click;
selectAllLK.Command += SelectAllLK_Click;
parent.Controls.Add(label);
parent.Controls.Add(selectNoneLK);
parent.Controls.Add(selectAllLK);
}
As you can see, I have tried different ways to get my LinkButtons working (none have worked though). The LinkButtons are rendered as plain anchor tags, like this: <a id="SelectNoneLK">None</a>
I know there is something wrong with the fact that the ID looks like that, since I am using a Master page for this and the ID should be something much longer.
Any help would be appreciated!
Nick
I'd guess that since cell is not part of the control hierarchy (you never add it to the table), the LinkButton's never find an IContainer parent to rewrite their ID's.
I tend to solve these types of issues using the excellent RenderPipe control that allows me to declare my controls in one place, but render them somewhere else.

Categories