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 .
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 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 %>
I'm binding my gridview's bound columns with datafield using the column name of my datatable. The problem is we have a scenario we need to put in a text where the datafield was int with value 0. I couldn't see any work around. Is there any easy way to do this?
If you don't like to use inline code in your aspx pages as David has suggested make a template with a literal control in it and implement the OnDataBinding event:
For example in your grid have the following template for your field:
<asp:TemplateField HeaderText="Your Header Name">
<ItemTemplate>
<asp:Literal runat="server" ID="litYourCustomField" OnDataBinding="litYourCustumField_DataBinding"></asp:Label>
</ItemTemplate>
</asp:TemplateField>
Then you implement the OnDataBinding in your code behind:
protected void litYourCustomField_DataBinding(object sender, System.EventArgs e)
{
Literal lit = (Literal)(sender);
int yourInt = Convert.ToInt32(Eval("YourNumber"));
lit.Text = (yourInt == 1) ? "It's a 1" : "It's something else";
}
I prefer this method to the inline code since it puts no code in your aspx pages. I usually have a #region defined in my .cs file that has all by databinding code. I am pretty sure performance wise they will be pretty much identical except for maybe the overhead of the literal control if you have the viewstate enable. Make sure to turn off viewstate when you don't need it.
If this is ASP.Net, you can make this a Template column and do the following:
<ItemTemplate>
<%# MyConversionFunction(Convert.ToInt32(DataBinder.Eval(Container.DataItem, "IntegerFieldName"))) %>
</ItemTemplate>
protected string MyConversionFunction(int ValueToCheck)
{
if(ValueToCheck.ToString() == "0")
{
return "SomeText";
}
else
{
return SomeValue.ToString();
}
}
I'm currently using a GridView and I want to set the CssClass for the Row depending on a property of the object that the row is being bound to.
I tried the following but it does not work (see comments):
<asp:GridView id="searchResultsGrid" runat="server" AllowPaging="true" PageSize="20" AutoGenerateColumns="false">
<!-- The following line doesn't work because apparently "Code blocks
aren't allowed in this context: -->
<RowStyle CssClass="<%#IIF(DataBinder.Eval(Container.DataItem,"NeedsAttention","red","") %>
<Columns>
<!--............-->
</Columns>
</asp:GridView>
Now I could simply handle the GridView's RowDataBound event and change the css class of the row there...but I'm trying to keep a clear separation between the UI and the page/business logic layers.
I have no idea how to accomplish this and I'm looking forward to hearing any suggestions.
Thanks,
-Frinny
You cannot do this in declarative markup.
Nearly all of GridView's declarative properties (including GridView.RowStyle) are grid-level settings rather than row-level. Apart from TemplateFields , they are not bound data containers, so they don't have access to the data in their rows.
If you want to keep this logic in the .aspx template, your only real option is to use template fields and manipulate their contents:
<asp:TemplateField>
<ItemTemplate>
<span class="<%# ((string)Eval("property3")) == "NeedsAttention" ? "red" : string.Empty %>">
<%# Eval("property1") %>
</span>
</ItemTemplate>
</asp:TemplateField>
Depending on what you want to do, this may be awkward - you don't have access to the containing <td> (or <tr> for that matter) and you'll have to repeat the formatting for each cell.
The GridView class goes to a lot of lengths to hide the details of HTML and styling from you. After all you could create a GridView control adapter that wouldn't even render as HTML tables. (Unlikely though that may be.)
So even though you're trying to avoid it, you're probably best off dealing with this in a OnRowDataBound handler - or use a Repeater (if that's appropriate).
I know it has been almost a year, but if anyone else is trying this, try to subclass the GridView.
public class GridViewCSSRowBindable : GridView
{
public string DataFieldRowCSSClass { get; set; }
protected override void OnRowDataBound(GridViewRowEventArgs e)
{
base.OnRowDataBound(e);
if (!string.IsNullOrEmpty(DataFieldRowCSSClass))
{
//This will throw an exception if the property does not exist on the data item:
string cssClassString = DataBinder.Eval(e.Row.DataItem, DataFieldRowCSSClass) as string;
if (!string.IsNullOrEmpty(cssClassString))
{
string sep = string.IsNullOrEmpty(e.Row.CssClass) ? string.Empty : " ";
e.Row.CssClass += sep + cssClassString;
}
}
}
}
And then in your Page:
<custom:GridViewCSSRowBindable ID="gvExample" runat="server" DataFieldRowCSSClass="RowCSS">
</custom:GridViewCSSRowBindable>
The objects being bound to this example GridView should have a public string RowCSS property.
If you haven't used inherited controls before, you might have to look up how to set that up in your project.
foreach (TableCell gvc in gvRowPhistry.Cells)
{
gvc.ForeColor = System.Drawing.Color.Blue;
}