|
Introduction
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":
- Insert new rows.
- Edit via
DropDownLists.
- Validation.
- Delete confirmation.
- Checkbox input.
- Current row highlighting.
- Selective cell highlighting based on value.
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.
Background
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 ;-)
Using the code
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.
Steps to create the project manually
- Create a blank project.
- Create an Images folder.
- Right click on Images.
- Choose "Add Existing Item".
- Go to the folder you unzipped the download to, and select all the .GIF files and click "Add".
- In the design view, add a
DataSource.
- Configure the
DataSource to select each individual field.
- Create a
GridView and select the previously created DataSource.
- Click the
GridView SmartTag to open up its Tasklist.
- Enable Paging, Sorting, Editing and Deleting.
- Select Edit Columns.
- In the lower right pane, the top item is
CommandField.
- Click it, and the top right pane shows the attributes.
- Change Appearance > Button Type to
Image.
- Set
CancelButtonUrl to Images/Cancel.gif.
- Set the rest of the
ButtonUrls appropriately.
- Select ProductID from the lower left pane.
- Press the red X to the right to remove it as it is not an editable field.
- Build and run the project.
You now have a working editable/delete-able table maintenance program.
Editing using DropDownLists
- Create a new
SqlDataSouce (in the Toolbox under Data).
- Configure this
DataSource to connect to the Suppliers table, and in the SELECT statement, choose SupplierID and CompanyName.
- Create a new
SqlDataSouce (you know where it is).
- Configure this
DataSource to connect the the Categories table, and in the SELECT statement, choose CategoryID and CategoryName.
- Click on the
GridView SmartTag to bring up the Tasklist.
- Click "Edit Columns...".
- Highlight SupplierID (in the lower left pane), then click on the phrase "Convert this field into a template" (over to the right).
- Do the same for CategoryID.
- Click on OK.
- You should now be looking at the
GridView Tasklist.
- Click Edit Templates at the bottom.
- Click the down arrow and you will see a list of templates, choose (under Column [2] - SupplierID) "
EditItemTemplate".
- Do not choose "
ItemTemplate".
- Select the text box and delete it.
- From the toolbox, drag a
DropDownList control and place it where the textbox was.
- Click its SmartTag and click "Choose data source".
- Choose the Supplier
DataSource that you set up earlier.
- Bind the display field to
CompanyName, and the value to SupplierID.
- Click the
GridViews SmartTag.
- Now, pick "
EditItemTemplate", but this time, under Column [3] - CategoryID.
- Repeat what you have just done to connect up the
CategoryID.
- Now, build and run the application.
- You will see in the
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).
- Click Cancel (because we're not ready to do an Update yet).
- Close the application.
- Now, we're going to turn those irritating Supplier/Category IDs into more user friendly names.
- You need to modify the first
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".
- Click Next once, and then change the retrieval type to Custom SQL Statement.
- Click Next and make sure Select is the highlighted tab.
- Click Query Builder.
- Right click in the pane showing the Products table and choose Add table.
- Add the following tables: Suppliers, Categories, then close.
- Add CompanyName from Suppliers and CategoryName from Categories to the query result.
- You're now done with the
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!
- Click Edit Templates in the Tasklist.
- Choose the ItemTemplate for Column [2] - SupplierID.
- Click the SmartTag for the
Label control and choose Edit Databindings.
- Select Text in the left pane and change the Bound to column to
CompanyName.
Important: Make sure that the two-way binding is still checked after you do this.
- Close the box.
- Click the SmartTag for GridView1 - Column [2] - SupplierID.
- Choose the
ItemTemplate for Column [3] - CategoryID.
- Click the SmartTag for the
Label control and choose Edit Databindings.
- Select Text in the left pane and change the Bound to column to
CategoryName.
Important: Make sure that the two-way binding is still checked after you do this.
- Close the box.
- Click the SmartTag for
GridView1 - Column [3] - CategoryID.
- Click End Template Editing.
- Build and execute the application.
Now, you will see that CategoryID and SupplierID numbers have been replaced by names.
- Click around and edit the various lines but don't save - this is the real live NorthWind database that other tutorials depend on. We'll get to adding next, so you can add your own rows and mangle/delete them without injuring the real data.
Follow these steps to make the GridView - Add New Records
- Click the Product DataSource SmartTag.
- Click Configure data source.
- Click Next twice.
- Make sure Select is the highlighted tab.
- Click Query Builder.
- Change the existing statement:
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
- Insert the following JavaScript code into the
<head> of the ASPX file: <script>
function FixGrid(idGrid, PageIndex, EditIndex)
{
var Start = 1;
if(EditIndex != 0)
{
if(PageIndex == 0)
{
Start = 2;
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);
}
}
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();
if(onc.indexOf("Delete$") == -1)
continue;
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;
ctl.outerHTML = ctl.outerHTML;
}
}
catch(e)
{
break;
}
}
}
</script>
- Put this script after the closing
</form> tag: <script>
FixGrid(document.all.GridView1,
<%=GridView1.PageIndex.ToString()%>,
<%=EditIndex %>);
</script>
- Right click the
GridView and select Properties.
- Click the Event button.
- Double click Action > RowUpdating.
- Double click Action > RowEditing.
- This will create the empty event handlers and the required event hookup code.
- Now, replace the code in the empty ASPX.CS file with:
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;
System.Web.UI.WebControls.SqlDataSource ds =
(System.Web.UI.WebControls.SqlDataSource)
this.FindControl(this.GridView1.DataSourceID);
System.Data.SqlClient.SqlConnection conn =
new System.Data.SqlClient.SqlConnection(ds.ConnectionString);
conn.Open();
string s = ds.InsertCommand;
System.Data.SqlClient.SqlCommand c =
new System.Data.SqlClient.SqlCommand(s, conn);
System.Data.SqlClient.SqlParameter p;
foreach (System.Collections.DictionaryEntry x in e.NewValues)
{
p = new System.Data.SqlClient.SqlParameter("@" + x.Key, x.Value);
c.Parameters.Add(p);
}
c.ExecuteNonQuery();
}
protected void GridView1_RowEditing(object sender,
GridViewEditEventArgs e)
{
EditIndex = e.NewEditIndex;
}
}
- Now the hack:
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.
Checkboxes
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.
Sorting
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.
Other business
Data presentation
There are two ways you can format data.
To statically format the data
- Choose Edit Column from the
GridView's SmartTag.
- Highlight the column you want to format in the lower left pane. Scroll the right hand pane to the bottom.
- Expand
ItemStyle and make your changes.
- In this sample program, I have set each item's
VerticalAlign to top, you'll understand why in the section on validation.
- I also set the
HorizontalAlign to right for the numeric columns.
- If the item you have chosen is also an
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.
To format the data at runtime
You must create a RowDataBound event handler. To do this:
- Right click the
GridView and select Properties.
- Click the lightning bolt at the top of the Properties window to access the events sheet.
- Double click in the input area to the right of
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)
{
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;
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.
Validation
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.
Styles
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.
Images
If anyone has better images that I can use for this example, please send them to me or send the URLs. These ones suck.
Conclusions
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!
Errata
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.
External links
If you have any other suggestions, please send them along.
SmashGrab / Redux series
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.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 29 (Total in Forum: 29) (Refresh) | FirstPrevNext |
|
 |
|
|
First of all I want to thank you for this great job.
I have followed your instructions and I have built a sample to understant what the grid does, but I get the following error :
>>>>> Microsoft JScript runtime error: 'firstChild' is null or not an object
in the following line of the FixGrid function:
>>>>> if(idGrid.firstChild.childNodes[1].childNodes[0].childNodes[0].type=="button")
I have tried to find what I am doing wrong but I can't find it. What could it be?
Thank you, Xavier.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I have a dropdownlist filled with subject name. Now I will select a sunject from that list and also I have a textbox where I will enter a number. Now I want that subject and the number to be placed in a gridview in columns subject, position.How will I do this in asp.net where in I am using C# coding. If anybody can suggest me! Thank you.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I wish more Code Project contributors would adopt your "step by step" approach instead of just talking through the code they're already quoting. Your article really saved me a lot of time as I transition from the old datagrid to the much improved gridview. 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Jeff mentioned about onclick event. I am able to change the background color for onclick event. But when another row is clicked the previous row should return to the original color. How can it be done?
Thank you, J
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
This was great. I don't know CS and I could follow and duplicate almost everythin you've done.
I did get lost on one part: Right click the GridView and select Properties. Click the Event button. Double click Action > RowUpdating.
I could not find an event button anywhere.
OverAll, It was an awsome article and I am implementing it immediately. I am now working on your Parent/child article and look forward to it as well.
Thank you so muck
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
I found that in order to use this great GridView Redux, it must have a dummy record in the database that the gridview is pointing to. Is there a way to eliminate this ?
Thanks in advance
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I like the example but I'm new to .Net and I'm trying to convert this to be able to use in my vb.Net pages.
I'm having trouble with this line:
System.Web.UI.WebControls.SqlDataSource ds = (System.Web.UI.WebControls.SqlDataSource) this.FindControl(this.GridView1.DataSourceID);
I have this so far:
Dim ds As System.Web.UI.WebControls.SqlDataSource = (System.Web.UI.WebControls.SqlDataSource) Me.FindControl(Me.GridView1.DataSourceID)
But I get a compiler error at "Me.FindControl...DatasourceID)". It says "End of statement Expected"
Thanks for Any help
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I am not a vb person, but try something like: Dim ds As System.Web.UI.WebControls.SqlDataSource = CType(Me.FindControl(Me.GridView1.DataSourceID), System.Web.UI.WebControls.SqlDataSource)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hi, It's very good. But, how will be the situation, if we use sorting and paging for the same grid? If we sort the grid by a column, the empty row at the first line which we use for adding the new one will be populated with some other record.How can we overcome that??
-Priya
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, First of all Thanks a lot for all the useful info. in this article. I developed a simple GridView using tips from this article. Everything seems to be working fine, except, I do not get "Delete Confirm" window working and "Add row" image does not replace "Edit" image. While debugging it goes to the lines where it accesses the FixGrid function , but I think it actually does not execute the function. One possible reason could be I do not have <head> </head> tags in my aspx page, I am using master-page and so have <asp:contect> and Content holder tag. andI am placing <script> after tag. Does that matter ? If not, then can you please tell me why the function is not working ? thanks in advance.
Tejashree Bambulkar Principal Consultant, T-Expert, LLC tejashree@texpert.us
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
1. You should bind the combo boxes to the SelectedValue instead of SelectedIndex.
2. If you run on a "server" that uses a different decimal separator you will have to change to it in the Unit Price RangeValidator (RangeValidator1).
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Gary,
great article. Unfortunatley, I get the following build error:
The multi-part identifier "Products.ProductName" could not be bound. ORDER BY items must appear in the select list if the statement contains a UNION, INTERSECT or EXCEPT operator.
Any ideas on how I can get this working.
Thanks.
Regards
Walter
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I had the same problem and got it working by...
1) In Design view click on the Smart Tag and Configure Data Source. 2) Next through to the "Define custom statement..." tab and click Query Builder. 3) The Execute Query button should work, so the Select statement seems to be right. 4) Highlight the Select statement, copy it, click Ok and paste it over the existing Select statement in the SQL Statement box 5) Click Next and Test Query and for me it started working without errors. I was then able to build the page.
Hope it works the same for you too.
|
| Sign In·View Thread·PermaLink | 3.50/5 (2 votes) |
|
|
|
 |
