![]() |
Web Development »
ASP.NET Controls »
General
Intermediate
GridView ReduxBy Gary DrydenHow to add/edit a GridView in style. |
C#, Windows, .NET 2.0, ASP.NET, ADO.NET, VS2005, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||
The ASP.NET 2.0 GridView is a wonderful beast that can allow you to create new database rows, edit in place, even provide DropDownLists and CheckBoxes to aid you in the entry process. It can even do data validation.
I have added the following additional functionality over and above what you get "out of the box":
DropDownLists.
None of the functionality that I will show you how to build is rocket science, and most of it is around the internet someplace (although I have never seen the "Insert new row before"). This is my humble attempt to consolidate it all in one article.
I was presented with a project to convert an Access single-user application into a Web-based multi-user SQL Server application. The Access application had about 10 tables that needed to be managed through an Add/Edit/Delete interface. This table maintenance was to be done by a single individual, so concurrency issues would not be a problem. I decided that with some tweaking, the GridView could perform what needed to be done. Out of the box, the GridView will not do row inserts, nor will it provide DropDownLists where the data to be edited is really an ID representing a row in another table. This example uses the NorthWind Products table to add and update data rows. It is a step-by-step guide to producing a web page which will accomplish this task. It is long and arduous, but this is what it takes. When you have completed this exercise, you should be ready to tackle your own tables with confidence. You may want to print out the steps and keep them handy so you don't have to be constantly switching screens (unless you have two monitors ;-)
Download the project, unzip it to somewhere on your hard disk, and then create a new web project from an existing folder. Build the project and test drive it so that you see what it does. Now, create a new ASPX file, Default2.aspx, and do it yourself manually.
DataSource.
DataSource to select each individual field.
GridView and select the previously created DataSource.
GridView SmartTag to open up its Tasklist.
CommandField.
Image.
CancelButtonUrl to Images/Cancel.gif.
ButtonUrls appropriately.
You now have a working editable/delete-able table maintenance program.
SqlDataSouce (in the Toolbox under Data).
DataSource to connect to the Suppliers table, and in the SELECT statement, choose SupplierID and CompanyName.
SqlDataSouce (you know where it is).
DataSource to connect the the Categories table, and in the SELECT statement, choose CategoryID and CategoryName.
GridView SmartTag to bring up the Tasklist.
GridView Tasklist.
EditItemTemplate".
ItemTemplate".
DropDownList control and place it where the textbox was.
DataSource that you set up earlier.
CompanyName, and the value to SupplierID.
GridViews SmartTag.
EditItemTemplate", but this time, under Column [3] - CategoryID.
CategoryID.
SupplierID and CategoryID columns, the Supplier/Category IDs (we'll fix that shortly), but when you click Edit, they magically transform into dropdown lists (well, it's not really magic, but it is pretty impressive).
DataSource you created to dig the supplier's name and the category's name out of their respective tables. You could do it the manly (stupid) way by editing the source code for the SELECT statement and doing the INNER JOINs yourself, or just click the DataSource's SmartTag and click "Configure data source".
DataSource. Now, click the GridView SmartTag. At some point, an alert box will ask you if you want to clear the work you have already done. Just say no!
Label control and choose Edit Databindings.
CompanyName.
Important: Make sure that the two-way binding is still checked after you do this.
ItemTemplate for Column [3] - CategoryID.
Label control and choose Edit Databindings.
CategoryName.
Important: Make sure that the two-way binding is still checked after you do this.
GridView1 - Column [3] - CategoryID.
Now, you will see that CategoryID and SupplierID numbers have been replaced by names.
SELECT Products.ProductID, Products.ProductName, Products.SupplierID,
Products.CategoryID, Products.QuantityPerUnit, Products.UnitPrice,
Products.UnitsInStock, Products.UnitsOnOrder, Products.ReorderLevel,
Products.Discontinued, Categories.CategoryName, Suppliers.CompanyName
FROM Products INNER JOIN Categories ON
Products.CategoryID = Categories.CategoryID
INNER JOIN Suppliers ON
Products.SupplierID = Suppliers.SupplierID
ORDER BY Products.ProductName
to be:
SELECT 0 as ProductID, '' as ProductName, 0 as SupplierID, 0 as
CategoryID, '' as QuantityPerUnit, 0.00 as UnitPrice,
0 as UnitsInStock, 0 as UnitsOnOrder, 0 ReorderLevel,
convert(bit, 0) as Discontinued,'' as CategoryName,
'' as CompanyName
UNION
SELECT Products.ProductID, Products.ProductName, Products.SupplierID,
Products.CategoryID, Products.QuantityPerUnit, Products.UnitPrice,
Products.UnitsInStock, Products.UnitsOnOrder, Products.ReorderLevel,
Products.Discontinued, Categories.CategoryName, Suppliers.CompanyName
FROM Products INNER JOIN Categories ON
Products.CategoryID = Categories.CategoryID INNER JOIN
Suppliers ON Products.SupplierID = Suppliers.SupplierID
ORDER BY Products.ProductName
<head> of the ASPX file: <script>
function FixGrid(idGrid, PageIndex, EditIndex)
{
var Start = 1;
if(EditIndex != 0)
{
if(PageIndex == 0)
{
Start = 2;
// see the actual source for explanation
// replace Edit image with Add image, remove Delete
idGrid.firstChild.childNodes[1].childNodes[0].childNodes[0].src=
"Images/Add.gif";
var i =
idGrid.firstChild.childNodes[1].
childNodes[0].innerHTML.indexOf(" ");
idGrid.firstChild.childNodes[1].childNodes[0].innerHTML =
idGrid.firstChild.childNodes[1].childNodes[0].innerHTML.slice(0, i);
}
}
// put delete confirmations in
for(var i=Start; ; i++)
{
try
{
var ctl=idGrid.firstChild.childNodes[i].childNodes[0].childNodes[2];
if(ctl.tagName == "INPUT")
{
var onc = ctl.onclick.toString();
// window.alert(onc);
// uncomment this to see what the onclick actually contains
if(onc.indexOf("Delete$") == -1)
continue; // don't want to add confirm to "update cancel"
var j = onc.indexOf("__do");
var k = onc.indexOf(")", j)+1;
onc = "if(confirm('Are you sure') == false)" +
" return(false); "+onc.slice(j, k);
ctl.onclick = onc;
// if you don't do this then the onclick will not work.
// it is probably related to how the onclick is actually
// defined (see window.alert above)
ctl.outerHTML = ctl.outerHTML;
}
}
catch(e)
{
// when we land here, we have run the table rows out
break;
}
}
}
</script>
</form> tag: <script>
FixGrid(document.all.GridView1,
<%=GridView1.PageIndex.ToString()%>,
<%=EditIndex %>);
</script>
GridView and select Properties.
public partial class YOUR_CLASSNAME_HERE : System.Web.UI.Page
{
public int EditIndex = -1;
protected void Page_Load(object sender, EventArgs e)
{
}
protected void GridView1_RowUpdating(object sender,
GridViewUpdateEventArgs e)
{
if (e.RowIndex > 0 || GridView1.PageIndex > 0)
return;
// only RowIndex 0 on Page 0 is the row we want to insert
// find the DataSource for the GridView control
System.Web.UI.WebControls.SqlDataSource ds =
(System.Web.UI.WebControls.SqlDataSource)
this.FindControl(this.GridView1.DataSourceID);
// Get the DataSource's connection string
System.Data.SqlClient.SqlConnection conn =
new System.Data.SqlClient.SqlConnection(ds.ConnectionString);
// open the connection
conn.Open();
// get the Insert command string
string s = ds.InsertCommand;
// create the new command
System.Data.SqlClient.SqlCommand c =
new System.Data.SqlClient.SqlCommand(s, conn);
System.Data.SqlClient.SqlParameter p;
// the NewValues collection contains
// the name value pairs that need to be inserted,
// make them parameters to the command
foreach (System.Collections.DictionaryEntry x in e.NewValues)
{
p = new System.Data.SqlClient.SqlParameter("@" + x.Key, x.Value);
c.Parameters.Add(p);
}
// execute the command
c.ExecuteNonQuery();
// the GridView framework will execute an update
// on a row where the identity column=0
// since none exists it will silently do nothing, but that is ok
}
protected void GridView1_RowEditing(object sender,
GridViewEditEventArgs e)
{
EditIndex = e.NewEditIndex;
}
}
The INSERT statement that is generated by VS2005 can not be executed as is. It is originally:
InsertCommand="INSERT INTO [Products] ([ProductName],
[SupplierID], [CategoryID], [QuantityPerUnit], [UnitPrice],
[UnitsInStock], [UnitsOnOrder], [ReorderLevel], [Discontinued])
VALUES (@ProductName, @SupplierID, @CategoryID, @QuantityPerUnit,
@UnitPrice, @UnitsInStock, @UnitsOnOrder, @ReorderLevel, @Discontinued)"
You need to change it to:
InsertCommand="INSERT INTO [Products] ([ProductName], [SupplierID],
[CategoryID], [QuantityPerUnit], [UnitPrice], [UnitsInStock],
[UnitsOnOrder], [ReorderLevel], [Discontinued])
VALUES (@ProductName, @SupplierID, @CategoryID,
@QuantityPerUnit, convert(money, @UnitPrice),
@UnitsInStock, @UnitsOnOrder, @ReorderLevel, @Discontinued)"
You can change it manually in the source code or through the IDE.
You pretty much get checkboxes for free. The GridView by default will bind a SQL column of type bit (and only bit) to a CheckBoxField (you'll get a runtime conversion exception if you don't). If you look at the SelectCommand that we modified above, I had to add convert(bit, 0) as Discontinued.
By default, the SortExpression for a field is the same as the field. In this case, we are displaying CompanyName and CategoryName in the SupplierID and SortExpression fields, so I have set the SortExpression for SupplierID to CompanyName and the SortExpression for CategoryID to CategoryName.
Look in the code behind file, and you will see how sort direction glyphs were added (code courtesy of Dino Esposito's New Grid in Town article). This code makes use of the OnRowCreated event, so make sure you hook it up in the GridView's Property Events sheet.
There are two ways you can format data.
GridView's SmartTag.
ItemStyle and make your changes.
VerticalAlign to top, you'll understand why in the section on validation.
HorizontalAlign to right for the numeric columns.
ItemTemplate, you can also make some formatting changes to its constituent controls by navigating to it as described above and making your required changes to the ItemTemplate not the EditItemTemplate. You must create a RowDataBound event handler. To do this:
GridView and select Properties.
RowDataBound to auto-generate an empty event handler. I then modified the empty template to look like this:
protected void GridView1_RowDataBound(object sender,
GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
if (e.Row.RowIndex != EditIndex)
{
// highlight the UnitsInStock cell if it is zero
int unitsInStock =
Convert.ToInt32(System.Web.UI.DataBinder.Eval(e.Row.DataItem,
"UnitsInStock"));
if (unitsInStock == 0)
e.Row.Cells[7].BackColor = System.Drawing.Color.Yellow;
// highlight the row that the mouse is over
// save the original attribute in a custom attribute
// so that you can restore it later
e.Row.Attributes.Add("onmouseover",
"this.originalcolor=this.style.backgroundColor;" +
" this.style.backgroundColor='Silver';");
e.Row.Attributes.Add("onmouseout",
"this.style.backgroundColor=this.originalcolor;");
}
}
}
The first part of the above code gets the value of the UnitsInStock field from the current row, and if the value is 0, change the cell's background to yellow. If you have seen other examples of RowDataBound functions, you may wonder why not just do Convert.ToInt32(e.Row.Cells[7].Text). Well here is the nasty secret that no-one wants to tell you about (or at least not document anywhere). Once you turn a BoundField into an ItemTemplate, you must access the original data, because at the time that RowDataBound is called, it is not yet in the cell for you to access. But if you think about it, this makes perfect sense!
At the RowDataBound time, the cell still contains the controls which make up the template, and there may be good reasons for you to still want to manipulate the constituent controls before rendering.
The second part shows you how to highlight the row that the mouse is over. The only trick here is to save the underlying background color (in the custom attribute originalcolor) before changing it so that it can be restored on the OnMouseOut event. I have seen other solutions that hard code the background color in the restore, but that technique does not handle alternating background colors.
I have added validation controls to each EditItemTemplate. Each control is validated as it is edited (i.e.: when it loses focus). If there is an error, the error message is displayed below the control. The reason why I formatted each control to be VerticalAlign = top was so that the line would look nicer when there were errors.
All elements, whether DataBound, ItemTemplates, or controls within an ItemTemplate, expose a CssClass element to which you can assign one or more classes to affect the look of the element. If you are specifying more than one class, separate them with a space. It is interesting that none of the GridView elements support inline styling via the CssStyle.Add(...) method that most other classes support. This means that you can not add individual inline style elements to a cell.
If anyone has better images that I can use for this example, please send them to me or send the URLs. These ones suck.
I have taken you through the long and complex VS2005 process of what should be the simple task of maintaining a table. While it is a great step forward, it could have gone a lot further. Come on Visual Studio team, we need a button in the GridView's SmartTag TaskList that will automate most of the process I have just described manually!
This has been a long step-by-step article, and I am sure that there are mistakes. Please be kind. Any corrections will be dutifully recognized and the article corrected.
If you have any other suggestions, please send them along.
I have recently started two series of articles here at CodeProject. Smash & Grab is intended as series of short articles on one specific code technique. Redux is intended as a series of longer articles which attempt to reduce a complicated topic (like GridView) into its basic constituent parts and show that once you have all the information, it isn't really that hard after all. To find the Smash&Grab articles, search for the keyword SmashGrab. To find the Redux articles, search for the keyword Redux. I welcome any contributions to either series, but please follow the guidelines when submitting articles to either.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 20 Feb 2006 Editor: Smitha Vijayan |
Copyright 2006 by Gary Dryden Everything else Copyright © CodeProject, 1999-2009 Web19 | Advertise on the Code Project |