Try it online
Introduction
When using Visual Studio .NET to create ASP.NET pages, the Property Builder is a great tool for quickly customizing the functionality or coloring of the ASP.NET DataGrid
control on a single page. However, I've sorely missed the ability to add CSS class names to the different element types (Header, Footer, Item, AlternatingItem, etc.) programmatically, so I could alter the look of all DataGrid
s in my solution's pages from a single CSS file. I've looked around on the web for a solution, but since I didn't find any - I've created my own. This little utility class also has a few other tricks up its sleeve.
DataGridHelper
can:
- add CSS class names to all rows, cells and buttons in a
DataGrid
. E.g. "Item_row", "Header_cell", "Button_Update", etc. (one line of code).
- add attributes to all rows and/or cells in a
DataGrid
(one line of code).
- add a JavaScript confirmation dialog to the buttons in a column of a
DataGrid
(one line of code).
V. 1.2 - IHttpModule implementation
For those of you who want to add CSS classes to your DataGrid
s without even a single line of code, the DataGridHelper
class now implements the IHttpModule
interface. This makes it possible to have all DataGrid
s automatically get the CSS classes added in your entire solution (by editing the web.config file) or in all solutions on the server (by editing the machine.config file).
See how to do this [here].
Using the code
I've created a little web project to show you how to use this utility class. Most of the methods are pretty straightforward, but adding CSS to the DataGrid
has a little catch: you have to call this method before databinding the DataGrid
.
The reason for this is that I haven't been able to find a way of getting to the Header, Footer, Pager, etc. rows in the DataGrid
in any other way than by attaching to the ItemDataBound
event of the DataGrid
.
I tried enumerating through the DataGrid.Items
, but that only gives access to the Item
, AlternatingItem
, SelectedItem
and EditItem
items.
Here are the core methods:
public static void AddCssToDataGrid(DataGrid aGrid);
public static void AddAttributesToCells(DataGrid theGrid,
string attributeKey, string attributeValue);
public static void AddAttributesToRows(DataGrid theGrid,
string attributeKey, string attributeValue);
public static void AddConfirm(DataGrid theGrid,
int columnNumber, string question);
The sample project I've included follows the traditional pattern of many web projects:
Page_Load
- Check to see if we have data in Session.
- If not - load it from disk into session.
- Events
- If "Delete" was clicked - delete that row.
- If "Load data" was clicked - reload the data.
PreRender
- Databind and make last minute modifications.
For those of you unfamiliar with this approach, it will help you to know that ASP.NET pages are always processed in the same order.
Page_Load
goes first, which means that this is a good place to make sure that data is loaded.
- All events are processed next, this includes all postback events (button clicks, etc.) and is the time to delete, edit and add stuff.
PreRender
runs last, and this is the time to present the data in its current state. In other words "databind".
To put this in another way: "Load data" - "Edit data" - "Show data".
The entire web project is not much of an application as such. It was solely created to display the DataGridHelper
class' functionality.
Things to note:
- I have an external CSS file "sample.css" with the styles "Item_cell", "AlternatingItem_cell", etc. This automatically colorizes the
DataGrid
after the CSS classes have been added to the cells and rows.
- I have an external JavaScript file "scripts.js" with a little DHTML script that dynamically changes the CSS classes of HTML objects, so they present themselves differently visually, depending on their class.
- To avoid having to include a database in the sample project, I created a typed dataset, loaded the database table into it, and wrote it to disk using the
DataSet
's WriteXml()
method. This way I can (and do) reload the data into the DataSet
using the ReadXml()
method.
- I have a
DataGrid
named dgPersons
on the page. Using the Property Builder in Visual Studio, I have added a hidden first column (column 0) to this DataGrid
containing the primary key of the rows in the DataTable
. This way I can easily look up the corresponding row in the DataTable
when a user clicks the Delete button in a row.
The sample project's code
DataSet as a member variable
I have declared my typed dataset as a member level variable so I can access it from all methods:
protected dsPersons persons;
Page_Load loads data
All that happens when the page loads is that data is loaded.
private void Page_Load(object sender, System.EventArgs e)
{
LoadData();
}
LoadData() method
Here is the LoadData
method that is called from Page_Load
:
public void LoadData()
{
if(Session["persons"] == null)
{
string path = Server.MapPath("persons.xml");
persons = new dsPersons();
persons.ReadXml(path);
Session["persons"] = persons;
}
persons = (dsPersons) Session["persons"];
}
...at this point we are sure to have a loaded dataset in the Session, so if this page view was caused by a click on a Delete button, then we can go ahead and mark that row as deleted in the DataTable
:
Delete event finds and removes a person:
private void dgPersons_DeleteCommand(object source,
System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
int id = int.Parse(e.Item.Cells[0].Text);
persons.person.FindByid(id).Delete();
}
If the user didn't click "Delete", but "Reload data", then this event would also occur at this time in the page's lifecycle, and we could load the data anew:
Reload data from file:
private void btnReloadData_Click(object sender, System.EventArgs e)
{
Session["persons"] = null;
LoadData();
}
In any case - the PreRender
event happens last, and this is the place where my utility class gets to work:
PreRender displays the data:
private void WebForm1_PreRender(object sender, System.EventArgs e)
{
if (chkAddCSS.Checked)
DataGridHelper.AddCssToDataGrid(dgPersons);
dgPersons.DataSource = persons.person;
dgPersons.DataBind();
if(chkAddDeleteConfirmation.Checked)
{
DataGridHelper.AddConfirm(dgPersons, 3,
"Do you wish to delete this person?");
}
if(chkAddJavascriptMouseovers.Checked)
{
DataGridHelper.AddAttributesToCells(dgPersons,
"onmouseover", "changeStyle(event);");
DataGridHelper.AddAttributesToCells(dgPersons,
"onmouseout", "changeStyle(event);");
}
DataGridHelper.AddAttributesToCells(dgPersons,"width", "33%");
}
Points of Interest
The JavaScript
You can use any of your own or other people's JavaScript code along with the DataGridHelper
class. I've just included one of my more interesting JavaScript codes here, in case you could use the same functionality at some point in life. I switch the CSS class name of the cells in the table when the mouse moves over them.
This is done instead of the altering color, size, etc. directly. By using this approach you are able to revert to the original state of the cell when your mouse leaves it, because I only append and remove the suffix "_hover" to the class name.
Because we have two alternating rowtypes "Item" and "AlternatingItem", we can't just reset the rows' or cells' background color to a predefined color, as this would make the rows appear alike after a mouseout.
Have a look and see if you like it.
Update: This is an improved version that uses cross-browser scripting to access the event model of either IE or FireFox.
<script language="javascript">
function changeStyle(evt)
{
var theEvent = evt || window.event;
var theEventSource = theEvent.target || theEvent.srcElement;
switch(theEvent.type)
{
case "mouseover" :
theEventSource.className += "_hover";
break;
case "mouseout" :
theEventSource.className =
theEventSource.className.replace("_hover", "");
}
}
</script>
This allows us to define CSS classes like this, that work with the cells of DataGrid
:
.Item_cell {
background-color: Cornflowerblue;}
.Item_cell_hover {
background-color: red;
font-weight:bold;
color: White;}
Advanced - IHttpModule interface implementation (version 1.2)
This implementation makes it possible for the DataGridHelper
class to attach CSS to all "outgoing" DataGrid
controls in a web project without you having to write code for each of them. If you just want to use the DataGridHelper
class and call the methods when needed, then just skip this last part. To make this work, you have to add the HttpModuleDataGridHelper.dll file to the BIN folder of your web application, and add the following lines to the </system.web>
part of your web.config:
web.config addition
<httpModules>
<add name="DataGridHelperModule"
type="AddCssToDataGridSample.DataGridHelper, HttpModuleDataGridHelper" />
</httpModules>
In case you don't want to manually edit your web.config, just run this tool in your web application's folder. It will update your web.config file for you, and download the DataGridHelper.dll file from the Internet to the BIN folder. The next article I submit to The Code Project will be about this installer-tool, and how it was made.
(If you want to know more about coding HttpModules, I suggest you read this article.)
Ending note
I thoroughly enjoy the CodeProject newsletter each week. I am an avid reader and I hope my contribution has given something back to you, the community:)
Before you rush to the keyboard to let me know that I can just add a CSS class to my DataGrid
and then define how the TD
code or TR
code should be styled using the ".datagrid td"
code, I just want to let you know that I know ; ).
I just think it is easier to adjust any client scripting, CSS or DHTML if all cells and rows have a CSS class.
Kind regards - Jake (Jakob Lund Krarup).
History
- 1.2
- Added implementation of the
IHttpModule
interface to the DataGridHelper
class. So in addition to working as before, you can now have the CSS classes added with no code : )
- 1.1
- As suggested by R.vd.B - I've changed the
AddCssToDataGrid
so it also adds CSS classes to the buttons in the DataGrid
.
- Scott (bisc0tti) noticed that when the CSS was added in FireFox, the delete confirmation didn't work. This hasn't got anything to do with the
DataGridHelper
class, but was a lack of cross-browser programming in the sample JavaScript I used.
- The script has been updated to work cross-browser now : ).
- Thanks Scott (and SirTrolle who made the connection to the CSS)! : )