This is the first article in a series focusing on the GridView and demonstrating some of its usages via a simple shop demo. Each article is going to use the same source files, which you can grab at the link shown above. To view the demo just unzip everything into a new directory on your web server and browse to the directory name. For example, if you unzipped it all into a directory called "gridviewshop" in the root of your web server you would navigate to:
If you've already implemented your system using DataGrids, including your own custom paging and sorting solutions, then you really don't need to consider updating to the GridView because, end functionality-wise, they both produce the same thing (i.e an HTML table). If however you're starting a new system, then you should go with the GridView, especially if you want to take advantage of its built-in paging and sorting abilities.
You can control several aspects of the GridView, from how it looks to how it functions, by setting various properties at design time. Later in this article series we will look into these areas more deeply by assigning some CSS classes to the table rows and table column headers, as well as adding some event handlers to allow user interactions with each row.
Populating the GridView is identical to populating a DataGrid. You create your DataSource and then bind it to the GridView with:
With .net 2 you have another option, which is to create a SqlDataSource and bind the GridView directly to that by setting its DataSourceID to match the ID you assign to the SqlDataSource, i.e:
<!-- Create the GridView and assign its DataSourceID to match the above i.e. mySqlDataSource -->
<asp:GridView
id="myGridView"
runat="server"
autogeneratecolumns="true"
DataSourceID="mySqlDataSource"/>
Personally I'm not that keen on this method, although it is "the recommended" method by Microsoft to set up your GridView. I prefer to have more control over my DataSource so I can manually filter its contents and more, which is why we won't be using this method in the demo shop.
So let's dive into constructing the demonstration shop. The outline for the shop is that there will be two GridViews on a single page; you can see this in the introduction image. One GridView will be used to display the products in our shop and the other will be the shopping basket. You could easily split these two up onto their own pages, but for the sake of simplicity here we'll keep them together.
If you open default.aspx which is included in the zip file that accompanies this article, you can see how the page has been set up. Most of the HTML is just pretty wrapping; the bits to pay attention to are the page declaration at the top, the main <form> tag and the pair of <GridView> tags inside it.
The Page Declaration
<%@ page inherits="shop.site" src="cs/site.aspx.cs" %>
The page declaration simply tells our page what namespace and class it belongs to; in this case our namespace is "shop" and our class is "site". There is an additional property defined called "src" which points to the plain text .cs file that contains the site class.
I normally have my classes contained in external .cs files as opposed to manually compiling them to .dll files during development. I do most of my coding with a simple text editor (Crimson Editor), so compiling with the command line after each change would be pretty time consuming. Having the files as external .cs files means that each time the page is viewed after a change has been made, it will automatically get recompiled into a new cached dll by the server. If I was using Visual Studio or one of the free Express IDEs, I'd use pre-compiled dlls during development, because it would then be a simple case of hitting Build to generate them. I do build the classes into pre-compiled dlls once I have finished working on them, but during development I prefer to spend my time coding, not compiling. I could write a batch file to do the command line compilation via a simple double click, but I'm extremely lazy and never got into the habit of doing this.
Binding Data
<asp:GridView
id="gvBasket"
AutoGenerateColumns="false"
ShowHeader="True"
ShowFooter="True"
DataKeyNames="id"
OnRowDataBound="gvBasket_RowDataBound"
runat="server">
<Columns>
<asp:ImageField DataImageurlField="thumb" alternatetext="Product Thumbnail" readonly="true" />
<asp:TemplateField HeaderText="Item">
<ItemTemplate>
<h3><asp:Literal id="litItemName" runat="server" /></h3>
</ItemTemplate>
<FooterTemplate>
<a href="delivery-costs.aspx" title="View the list of delivery charges">Delivery Charges</a>
<br /><hr />
<b>Total</b>
</FooterTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
If you take a closer look at both of the GridViews you will notice that they both have AutoGenerateColumns set to false. Without this line, or if it was set to true, our columns would be created for us when we bind the DataSource. By turning this feature off we can define our own columns using the "Columns" sub tag. Inside that we can create a number of different type columns. In this demonstration we have used the ImageField and TemplateField column types. The ImageField column takes an image path as its value via the DataImageUrlField property and then displays that image inside its own column when rendered to the page.
The TemplateField is the real gem column. It allows you to define a HeaderTemplate, an ItemTemplate and a FooterTemplate among others. These three tags let you put any content you want into these areas. The HeaderTemplate and FooterTemplate both refer to that column's header and footer, and the ItemTemplate refers to the body content that is displayed in that column for each row.
If you look at the basket GridView you will see that we've used the ItemTemplate to show the name, price and quantity of each item in the basket, and then in the FooterTemplate we are displaying both the delivery charges and the totals. The above snippet just shows the "name" column and its footer; to see the full implementation look in the default.aspx source file. Because the headers for each column are just going to be static text we skipped using the HeaderTemplate and instead opted for the HeaderText property of the TemplateField. To show the header and footer of a GridView you must set the ShowHeader and ShowFooter properties to True on the GridView.
Another reason the ItemTemplate is great to use is that you can put other HTML and .net tags inside it. In the demonstration there are several different types including <input>, <br />, <hr />, <asp:Literal> and <a>. The only downside to putting these inside an ItemTemplate tag is that you have to do a little more work to pre-populate them, but luckily it isn't much. The first step is to set the RowDataBound event on the GridView. You assign a function to this that will get called each time a row is created after we bind our DataSource. You can see this in the basket GridView properties:
OnRowDataBound="gvBasket_RowDataBound"
The equivalent function for the product GridView is simpler but it only shows how to populate the ItemTemplate and not the Header or Footer templates.
An Important Function
Now is a good time to open the main class file titled "site.aspx.cs" inside the "cs" folder and locate the function called "gvBasket_RowDataBound." Here's the function on its own, although you should check out the rest of the file in your own time:
protected void gvBasket_RowDataBound(object sender, GridViewRowEventArgs e)
{
switch( e.Row.RowType )
{
case DataControlRowType.DataRow:
// Name/Desc
((Literal)e.Row.FindControl("litItemName")).Text = Convert.ToString(((DataRowView)e.Row.DataItem)["name"]);
// Quantity
string quantity = Convert.ToString(((DataRowView)e.Row.DataItem)["quantity"]);
((HtmlInputText)e.Row.FindControl("itProductQuantity")).Value = quantity;
// Price
((Literal)e.Row.FindControl("litPrice")).Text = String.Format("{0:C2}", Convert.ToDouble(((DataRowView)e.Row.DataItem)["price"]) * Convert.ToInt32(quantity));
break;
case DataControlRowType.Footer:
DataTable dtShop = getBasketDt();
double total = 0.00;
for(int i = 0; i < dtShop.Rows.Count; i++)
{
total += Convert.ToInt32(dtShop.Rows[i]["quantity"]) * Convert.ToDouble(dtShop.Rows[i]["price"]);
}
((Literal)e.Row.FindControl("litTotalQuantity")).Text = Convert.ToString(dtShop.Compute("SUM(quantity)", ""));
((Literal)e.Row.FindControl("litDeliveryPrice")).Text = String.Format("{0:C2}", Convert.ToDouble(calcDeliveryCost(total)));
((Literal)e.Row.FindControl("litTotalPrice")).Text = String.Format("{0:C2}", Convert.ToDouble(calcDeliveryCost(total)) + total);
break;
}
}
The first thing we do is perform a switch on the RowType property so we can tell whether we are populating a Header, Footer or Item Template as all three will be separate calls to this same function. For both the product and basket we catch the DataControlRowType.DataRow row type as this is our ItemTemplate.
Because we gave all our controls unique IDs on the HTML page we can use the row's FindControl function. This will return an "Object" if any of the controls inside the row have a matching ID. We cast that to the type of object we're expecting, for example a "Literal" or a "HtmlInputText" field, and then assign the data to it via its TextorValue property. Each time a row is bound it gets passed into the function via the GridViewRowEventArgs.Row property. Using that we can access the row's DataItem, which contains all the data for that row from the DataSource. It's then up to us to decide which data we want out of that and how we shall use it.
In the basket we pull the name, quantity and price columns out of the DataItem and assign them to the relevant controls we embedded within the ItemTemplate. It's much the same for the DataControlRowType.Footer except that we pull a copy of the DataSource out of Session state ( getBasketDt(); ) since we want to generate the total cost and delivery cost using information from all of the rows, and not just the single row that got passed into the function.
Conclusion
Hopefully this article has shown you the basics of using a GridView as well as a few other tips and tricks along the way. We looked at one method of implementing the GridView and how we can have complete control over how its contents are generated. The next installment in this series will look into where the data comes from and take you through setting up the actual product/basket page.