On one of our pages, we've got a table that has rows generated at runtime. I want to move the content of those rows out into their own control and set properties on it at runtime, in order to separate the presentation from the code-behind. But it's not working the way I'm expecting.
If I have my row-like control inherit from UserControl, then I can do this to create the control at runtime:
MyControl row = (MyControl)LoadControl("~/controls/MyControl.ascx");
Then, I wanted to add that to my table:
MyTable.Rows.Add(row);
But row does not inherit from System.Web.UI.HtmlControls.HtmlTableRow or System.Web.UI.WebControls.TableRow, so you can't do that. However, if I make my control inherit from one of those two, then the LoadControl call above complains that the control doesn't inherit from UserControl.
What's the happy solution here? If additional information is needed, please ask.
I think what you're looking to do is this:
MyControl row = (MyControl)LoadControl("~/controls/MyControl.ascx");
TableCell newCell = new TableCell();
newCell.Controls.Add(row);
TableRow newRow = new TableRow();
newRow.Cells.Add(newCell);
MyTable.Rows.Add(newRow);
The problem as you stated is that your control cannot inherit from multiple base classes and you cannot add your control directly to the Rows collection. You need to first wrap it. There may be cleaner solutions, but this one does work.
Related
Following these two threads:
How can I create an Array of Controls in C#.NET?
Cannot Access the Controls inside an UpdatePanel
I current have this:
ControlCollection[] currentControlsInUpdatePanel = new ControlCollection[upForm.Controls.Count];
foreach (Control ctl in ((UpdatePanel)upForm).ContentTemplateContainer.Controls)
{
currentControlsInUpdatePanel.
}
currentControlsInUpdatePanel does not have an add or insert method. why does the first link i post allow that user to .add to his collection. This is what I want to do, find all the controls in my upForm update panel. but i dont see how i can add it to my collection of controls.
I don't think this code makes sense. You are creating an array of ControlCollection objects and trying to store Control objects in it. Furthermore, since currentControlsInUpdatePanel object is an array, there will not be an Add() method available on that object.
If you want to use the Add() method, try creating currentControlsInUpdatePanel as a List object.
Example:
List<Control> currentControlsInUpdatePanel = new List<Control>();
foreach(Control ctl in ((UpdatePanel)upForm).ContentTemplateContainer.Controls)
{
currentControlsInUpdatePanel.Add(ctl);
}
If you want to continue to use an array to store the Control objects, you will need to use the index value to set your objects in the array.
Example:
Control[] currentControlsInUpdatePanel = new Control[((UpdatePanel)upForm).ContentTemplateContainer.Controls.Count];
for(int i = 0; i < upForm.Controls.Count; i++)
{
currentControlsInUpdatePanel[i] = ((UpdatePanel)upForm).ContentTemplateContainer.Controls[i];
}
The UpdatePanel's child controls collection is a special collection that contains only one child control: its template container. It is then that control that contains all the child controls of the UpdatePanel (such as a GridView or Button).
As it is noted in the other questions linked to in the question, walking the child control tree recursively is the best way to go. Then, when you've found the spot to which you need to add controls, call Controls.Add() in that place.
My suggestion would be a different approach: place an <asp:PlaceHolder> control in the UpdatePanel and give it a name and add controls to that. There should be no particular advantage to accessing the controls collection of the UpdatePanel itself, and then you wouldn't have to dig through implementation details of the controls (which, while they are unlikely to change, can make code much more difficult to read).
Try to use
ControlCollection collection = ((UpdatePanel)upForm).ContentTemplateContainer.Controls;
This gives you all the controls in that control collection. From there you can use CopyTo to copy it to the array you need:
Control[] controls = new Control[collection.Length];
collection.CopyTo(controls , 0);
I have a function that returns a DataTable that I can databind to a DropDownlist or Repeater just fine. However, if I databind an IEnumerable of the DataTable's DataRows, I get an HttpException: "DataBinding: 'System.Data.DataRow' does not contain a property with the name 'some_column'".
repeater.DataSource = ThisReturnsDataTable(); // Works fine
repeater.DataSource = ThisReturnsDataTable.AsEnumerable(); // HttpException
Why is this?
I'm not looking for a solution to the problem, like for example:
repeater.DataSource = ThisReturnsDataTable().AsEnumerable().Select(
x => new {some_column = x["some_column"]});
I simply want to know why the databinding with an IEnumerable of DataRows fails.
I found a pretty good explanation here, although his first solution to the problem, AsDataView(), doesn't seem to work/exist (in 3.5, at least). CopyToDataTable() works swimmingly, though.
.Net DataTables can be very useful when writing data-driven
applications. However, they have one limitation: There is no obvious
way to databind a grid (or other control) to an arbitrary list of
datarows from a table. You can bind to an entire table directly by
setting a DataSource to the DataTable itself, and you can bind to a
subset of a table by creating a DataView with a filter.
In general, you cannot bind to an IEnumerable (eg, a LINQ query);
the databinding infrastructure can only handle an IList (non-generic)
or an IListSource. This is true for any kind of datasource.
Therefore, to bind to any LINQ query, you need to call .ToList(). (Or
.ToArray())
However, when binding to a DataTable, you can’t even use a
List. If you try, you’ll get four columns (RowError,
RowState, Table, and HasErrors) and no useful information. This
happens because the List doesn’t tell the databinding
infrastructure about the special properties of the DataRows. To
understand the problem, some background is necessary
Databinding is controlled by the ListBindingHelper and TypeDescriptor
classes. When you bind to a list, the
ListBindingHelper.GetListItemProperties method is called to get the
columns in the list. If the list implements the ITypedList interface,
its GetItemProperties method is called. Otherwise, it will use
TypeDescriptor to get the properties of the first item in the list.
(this uses reflection)
The DataView class (which DataTable also binds through, using
IListSource) implements ITypedList and returns
DataColumnPropertyDescriptors that expose the columns in the table.
This is why you can bind to a DataView or DataTable and see columns.
However, when you bind to a List, there is no ITypedList that
can return the columns as properties. It therefore falls back on
reflection and shows the physical properties of the DataRow class.
To solve this issue, you need to wrap the list in a DataView so that
you can take advantage of its ITypedList implementation. You can do
that using the AsDataView() method. This method is only available on
the DataTable and EnumerableRowCollection classes; it cannot be
called on an arbitrary LINQ query. You can only get an
EnumerableRowCollection by calling special versions of the Cast,
OrderBy, Where, and Select methods from a DataTable.
Therefore, you can databind to a simple LINQ query by calling
AsDataView() on the query. To bind to a List, or to a more
complicated query, you can use an ugly hack:
List<DataRow> list = ...;
grid.DataSource = datatable.AsEnumerable()
.Where(list.Contains)
.AsDataView();
The AsEnumerable() call is not needed for typed datasets.
You can also call CopyToDataTable(), which will works [sic] on an arbitrary
IEnumerable. However, it makes deep copies of the rows, so
it isn’t helpful if you want the user to update the data, or if you
want the user to see changes made (in code) to the original datarows.
From: http://blog.slaks.net/2011/01/binding-to-lists-of-datarows.html
I could be wrong, but I believe instead of doing the .Where clause, you should be able to do something like this:
DirectCast([datatable].AsEnumerable, EnumerableRowCollection(Of DataRow)).AsDataView()
I've two issues currently preventing me from finishing two projects properly. I'll put them both here as I believe they're connected to the asp.net page lifecycle, but I can't find a way around them.
First I have a DropDownList which I must sort in codebehind. It only contains text, so I should be able to do that with the following method called in page load:
Dim alist As ArrayList = New ArrayList
For Each litem As ListItem In ltEsittelyDropDownList.Items
alist.Add(litem.Text)
Next
alist.Sort()
Dim uusiDDList As New DropDownList
For i As Integer = 0 To alist.Count - 1
Dim litem As New ListItem
litem.Text = alist(i).ToString
litem.Value = alist(i).ToString
uusiDDList.Items.Add(litem)
' Response.Write(alist(i).ToString)
Next
ltEsittelyDropDownList = uusiDDList
ltEsittelyDropDownList.DataBind()
As you can see, there's a commented response.write in there, which shows the list is actually sorted. So why, when I load the page, can't I see any effect?
The other problem, which is more critical and difficult, is as follows:
In the aspx page I'm binding a SQL Server 2005 datasource to a gridview. And in the code-behind I catch on to the RowDataBound event in which I handle some links and properties inside the gridviews' cells. But I cannot get this to work on the first page load, only after the first extra postback.
So, what is there to do? And thanks for all advice in front!
Your first problem is calling DataBind on a control you have filled manually. You likely have a DataSource specified in the control declaration, which is being used when DataBind is called. You can simplify the code by just adding the list items to the original control:
For i As Integer = 0 To alist.Count - 1
ltEsittelyDropDownList.Items.Add(New ListItem(alist(i).ToString())
Next
Alternatively, as you have a collection already, you can just bind it to the control:
ltEsittelyDropDownList.DataSource = alist
ltEsittelyDropDownList.DataBind()
For your second problem, some example code would help - specifically, where and how the control is databound and the code in RowDataBound.
I am developing an ASP.NET web application at work, and I keep running into the same issue:
Sometimes I want to write HTML to the page from the code-behind. For example, I have a page, Editor.aspx, with output which varies depending on the value of a GET variable, "view." In other words, "Editor.aspx?view=apples" outputs different HTML than "Editor.aspx?view=oranges".
I currently output this HTML with StringBuilder. For example, if I wanted to create a list, I might use the following syntax:
myStringBuilder.AppendLine("<ul id=\"editor-menu\">");
myStringBuilder.AppendLine("<li>List Item 1</li>");
myStringBuilder.AppendLine("</ul>");
The problem is that I would rather use ASP.NET's List control for this purpose, because several lines of StringBuilder syntax hamper my code's readability. However, ASP.NET controls tend to output convoluted HTML, with unique ID's, inline styles, and the occasional block of inline JavaScript.
My question is, is there an ASP.NET control which merely represents a generic HTML tag? In other words, is there a control like the following example:
HTMLTagControl list = new HTMLTagControl("ul");
list.ID = "editor-menu";
HTMLTagControl listItem = new HTMLTagControl("li");
listItem.Text = "List Item 1";
list.AppendChild(listItem);
I know there are wrappers and the such, but instead of taming ASP.NET complexity, I would rather start with simplicity from the get-go.
is there an ASP.NET control which
merely represents a generic HTML tag?
Yes, it's called the HtmlGenericControl :)
As far as exactly what you want no, but you can get around it easily:
HtmlGenericControl list = new HtmlGenericControl("ul");
list.ID = "editor-menu";
HtmlGenericControl listItem = new HtmlGenericControl("li");
listItem.InnerText = "List Item 1";
list.Controls.Add(listItem);
If you really need to get down to bare metal then you should use the HtmlTextWriter class instead of a StringBuilder as it is more custom tailored to pumping out raw HTML.
If you want to just assign the results of your stringbuilder to a blank control, you can use an <asp:Literal /> control for this.
LiteralControl has constractor that you can pass your html...
i think it is better.
new LiteralControl(sb.ToString());
What are the advantages/disadvantages of using this model
<asp:GridView Id="grdEmployees" runat="server" DataSourceID="objEmployees">
...
</asp:GridView>
in comparison with programmatically creating controls and binding data to them, etc...?
In which cases should one use each method?
There's no right or wrong here - it depends on what you're trying to achieve.
You can safely use static controls when you know your layout is not subject to change (i.e. a grid with a fixed number of columns), or any future possible changes are likely to be of static nature (a new column in your grid). This approach is perfectly fine and obviously faster in the average real world case.
If you need flexibility then you need to generate the controls programmatically (i.e. you need to generate a variable number of grids - or any other controls). For flexibility we mean that your layout needs to take into account variable values you won't be able to know till runtime.
I find uses for both models, but tend to try to use the markup in the aspx pages where I can because it is easier to read and helps me separate my view code from my logic code. The places where I programatically create controls and bind data to them are when I need a dynamic number of controls. A good example might be when you are generating a set of drop-downs dynamically for user search criteria -- I would do something like this:
SqlDataReader dr;
// Set up database connection and set dr to search query.
while(dr.Read())
{
Literal name = new Literal();
name.Text = dr["Name"] + ": ";
Page.Controls.Add(name);
DropDownList ddl = new DropDownList();
ddl.ID = "Search_" + dr["ID"];
SqlDataReader dr2;
// Set up database connection and set dr2 to search items query.
while(dr2.Read())
{
ListItem li = new ListItem(dr2["Name"], dr2["Value"]);
ddl.Item.Add(li);
}
Page.Controls.Add(ddl);
}
One other thing to keep in mind is you can create markup controls in your aspx page and then bind them to custom DataSets that you populate in your code-behind.
UI state should only be affected the business process it's representing. You should therefore aspire to have your UI automatically update to changes in the business model. If you're programming the UI manually then you're more likely to have cases where the state of the business model is not accurately reflected. Programming the UI declaratively removes most of this concern. Where possible, use the declarative method.