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.
Related
Is it possible to store in the same array - list of different type elements declaring in the same array different property value (bool)? For exampe list of those elements:
GV1_BTNEdit.Visible = false;
GV1_BTNCancel.Visible = true;
GV1.Columns[3].Visible = true;
GV1.Columns[5].Visible = true;
GV1.Columns[4].Visible = true;
Lbl1_GV1.Visible = true;
Edit: (pseudo-code)
This is how funcionality should look like:
Declares a list/array of elements with a simultaneous declaration of parameters / properties of these elements:
list/element array (list_name)
{
textbox1.visible = true;
label1.visible = false;
button1.visible = true;
}
Calling a list/array, i.e. simultaneous assignment to the elements of the list - the property resulting from their declaration in the list:
list_name[];
Calling the list with the same property change, i.e. simultaneous assigning to all elements of the list the property visible = false:
list_name[i].visible = false;
Calling the list with changing the property of visible elements to the opposite (i.e. what was true will change to false, and what was false - to true):
list_name[i].visible != list_name[i].visible;
If, after all, it cannot be done in this particular way, is there another similar possibility?
As a general rule, often it is better to place a group of controls say inside of div, and then simple hide/show the div to show/hide the group of controls.
so say this:
<div id="BillAddress" runat="sever">
Text address, and more controls here
</div>
Then in code, you can hide/show the whole mess with
BillAddress.Style("display") = "none";
Or to show that group of controls, then this:
BillAddress.Style("display") = "normal";
So, web land, and web markup lends itself to MUCH better using span's, or div's, or whatever to group controls.
And the beauty of above, is then you can also often wirte client side code in JavaScript to hide/show groups of controls inside that div 100% with client side code.
Having stated + suggested the above?
You can also say cook up your "own" attributes on a group of controls.
Say:
<asp:TextBox ID="TextBox1" runat="server"
MyGroup="Billing" >
</asp:TextBox>
<asp:TextBox ID="txtNewCity" runat="server"
MyGroup="Billing>
</asp:TextBox>
So, note how in above I just made up a attribute for the control.
Then, in code, can do this:
For Each c As System.Web.UI.Control In Page.Controls
If c.Attributes("MyBilling") IsNot Nothing Then
// hide/show the control
So, in web land, I don't really think it makes a whole lot of sense to attempt to group some controls in a array - just drop all those controls inside of some div, and hide/show that div. But, you can use "tag" or even cook up + add your own attributes to controls, and then loop the controls in that page, or div or whatever.
For example, I cooked up my own "data binder" routine.
I simple will place a group of controls say inside of a "div" with a "id" and runat server.
Then, to load data into that "div" and set of controls?
I place controls in a div, and then I can do this:
int intPK = (int)ViewState[this.ID + "_MyPk"];
string strSQL = "SELECT * FROM " + this.MyTable + " WHERE ID = " + intPK;
DataTable rstData = General.MyRst(strSQL);
General.FLoader(this.EditRecord, rstData.Rows[0]);
In above, EditRecord is my "div"
The markup looks like:
<asp:TextBox ID="txtHotel" runat="server" f="HotelName" width="280"></asp:TextBox> <br />
<asp:TextBox ID="tFN" runat="server" f="FirstName" Width="140"></asp:TextBox> <br />
etc. etc.
So, I use a made up attribute called "f", where f="database column name"
So, with that simple idea, then I can load up a whole data form with about 3 lines of code.
So, say I click edit on a grid.
I pop this form:
but, the form data is done with my fLoader routine. All it does is look for "f" attributes, and fills out the control from the database.
And then for save, I have Fwriter. Does the same thing, but in reverse.
So, save button code for above is this:
protected void cmdSave_ServerClick(object sender, EventArgs e)
{
int intPK = (int)ViewState[this.ID + "_MyPk"];
string strSQL = "SELECT * FROM tblHotels WHERE ID = " + intPK;
DataTable rstData = General.MyRst(strSQL);
General.FWriterUpdate(this.EditRecord, rstData.Rows[0]);
}
So, Fwriter again just loops the controls inside of that div, and transfers back from the controls their values into the table.
So, in above, we see two examples of grouping controls, and then being able to "operate" on that group of controls.
No arrays required here. so, I used these concepts for my own data binding system, and thus I NEVER have to write code to load up each individual control from the database. Nor do I have to write code to send + save the data back to the database.
I means I quite much have ms-access like drag + drop of controls, and the shuffle from database to the form is done for me. And this all works due to the requiring to "group" and identify controls on the page, and what I want to do with that group of controls.
Edit: Processing conrols in an array or list
In all of the above "div" and control processing routines, we REALLY are doing the same as if we placed the controls into an array, or list.
For example, the floader routine does this: (we can pass it the current web page, or as more often a "div".
So, it looks like this:
foreach (System.Web.UI.Control c in F.Controls)
{
if (c.GetType() == typeof(TextBox))
{
TextBox ctlC = c as TextBox;
ctlC.Text = "zoo";
ctlC.Visible = true;
}
else if (c.GetType() == typeof(Label))
{
Label ctlC = c as Label;
}
else if (c.GetType() == typeof(DropDownList))
{
DropDownList ctlC = c as DropDownList;
}
else if (c.GetType() == typeof(CheckBox))
{
CheckBox ctlC = c as CheckBox;
}
I removed most of the code, but above shows how you can pass/have a bunch of controls in a page, or div or whatever.
So, my WHOLE post and narrative been based on the above approach - which I assumed was obvious here.
However, lets code up a collection, and thus make this even more obvious.
so this markup:
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<br />
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
<br />
<asp:CheckBox ID="CheckBox2" runat="server" />
So, our code behind can then do this:
List<Control> mycontrols = new List<Control>();
mycontrols.Add(TextBox1);
mycontrols.Add(CheckBox2);
mycontrols.Add(Label1);
foreach (Control myC in mycontrols)
{
myC.Visible = false;
}
// set text value of controls
foreach (Control myC in mycontrols)
{
myC.Visible = false;
if (myC.GetType() == typeof(TextBox))
{
TextBox ctlC = myC as TextBox;
ctlC.Text = "zoo";
ctlC.Visible = true;
}
else if (myC.GetType() == typeof(Label))
{
Label ctlC = c as Label;
ctlC.Text = "zoo zoo";
}
else if (myC.GetType() == typeof(CheckBox))
{
CheckBox ctlC = c as CheckBox;
ctlC.Checked = true;
}
}
So, for visible, we can use visible property. but, for other features of the control, say like Text or whatever? We cast the control into its type, and thus can use say checked of the checkbox, and it does not even have a .text property.
I have a WebHierarchicalDatagrid where I manually create the columns in my PageLoad() event. None of my columns shows on initial page load, even though the datasource has data that matches the columns. (I DO set and bind after the columns are created/added to the grid). If I refresh (PostBack), then they show. I have cleared and reset the grid many ways.
If I define columns in markup then the initial page load works of course, but I need to dynamically create columns based on my user roles and if I clear and recreate the desired columns in my page load, page prerender, etc I get a viewstate error.
Seems that you cannot use markup and codebehind to define a grid. Would not be a problem, but I have a custom (user control) pager template defined in markup and I spent days trying to get create that in codebehind given I cannot just point the codebehind pager creation to use an existing user control. Total catch-22.
Infragistics grids are just too tweaky to deal with anymore. If you stay on the straight and narrow, they are good, but stray off the path and you are in big trouble!
AutoGenerateBands and AutoGenerateColumns should be set to false. Also, I don't know whether you are using GridView to configure the grid or not, although I wanted to let you know that WebHierarchicalDataGrid.Columns collections is relevant to the root band of the columns defined at designed time or from the markup. As for the columns that are auto generated, they could be accessed from WebHierarchicalDataGrid.GridView.Columns.
As I understand you are creating the columns from Page_Load event, try to do that on WHDG_Init. I am just curious what would be the result.
protected void WebHierarchicalDataGrid1_Init(object sender, EventArgs e)
{
WebHierarchicalDataGrid1.DataSource = new TestData().GetData();
WebHierarchicalDataGrid1.DataKeyFields = "ID";
WebHierarchicalDataGrid1.Columns.Add(CreateNewBoundDataField("ID", "ID"));
WebHierarchicalDataGrid1.Columns.Add(CreateNewBoundDataField("Name", "Name"));
WebHierarchicalDataGrid1.Bands.Add(CreateNewBand("ChildBand_0", "Child", "ChildID"));
WebHierarchicalDataGrid1.Bands["ChildBand_0"].Columns.Add(CreateNewBoundDataField("ChildID", "ChildID"));
WebHierarchicalDataGrid1.Bands["ChildBand_0"].Columns.Add(CreateNewBoundDataField("ID", "ID"));
WebHierarchicalDataGrid1.Bands["ChildBand_0"].Columns.Add(CreateNewBoundDataField("Address", "Address"));
WebHierarchicalDataGrid1.Bands["ChildBand_0"].Behaviors.CreateBehavior<Filtering>();
}
public static BoundDataField CreateNewBoundDataField(string columnName, string headerText)
{
BoundDataField boundDataField = new BoundDataField();
boundDataField.DataFieldName = columnName;
boundDataField.Key = columnName;
boundDataField.Header.Text = headerText;
return boundDataField;
}
public static Band CreateNewBand(string key, string dataMember, string dataKeyField)
{
Band band = new Band();
band.AutoGenerateColumns = false;
band.Key = key;
band.DataMember = dataMember;
band.DataKeyFields = dataKeyField;
return band;
}
I'm dynamically creating controls on my page (aspx) using the code below, but when the asynchronous postback triggers the radio button doesn't retain it's selection and the method uptheup doesn't get called. I'm guessing it's to do with the view state not returning the values, but I thought if the control ID was the same and it's created in Page_Init the in Page_Load the value should automatically be set from view state??
What I want is if someone selects No for the textbox to become visible. The control IDs are set from a database and are the same each time it loads as the code is used several times (I've replaced the IDs for easy reading below)
the following is called from Page_Init
RadioButtonList rbtnl = new RadioButtonList();
rbtnl.ID = "rbl_1";
rbtnl.Items.Add("Yes");
rbtnl.Items.Add("No");
rbtnl.AutoPostBack = true;
rbtnl.EnableViewState = true;
rbtnl.SelectedIndexChanged += new EventHandler(uptheup);
rbtnl.ClientIDMode = System.Web.UI.ClientIDMode.AutoID;
scriptmanager1.RegisterAsyncPostBackControl(rbtnl);
TextBox tbx = new TextBox();
tbx.ID = "tb-1";
tbx.CssClass = "form-control";
tbx.Visible = false;
UpdatePanel upx = new UpdatePanel();
upx.ID = "up-1";
upx.ContentTemplateContainer.Controls.Add(rbtnl);
upx.ContentTemplateContainer.Controls.Add(tbx);
upx.UpdateMode = UpdatePanelUpdateMode.Always;
upx.EnableViewState = true;
upx.ChildrenAsTriggers = true;
plcEvalBody.Controls.Add(upx);
OK so the code is fine and it works well, the issue was me assuming the IDs weren't the issue - they are - NEVER USE $ in control ID's. I'd used this to separate two sections of the ID and it is not valid.
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;
}
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?