Adding CSS Class on RowDataBound - asp.net

I'm trying to append a CSS class to a row on RowDataBound. I'm using the alternating css class property against the GridView, so I'm assuming this is applied on RowDataBound. If you assign a CSS class programatically to the CssClass property of the row within the RowDataBound event then the alternating row style css class is not applied, even if you append the CSS class.
Here's what I've got:
Protected Sub gvGeneral_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles gvGeneral.RowDataBound
If e.Row.RowType = DataControlRowType.DataRow Then
If previousExpenseType <> e.Row.Cells(2).Text And previousExpenseType.Length > 0 Then
e.Row.CssClass += "bottom-border"
End If
previousExpenseType = e.Row.Cells(2).Text
End If
End Sub
previousExpenseType is just a String which I'm doing comparisons against. If the expense type changes, then I want to have a border applied to the bottom of the <tr> element.
Any ideas how to get around this? It seems there's an event after RowDataBound that's applying the alternating row style css class to the row.

try this in your RowDataBound event handler...
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
GridView grid = GridView1;
GridViewRow row = e.Row;
if (row.RowType == DataControlRowType.DataRow)
{
bool isAlternating = row.RowState == DataControlRowState.Alternate;
List<string> classes = new List<string>();
/* Setting the CssClass of a row overwrites any CssClass declared in the
* markup, so lets save the value that is in the markup and add to it.
*/
if (isAlternating) { classes.Add(grid.AlternatingRowStyle.CssClass); }
else { classes.Add(grid.RowStyle.CssClass); }
//
//logic for adding other css classes to the row...
//
//set the CssClass property of the row to the combined css classes value
row.CssClass = string.Join(" ", classes.ToArray());
//
//more row processing...
//
}
}

Another work around you may want to try is to perform your checks and CSS Class manipulation after all the rows are databound by using one of the later gridview events and looping through the rows yourself. I am aware that this would add some extra processing time, but depending on the size of the datasets your are displaying, it could be worth it.

Gridview rowdatabound allows you ONLY to manipulate the HTML within a TR so you probably do not have an option to do what you want to do with gridView. Instead i would recommend to use Repeater which gives you much more control over the HTML you generate. here is what you can do with the repeater
<table>
<thead>
<tr><td>Head1</td><td>Head2</td></tr>
</thead>
<asp:Repeater ID="rptTaskItems" runat="server">
<ItemTemplate>
<tr><td>column 1 data</td>
<td>column 1 data</td>
</tr>
<asp:PlaceHolder ID="PHBar" runat="server" visible="false">
<tr><td colspan="2"><hr/></td>
</tr>
</asp:PlaceHolder>
</ItemTemplate>
</asp:Repeater>
</table>
In the ItemDataBound, you can make this placeholder [PHBar] visible ONLY when your condition is true and thats it.
Hope this helps.

Related

Computed variable for every repeater row shows up in the next row

I have a protected variable and used in a repeater itemtemplate, <%# Bonus %> which is calculated in the repeater's OnItemDataBound event based on other columns' values in the same row.
The problem is that the Bonus value for a row is showing in the next row. So for row #1, the value is blank. The second row shows the bonus for row #1, third row shows bonus value for second row and so on. The bound values are showing fine. It's the computed value showing in the wrong row.
It seems in the OnItemDataBound event the computed value is calculated but for some reason, the value is not used in the rendering of the current row but in the next row.
What am I doing wrong?
Updated
Simplified sample code:
.......
protected Decimal Bonus;
.....
(then inside repeater OnItemDataBound handler)
.....
DataRowView row = (DataRowView) e.Item.DataItem;
Bonus = Convert.ToInt32(row["salary"]) * .01;
in ASPx:
.....
<asp:Repeater runat="server">
<ItemTemplate>
<td><%# Eval("salary") %> <== will show correct value
</td>
<td><#% Bonus %> <== doesn't show the bonus computed for that row from OnItemDataBound handler. Shows up in the next repeater row.
</td>
<ItemTemplate>
OnItemDataBound occurs after the row is bound.
All the values of the current row are present in the parameters to the function.
If you need to set a new value, since the repeater is not going to bind the values again after this function, you should set the text of the control yourself.
void myRepeater_ItemDataBound(Object Sender, RepeaterItemEventArgs e) {
// This event is raised for the header, the footer, separators, and items.
// Execute the following logic for Items and Alternating Items.
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem) {
if (((Evaluation)e.Item.DataItem).Rating == "Good") {
((Label)e.Item.FindControl("BonusLabel")).Text= Bonus.ToString();
}
}
}
You can bind to a method that performs the calculation:
code-behind
public string CalculateBonus(object salaryValue)
{
int salary;
string bonus;
if (Int32.TryParse(salaryValue.ToString(), out salary))
{
bonus = (salary * .01).ToString();
}
else
{
bonus = "N/A"; //or whatever default value you want to return
}
return bonus;
}
aspx page
<td><%# CalculateBonus(Eval("Salary")) %></td>