|
|
Thanks. It works, the Gridview now appears. I'm getting an error though, concerning currency, when i try to edit a row - will stard a new thread.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
It's hard to freeze the headers, while letting the detail rows scroll. I'd love to see any links about this, or ideas people have.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hi, you speak of Validation in your article. But I can't find it . Can you tell me where I must look. I'm new with asp.net .
TIA, Antonio
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Ok, I'll be kind as the author asks:
I really like this sample code, it does save a TON of research that I would have otherwise have to do myself.
That said, there are a number bugs in the code. (I will send the author my updates when I get things ironed out.)
For now, to answer your question, validation happens when you try to update or add a row. One bug to be fixed is that if a valid price is entered, eg 12.34 validation fails because it does not like decimals. Probably doesn't like $ signs either.
More as it happens...
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
I just wish I had found this earlier!
I AM however having a slight issue (and it is probably an obvious fix that I am not finding so obvious).
Your script tags for calling FixGrid() are understandably outside of the FORM, but I am using a master page. With this scenario the form wraps around the whole child page (which has my gridview). Where is the best place for me to add the javascript call?
Adding it to the master page - even with like this: <script> if (isObject(gvMyGridview)) { FixGrid(...) } still causes an error because it does not find the gridview object in the line with the FixGrid call...
___Added Later___
Also tried using Me.ClientScript.RegisterStartupScript - this gets the script into the page but it errors out on the call. (it is still being placed before the </FORM> tag
Working on some other ways to get this done... but if I am missing something obvious (or not so obvious) I'd appreciate input.
Thanks!
-- modified at 15:56 Tuesday 21st March, 2006
|
| Sign In·View Thread·PermaLink | 3.50/5 (2 votes) |
|
|
|
 |
|
|
Hi
First of all thank you for this wonderful start,
I am just trying to add few other functionalities, to this like,
1. Check Boxes in all rows to facilitate select and confirm, delete the rows Updating the same. 2. Multi row editing on loading a particular page of records or set of records
Please throw some light on these, on how to start with, or does this two items, makes the approach different from what you have chosen?
Thank you in advance
|
| Sign In·View Thread·PermaLink | 4.00/5 (1 vote) |
|
|
|
 |
| | |