I have a listview, where I set the datasource in the code-behind - this works fine.
When I add another listview (or databound control) to the listview's itemtemplate, and set the datasource for that control in the codebehind, the fields returned by the query seem to be unavailable to the nested listview; ASP.NET throws the following error: DataBinding: 'System.String' does not contain a property with the name 'j_Name'.
In the example below, d_Description works fine, whereas j_Role throws the error indicated above. I can see the data being returned by the query, and I know the column names match, so what's causing the error (and how do I solve it)?
ASPX page
<asp:ListView ID="LV1" runat="server">
<LayoutTemplate>
<table runat="server" id="tblSummary">
<tr runat="server" id="itemPlaceholder" />
</table>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td>
<%#Eval("d_Description")%>
</td>
</tr>
<tr>
<td>
<asp:ListView ID="LV2" runat="server">
<ItemTemplate>
<%#Eval("j_Role")%>
</ItemTemplate>
<LayoutTemplate>
<asp:placeholder id="itemPlaceholder" runat="server" />
</LayoutTemplate>
</asp:ListView>
</td>
</tr>
</ItemTemplate>
</asp:ListView>
CODE BEHIND
var qry1 = from q in context.Descriptions select q.d_Description;
LV1.DataSource = qualificationQry;
LV1.DataBind();
var qry2 = from q in context.Roles select q.j_Role;
LV2.DataSource = qualificationQry;
LV2.DataBind();
EDIT:
I've added code similar to that below to the ItemDataBound event of the outer listview, and I am still encountering the same error. Presumably I am misunderstanding the instructon?
protected void LV_ItemDataBound(object sender, ListViewItemEventArgs e)
{
using (dbDataContext context = new dbDataContext()
{
var qry2 = from q in context.Roles select q.j_Role;
ListView tempLV = (ListView)e.Item.FindControl("LV2");
tempLV.DataSource = qry2;
tempLV.DataBind();
}
}
EDIT: 2
After reading around the web some more (now that I have an idea of what to search for) the proposed answer seems to be correct - however, it's not working - can anyone suggest why?
EDIT: 3
If I ditch the outputting of j_Name, and just have a hardcoded string, there is no error, and the hard-coded string ouputs the expected number of times. This would indicate that it's just the column name (j_Name) that is wrong - even though I can see the dataset coming back from the query with that exact column name.
EDIT: 4
Fixed it.
This is wrong
var qry2 = from q in context.Roles select q.j_Role;
This is correct
var qry2 = from q in context.Roles select q;
You need to bind the inner list on every row of the outer list in the ItemDataBoundEvent for the outer list.
So this code:
var qry2 = from q in context.Roles select q.j_Role;
LV2.DataSource = qualificationQry;
LV2.DataBind();
will need to go in an even handler for the outer lists ItemDataBound event:
protected void ContactsListView_ItemDataBound(object sender, ListViewItemEventArgs e)
{
if (e.Item.ItemType == ListViewItemType.DataItem)
{
// Bind the inner list on every repeat of the outer list
var qry2 = from q in context.Roles select q.j_Role;
LV2.DataSource = qualificationQry;
LV2.DataBind();
}
}
And presumably you'll want to filter the values for the inner list based on a value from the current item of the outer list
Related
I have an aspx file and its small part of the code is
<table class="table table-responsive-sm table-bordered table-hover" runat="server" style="align-content:center;">
<tr><th>Header1</th></tr>
<tr><td id="name1"></td></tr>
</table>
<table class="table table-responsive-sm table-bordered table-hover" runat="server" style="align-content:center;">
<tr><th>Header2</th></tr>
<tr><td id="name2"></td></tr>
</table>
<%-- Similary there are manly table lets say upto 20 --%>
<table class="table table-responsive-sm table-bordered table-hover" runat="server" style="align-content:center;">
<tr><th>Header20</th></tr>
<tr><td id="name20"></td></tr>
</table>
it's code behind is like to set the value is each td :
public void Page_Load()
{
name1.InnerText = "1"
name2.InnerTex = "2"
//similary all td is assigned like this
name20.InnerText = "20"
}
This works fine but in the future there may by many table let's say 100. then do I need to set all the value like the code above. Is there any other method?
I tried using following code:
for(int i=1; i<21; i++)
{
tableId = (HtmlTableCell)this.Page.Master.FindControl("name" + i.ToString());
tableId = (HtmlTableCell)this.Page.FindControl("name" + i.ToString());
tableId = (HtmlTableCell)this.FindControl("name" + i.ToString());
tableId.InnerText = i.toString();
}
but the value of tableId is always null.
How can I solve this?
Ok, this is one of those cases in which we want to consider a different approach.
Now, I don't think it makes sense to have say 10, or even 20 tables here.
Lets think of this question:
I need 20 integer variables.
Do, I do this:
int x1;
int x2;
int x3;
... and so on for 20 variables?
No, we don't do that. We would do this:
int[20] x;
Now, I can use a for loop, even a foreach or whatever.
The same issue applies to your probelm.
You have a group or thing that you need "many" of.
As others in the comments suggest, a repeater (or listview, datalist, gridview) are controls or a "system" that lets you with great ease "repeat" somthing.
So, I like that "array" might need 2 or 50 of these "things".
It makes no sense to try and code out a html writer, or some loop to create this repeating thing. We can do this in code.
So, lets use a repeater.
Say like this:
<asp:Repeater ID="Repeater1" runat="server"
OnItemDataBound="Repeater1_ItemDataBound">
<ItemTemplate>
<asp:Label id="MyHeader" runat="server"
Text='<%# Eval("TableGroup") %>'
Font-Size="X-Large" >
</asp:Label>
</ItemTemplate>
</asp:Repeater>
Very simple. Now, do we want 2 table groups, or 60 of them?
Our code to load up the above can look like this:
We will simple create a list of "what" we want to repeat.
So, lets create something with table like structure. In fact, lets create a .net table!!!
Say we drop a button on the above, and then this code:
protected void Button1_Click(object sender, EventArgs e)
{
DataTable TblList = new DataTable();
TblList.Columns.Add("TableGroup");
for (int i = 1;i<=3;i++)
{
DataRow OneRow = TblList.NewRow();
OneRow["TableGroup"] = "Table " + i;
TblList.Rows.Add(OneRow);
}
Repeater1.DataSource = TblList;
Repeater1.DataBind();
}
Note how clean, how simple, and HOW we did not have to "try" and write code to spit out markup. And while that for/loop is 3, we could make it 30.
Now our output is this:
So, note how what we "made" repeats 3 times. But it could just as well repeat 20, or 100 times.
Now, inside each repeater, we want a table. Once again, we "could" use a table. but if you think about this, a table needs a header, then rows and for each row we would have to define a column.
Once again, that's quite a bit of work. So, lets use a control that can display + render a WHOLE table in one shot!
I suggest a gridview. So, now in the above repeater, lets drop in that grid view.
And we even use your bootstrap class(s) you specified - (since they look VERY nice - well done on your part!!!!).
Ok, so now our repeater becomes this:
<asp:Repeater ID="Repeater1" runat="server" OnItemDataBound="Repeater1_ItemDataBound">
<ItemTemplate>
<asp:Label id="MyHeader" runat="server"
Text='<%# Eval("TableGroup") %>'
Font-Size="X-Large" >
</asp:Label>
<asp:GridView ID="GTable" runat="server"
class="table table-responsive-sm table-bordered table-hover" Width="20%">
</asp:GridView>
</ItemTemplate>
</asp:Repeater>
<br />
Note HOW little markup we have so far. And Gridview is really just a nice table - but one that's less work and hassle.
So, now we need to add code to fill up the table for each group.
Turns out, there is HANDY event that fires for each group creation for us!!!!
So, lets just cook up a table - say a user name, and age.
So, we use the ItemDataBound event. This event fires for each group!!!
So, 3 groups, or 60 - it will not matter.
So, we can code out a table, then then STUFF it right into our gridview.
this code:
protected void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
// each new group/thing, lets fill the grid/table
GridView gV = (GridView)e.Item.FindControl("GTable");
DataTable TestTable = new DataTable();
TestTable.Columns.Add("Name");
TestTable.Columns.Add("Age");
int TableGroup = e.Item.ItemIndex + 1;
// add 3 rows
for (int i = 1; i <= 3; i++)
{
DataRow OneRow = TestTable.NewRow();
OneRow["Name"] = "Group " + TableGroup + " Name " + i;
OneRow["Age"] = i * 15;
TestTable.Rows.Add(OneRow);
}
gV.DataSource = TestTable;
gV.DataBind();
}
Note again:
We are ONLY writing code for ONE thing. Might be repeated over and over, but we don't care. Note also how "easy" it is to think this way.
Now, I did include the repeater "index" to at least add somewhat different rows for each table.
Note again how NICE it is to create that table. It gives us headings, rows, columns
all in a nice simple structure.
And our output is now this:
Is that not cool or what?
So, now with the above automatic rendered.
What do you want to do? Grab a particular repeater item? Maybe add a button to each grid row? We can do that, do that one time, and it will repeat for us.
but, you can get say the 2nd row this way:
protected void Button2_Click(object sender, EventArgs e)
{
// get 2nd repeater (0 based, so get #1)
int RepeatNum = 1;
RepeaterItem MyRepItem = Repeater1.Items[RepeatNum];
// ok, got the item, get <h2> header item
//
Label MyHead = (Label)MyRepItem.FindControl("MyHeader");
Debug.Print(MyHead.Text);
// now get our grid
GridView GV = (GridView)MyRepItem.FindControl("GTable");
foreach (GridViewRow gRow in GV.Rows)
{
Debug.Print("Name = " + gRow.Cells[0].Text);
Debug.Print("Age = " + gRow.Cells[1].Text);
}
output window:
Table 2
Name = Group 2 Name 1
Age = 15
Name = Group 2 Name 2
Age = 30
Name = Group 2 Name 3
Age = 45
So think in groups. I have VERY little markup. And BETTER is how the code behind does not try and spit out a ZILLION "tr" "th" "table" etc. I don't write all that HTML - but leverage the built in tools.
What is nice, is I can now change my mind, and say render this as say "two things" going accross the page - kind of like a "card view".
So, we wrote ONE thing for the group.
So we wrote ONE thing for the table.
We then repeated both over and over.
I have a drop down list box like following. Under some condition, in the databound event I want to remove the items whose bitActive is set to 0 (inactive). I did not put a WHERE bitAcive!=0 in the selectCommand, because I only want to remove them under some conditions. Is there any way I could iterate the items and check the value of bitActive?
<tr>
<td width="30%" align="right">Location<span class="littlefont">*</span></td>
<td width="70%" align="left">
<asp:DropDownList ID="ddlLocation" runat="server"
DataSourceID="SqlDSLocation" DataTextField="txtRefLocation_Name"
DataValueField="intRefLocation_ID" ondatabound="ddlLocation_DataBound">
</asp:DropDownList>
<asp:SqlDataSource ID="SqlDSLocation" runat="server"
ConnectionString="<%$ ConnectionStrings:SPRConnectionString %>"
SelectCommand="SELECT DISTINCT [intRefLocation_ID], [txtRefLocation_Name], [location], [bitActive] FROM [tblRefLocation] ORDER BY [intRefLocation_ID]">
</asp:SqlDataSource>
</td>
</tr>
In codebehind you can call the SQLDataSource.Select() method:
System.Data.DataView dv = (System.Data.DataView)SqlDSLocation.Select(DataSourceSelectArguments.Empty);
And then iterate through the rows returned, finding the "bitActive" rows who are set to zero and removing them from your DropDownList (code sort of hacked from the example linked above):
foreach(System.Data.DataRow row in dv.Table.Rows)
{
// This is approximate logic, tailor this to fit your actual data
if (row["bitActive"].ToString() == "False")
{
ddlLocation.Items.Remove(row["intRefLocation_ID"].ToString());
}
}
Note that this is not removing these rows from your SQL table. Make sure you don't databind your DropDownList again after this - otherwise all the stuff you just removed will return.
EDIT: For a more efficient and elegant solution, see James Johnson's answer.
Instead of removing items in the ItemDataBound event, why not just filter the datasource before binding it?:
var table = new DataTable("MyTable"); //assume it's populated
if (table.Rows.Count > 0)
{
var results = table.AsEnumerable().Where(r => r.bitActive).AsDataView().ToTable();
if (!results.HasErrors)
{
DropDownList1.DataSource = results;
DropDownList1.DataBind();
}
}
I have a repeater like this:
<asp:Repeater runat="server" ID="pde">
<HeaderTemplate></HeaderTemplate>
<ItemTemplate>
<asp:Literal runat="server" ID="literal1"></asp:Literal>
</ItemTemplate>
<FooterTemplate><asp:Literal runat="server" ID="literal2"></asp:Literal></FooterTemplate>
</asp:Repeater>
Now in literal2 I want to get the value from code behind.But I am not able to get the literal2 in code behind.Any idea how to get this?
You should be able to access it by accessing the last RepeaterItem in your repeater which will be your footer. Then do a search, using FindControl, on the RepeaterItem for any control you are looking for.
Using your example above do:
Literal controlYouWant = pde.Controls[pde.Controls.Count - 1].FindControl("literal2") as Literal;
You can break down the statements to:
// This will get you the footer
RepeaterItem item = pde.Controls[pde.Controls.Count - 1];
// From here you finds any control you want within the RepeaterItem.
Literal controlYouWant = item.FindControl("literal2") as Literal;
First search through items in repeater and only take the footer item type and discard the others. Take a look to the following code.
foreach(RepeaterItem item in repeterName.Controls)
{
if (item.ItemType != ListItemType.Footer) continue;
var lblMyLabel = ((Label)item.FindControl("lblMyLabel"));
lblMyLabel.Text = "I found it!!!";
}
My repeater's templates generate a table, where each item is a table row.
When a very very specific condition is met (itemdata), I want to add an additional row to the table from this event.
How can I do that?
protected void rptData_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item ||
e.Item.ItemType == ListItemType.AlternatingItem)
{
bool tmp = bool.Parse(DataBinder.Eval(e.Item.DataItem, "somedata").ToString());
if (!tmp && e.Item.ItemIndex != 0)
{
//Add row after this item
}
}
}
I can use e.Item.Controls.Add() and add TableRow but for that I need to locate a table right?
How can I solve that?
I would put in the control in the repeater item template, set its visibility to hidden & only in that case, I will show it...
Like:
<asp:Repeater ...>
..
<ItemTemplate>
<tr>
<td>...</td>
</tr>
<tr runat="server" id="tr_condition" Visible="false">
<td> Show only in specific condition </td>
</tr>
Then in the ItemDataBound event:
var tr_condition = e.Item.FindControl("tr_condition") as HtmlTableRow;
tr_condition.Visible = myCondition;
where myCondition is a flag which gets sets on the specific condition
HTH
One way to do this is to include the row in the template code, and set the Visible property based on your 'tmp' variable. The you don't need to 'add' it - it is already there.
Edit: another idea - insert your extra row using a <%# %> data binding block. To do this, you need to generate the <tr><td>.. code as a atring in your ItemDataBound event. Use a protected string variable - like this:
protected string _extraRowHtml;
in ItemDataBound:
_extraRowHtml = tmp ? "<tr><td> -- generated content -- </td></tr>" : string.Empty;
in ItemTemplate:
<tr><td> -- regular row content --</td></tr>
<%# _extraRowHtml %>
is there any way to bring up a subheader row at changes in a field in an databound asp:repeater control e.g.
Instead of
country | colour | number
uk | red | 3
uk | green | 3
france | red 3
Do this:
==UK==
colour | number
red | 3
green 3
==FRANCE==
colour | number
red | 3
Many thanks for any help.
There's no built-in support, but that doesn't mean it's impossible.
You'll need to over-ride the OnItemDataBound event, and have something like this in markup:
<asp:Repeater OnItemDataBound="NextItem" ... >
<ItemTemplate><asp:Literal Id="Header" Visible="False" Text="{0}<strong>{1}</strong><br/><table>" ... />
<tr>
<td><asp:Label id="Color" Text="<%# Eval("Color")" ... /></td>
<td><asp:Label id="Number" Text="<%# Eval("Number")" ... /></td>
</tr>
</ItemTemplate>
</asp:Repeater></table>
Then in the code-behind:
private string CurCountry = string.Empty;
private void NextItem(object sender, RepeaterItemEventARgs e)
{
if ( e.Item.ItemType != ListItemType.Item
&& e.Item.ItemType != ListItemType.AlternatingItem) return;
DbDataRecord row = (DbDataRecord)e.Item.DataItem;
if (CurCountry != row["country"].ToString() )
{
string prev = (CurCounter == string.Empty)?"":"</table>";
CurCountry = row["country"].ToString();
Literal header = (Literal)e.Item.FindControl("Header");
Literal footer = (Literal)e.Item.FindControl("Footer");
header.Text = string.Format(header.Text, prev, CurCountry);
header.Visible = true;
}
}
Another solution is to simply use two repeaters, one nested within the other. You can pass your groups with the child records to the first repeater, and on the ItemDataBound of the groups repeater, pass the child records to the child repeater and call DataBind() there.
This is more code but does actually give you more control over the layout without having HTML creation code in your code-behind.
As you can see here, we have a parent repeater and in the item template we can customise each group as we see fit. In the ChildRepeater, we have our item template in which we can customise each item inside the grouping. Very clean and all with declarative UI.
<asp:Repeater runat="server" id="GroupRepeater">
<ItemTemplate>
<asp:Literal runat="server" id="HeaderText" />
<asp:Repeater runat="server id="ChildRepeater">
<ItemTemplate>
<asp:Literal runat="server" id="InfoGoesHere" />
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
In the code behind we can have something like this:
private void GroupRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
//Get the child records, this can be any data structure you want
SomeChildCollection children = ((SomeGroupCollection)e.Item.DataItem).Children;
//Find the child repeater
Repeater childRepeater = e.Item.FindControl("ChildRepeater") as Repeater;
childRepeater.ItemDataBound += SomeMethod;
childRepeater.DataSource = children;
childRepeater.DataBind();
}
After binding each child you can subscribe to the ItemDataBound event and do the child binding to controls as you see fit.
To joel Solution, I may advice following - change this line:
header.Text = string.Format("<strong> {0} </strong><br/><table>", CurCountry);
to
header.Text = string.Format(header.Text, CurCountry);
And than you can customize look & feel from .as?x file.
Ah - you're after a grouping repeater. The solution that I've adapted in the past seems to have disappeared. Maybe you can find it. But anyway, a search for grouping repeaters should help.
There seem to be a few bugs in the dotnetjunkies code but I've found a much simpler solution here http://www.west-wind.com/weblog/posts/140753.aspx
Now repeaters don't support grouping, so you have to do this on your own, but it's pretty easy to do. In the repeater above the following data expression is responsible for the grouping:
<%# this.RenderGroup(Eval("Group") as
string) %>
The expression calls a method on the form to render an extra div tag. The code for this simple method looks like this:
string LastGroup = "###~";
protected string RenderGroup(string Group) {
if (Group == this.LastGroup)
return ""; // *** Group has changed
this.LastGroup = Group;
return "<div class='groupheader'>" +Group + "</div>";
}
This code gets called for every item and it simply checks to see if the group has changed. If it hasn't nothing is returned otherwise a simple div tag is returned as a string to embed into the markup. I chose to send back the HTML but I suppose you could also return true or false and conditionally render a control in the Repeater which would make the CSS Nazis happier .