How to avoid repetition of data in Gridview?

In a web application I am binding the data to a GridView. In the GridView some of data is repeating. I want to not display the data again and again.
For example Empid is displaying more than one time in the same column. I want to not display the empid again in that column.
You can implement the OnDataBinding event for the specific column you are using. I never use AutoGenerateColumns so having fine control of each cell is pretty simple to implement.
Eg:
// Create global in your .cs file
string _currentEmpID = string.Empty;
Define your column like:
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:Literal ID="ltEmpID" runat="server"
OnDataBinding="ltEmpID_DataBinding" />
</ItemTemplate>
</asp:TemplateField>
<!-- Your other columns... -->
</Columns>
Then just implement your DataBinding event:
protected void ltEmpID_DataBinding(object sender, System.EventArgs e)
{
Literal lt = (Literal)(sender);
string empID = Eval("EmpID").ToString();
if (!empID.Equals(_currentEmpID))
{
lt.Text = empID;
_currentEmpID = empID;
}
else
{
lt.Text = string.Empty;
}
}
The RowDataBound forces you to search for controls and if changes are required in the future you have the possibility of breaking other things being modified within the event. Because of this, I prefer to use the control's DataBinding event whenever possible as it localizes functionality to only the control and gives you the flexability to swap out controls and functionality easily without the worry off affecting other things.
If you group your data by the columns you don't want to repeat before binding it to your datasource you can bind an event to RowDataBound and check if the current value equals the previous and then hide the cell.
Check this for an example.
Just add the property AutoGenerateColumns in the gridview and assign it the value of false.
AutoGenerateColumns="false"

how to have listview row colour to change based on data in the row

I followed this question stuck on changing each row color during run-time in listview in asp.net based on database entries and tried to do the same in VB but i am getting some unexplained errors, like Object reference not set to an instance of an object
most likely for this row =>
Dim cell As HtmlTableRow = DirectCast(e.Item.FindControl("MainTableRow"), mlTableRow)
Please let me know if there is any better way / correct way to do this in VB?
Protected Sub ListView2_ItemDataBound1(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.ListViewItemEventArgs) _
Handles ListView2.ItemDataBound
If e.Item.ItemType = ListViewItemType.DataItem Then
Dim dataitem As ListViewDataItem = DirectCast(e.Item, ListViewDataItem)
Dim mstorename As String = DataBinder.Eval(dataitem.DataItem, "Store")
If mstorename = "A1" Then
Dim cell As HtmlTableRow = DirectCast(e.Item.FindControl("MainTableRow"), mlTableRow)
cell.BgColor = #E0E0E0
End If
End If
End Sub
Many thanks for your help.
dk
For this to work, you must ensure that you provide MainTableRow id to tr element and mark it as runat="server" i.e. make sure that your mark-up (html) is something like
<ItemTemplate>
<tr id="MainTableRow" runat="server">
...
A different (and IMO, simpler) approach will be using data-binding expressions. For example, in your markup, use
<ItemTemplate>
<tr class='<%# GetRowStyle(Container.DataItem) #>'>
And in code-behind, have a protected function to provide CSS class based on data (a example c# function will be)
protected string GetRowStyle(object item)
{
var store = DataBinder.Eval(item, "Store");
if (store == "A1")
{
return "altRow";
}
else
{
return "row";
}
}
And lastly, define those css classes (row, altRow) as per your needs.
and without any code behind at all.
I just added another field to the SQL called status
for example
select given, surname, case when owing > 1000 then 'Behind' else 'OK' end as Status from cust
then in the page
<ItemTemplate>
<tr class='<%# Eval("Status") %>' style="">
and
<style type="text/css">
.behind
{
font-style :italic ;
color: black ;
}
.ok
{
color: grey ;
}
</style>
I know this is old but if anyone looking for inline without css (like I was) here is my solution:
The db column 'Priority' contains 0,1,2, etc.. and i want to color my list rows red,blue,green according to those :
<ItemTemplate>
<div style='<%# color:" + mylistof_PRIORITYCOLORS[Convert.ToInt16(Eval("Priority"))] %>'>
and your list defined
public static List<string> mylistof_PRIORITYCOLORS = new List<string> { "Red", "Blue", "Green" };

Adding <tr> from repeater's ItemDataBound Event

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 %>

asp:repeater - headers at section change

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 .

Resources