Showing posts with label gridview. Show all posts
Showing posts with label gridview. Show all posts

Thursday, January 20, 2011

Render ASP Control To HTML String

Every now and then, you may come across the need to convert an ASP.NET control to html. For instance, if you were drafting an HTML email with your site, you may create the content of the email with .NET controls, then convert it to html to place into the body of the message.

I recently ran across the need to convert an asp hyperlink control to its html equivalent as part of a function used in creating a GridView.

My gridview contained an Eval and Function:
<div style="font-style: italic; font-size: 11px;">
Answered by <%#ShowName(Eval("faq_author"))%>
</div>


So, I attempted to use the function ShowName plant a control itself in the gridview:
Public Function ShowName(ByVal thisID As Integer) As HyperLink
Dim thisAuthor As New FaqAuthor(thisID)
Dim thisLink As New HyperLink()
thisLink.Text = thisAuthor.Name
thisLink.NavigateUrl = thisAuthor.ProfileURL
Return thisLink
End Function


Unfortunately, this doesn't work out like you'd hope:


But, there's an easily solution to change that System.Web.UI.WebControls.HyperLink into an actual html hyperlink. Using the IO.StringWriter class and HtmlTextWriter class, we can execute the .RenderControl function that can be found in nearly .NET control that renders into an HTML element.

My final ShowName function should look like this:
Public Function ShowName(ByVal thisID As Integer) As String
Dim thisAuthor As New FaqAuthor(thisID)
Dim thisLink As New HyperLink()
thisLink.Text = thisAuthor.Name
thisLink.NavigateUrl = thisAuthor.ProfileURL
Dim myStringWriter As New IO.StringWriter()
Dim myHtmlWriter As New HtmlTextWriter(myStringWriter)
thisLink.RenderControl(myHtmlWriter)
Return myStringWriter.ToString
End Function


And it works perfectly.
My gridview now shows hyperlink text that gives my users' name and links to their profiles.

Friday, August 27, 2010

Nested Gridview Within Another Gridview

I recently came across a project for which I had a need for a gridview within a gridview.
For this example, I will create a grids of documents and guides housed within a parent grid of resource categories.

Let's start with a basic gridview of 4 documents (2 training modules on gridviews and 2 user manuals for cell phones).

<asp:GridView
ID="gvDocuments"
DataSourceID="dataDocuments"
AutoGenerateColumns="false"
DataKeyNames="document_id"
CellPadding="8"
HeaderStyle-BackColor="#e0e0e0"
BorderColor="#e0e0e0"
runat="server">
<Columns>
<asp:HyperLinkField
HeaderText="Resource Link"
DataNavigateUrlFields="document_filename"
DataNavigateUrlFormatString="{0}"
Target="_blank"
DataTextField="document_name"
DataTextFormatString="{0}" />
<asp:BoundField
DataField="document_description"
HeaderText="Description" />
</Columns>
</asp:GridView>
<asp:SqlDataSource
ID="dataDocuments"
runat="server">
</asp:SqlDataSource>


Both could be considered training, but to better organize the page, I will split the documents up into groups, each document having a group column in the database named document_group.
On my backend, I have a new table to complement the Documents table called DocumentGroups that has a docGrp_name and docGrp_id field. The docGrp_id field will link to the document_group column I mentioned.

Now to drop my original gridview in a second gridview of "Document Groups"
This is where the code gets a little more bloated but still relatively simple:
<asp:GridView
ID="gvDocumentGroups"
DataSourceID="dataDocumentGroups"
DataKeyNames="docGrp_id"
AutoGenerateColumns="false"
GridLines="None"
runat="server">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<div style="padding-bottom: 12px;">
<div><%#Eval("docGrp_name")%></div>
<asp:GridView
ID="gvDocuments"
DataSourceID="dataDocuments"
AutoGenerateColumns="false"
DataKeyNames="document_id"
CellPadding="8"
HeaderStyle-BackColor="#e0e0e0"
BorderColor="#e0e0e0"
runat="server">
<Columns>
<asp:HyperLinkField
HeaderText="Resource Link"
DataNavigateUrlFields="document_filename"
DataNavigateUrlFormatString="{0}"
Target="_blank"
DataTextField="document_name"
DataTextFormatString="{0}" />
<asp:BoundField
DataField="document_description"
HeaderText="Description" />
</Columns>
</asp:GridView>
<asp:SqlDataSource
ID="dataDocuments"
runat="server">
</asp:SqlDataSource>
</div>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:SqlDataSource
ID="dataDocumentGroups"
runat="server">
</asp:SqlDataSource>


I added in some padding for better spacing, but you can play around with the CSS however you want.

Now. I didn't put connection strings or select commands in the original SqlDataSource because, if you don't know what you're doing there, this tutorial is not for you.
But now that we've embedded this gridview inside another one, that (inner layer) SqlDataSource is more than likely going to be fed a variable from your outer layer grid.
For my purposes, I'm going to use the docGrp_id, which I conveniently fed into the DataKeys of my outer layer SqlDataSource.
For more information on pulling the current row's datakey, click here.

For Each thisRow As GridViewRow In gvDocumentGroups.Rows
CType(thisRow.FindControl("dataDocuments"), SqlDataSource).SelectCommand = "SELECT * FROM [Documents] WHERE [document_group] = '" & gvDocumentGroups.DataKeys(thisRow.RowIndex).Value & "'"
Next




And there you have it!

Monday, June 28, 2010

How To Fill a GridView with Custom Objects in an Arraylist using a DataTable

Usually when I use a GridView, I pull and write to a database table, which is the easiest way to use them. All of the Edit and Delete commands are pretty much prepackaged together with the SqlDataSource control.

However, I recently found myself building a shopping cart tool that didn't touch a database until the user submits the order. I personally like to write to the database right away in case the client gets disconnected, but in this particular case, I didn't have that pleasure.

So, I needed to be able to pull an arraylist of objects of type ORDER (a custom class I build) out of the session variable SHOPPINGCART (another custom class I built) and feed them into a gridview for the user to edit quantity, remove items from the cart, etc before checking out.

I first put together my GridView:
<asp:GridView
ID="gvYourCart"
DataKeyNames="OrderID"
AutoGenerateColumns="false"
runat="server">
<Columns>
<asp:BoundField
HeaderText="Quantity"
DataField="Quantity" />
<asp:BoundField
HeaderText="Device Model"
DataField="Device Model"
ReadOnly="true" />
<asp:BoundField
HeaderText="Rate Plan"
DataField="Rate Plan"
ReadOnly="true" />
<asp:BoundField
HeaderText="Total One Time Cost"
DataField="Total One Time Cost"
ReadOnly="true" />
<asp:BoundField
HeaderText="Total Recurring Costs"
DataField="Total Recurring Costs"
ReadOnly="true" />
</Columns>
</asp:GridView>

I only want users to be able to edit the Quantity field and remove orders from the cart, so I've made all other columns readonly.

Now, to start populating my gridview, I'll need to create a DataTable and populate it with the fields that are relavant to what I'll be displaying.
Private _dt As Data.DataTable
Public Sub Form_Load() Handles Me.Load
CType(Master.FindControl("nav_order"), HtmlTableCell).Attributes.Add("class", "nav_highlight")
_dt = New Data.DataTable()
_dt.Columns.Add("OrderID")
_dt.Columns.Add("Quantity")
_dt.Columns.Add("Product Model")
_dt.Columns.Add("Total One Time Cost")
gvYourCart.DataSource = _dt
If Not IsPostBack Then
BindData()
End If
End Sub

Public Sub BindData()
For Each thisOrder As Order In CType(Session("ShoppingCart"), ShoppingCart).Orders
Dim newRow As Data.DataRow = _dt.NewRow()
newRow.Item("OrderID") = thisOrder.ID
newRow.Item("Quantity") = thisOrder.Quantity
newRow.Item("Product Model") = thisOrder.Product.Name
newRow.Item("Total One Time Cost") = FormatCurrency(thisOrder.OneTimeCost(), 2)
_dt.Rows.Add(newRow)
Next
gvYourCart.DataBind()
End Sub

Because I know that I will need to bind data to my gridview every time I edit or delete from the control, I went ahead and created a BindData function that will pull data from my Shopping Cart in sessions and tie its data to the DataTable.

In order to edit and delete rows of my GridView, I will need to create 4 new functions (these are usually handled by the UpdateCommand and DeleteCommand of the SqlDataSource, but we'll need to build our own since we're doing things a little more custom than usual.

Add the following tags to the GridView control:
AutoGenerateEditButton="true"
AutoGenerateDeleteButton="true"
OnRowEditing="EditQty"
OnRowCancelingEdit="CancelEditQty"
OnRowUpdating="UpdateEditQty"
OnRowDeleting="DeleteOrder"


Now, at this point your code may look nothing like mine, but this should give you a pretty good start.

The EditQty sub procedure will pretty much always look like this. All we are doing is telling the GridView which row we will be editing. Because we set some columns as ReadOnly, they will not be editable.

Public Sub EditQty(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewEditEventArgs)
gvYourCart.EditIndex = e.NewEditIndex
BindData()
End Sub


Again, the CancelEditQty sub procedure is pretty standard. We are just letting the gridview know that we don't want to edit any rows by setting the EditIndex to -1.

Public Sub CancelEditQty()
gvYourCart.EditIndex = -1
BindData()
End Sub


Because I didn't allow for sorting on my GridView, I know that the shopping cart's order are displayed on the GridView in the same order as they are stored in the shopping cart, so I can afford to make the assumption that the index of the ArrayList in my ShoppingCart object's Orders property matches the e.RowIndex of the GridView, but be careful of how you make your edits.

Public Sub UpdateEditQty(ByVal sender As Object, ByVal e As GridViewUpdateEventArgs)
Dim thisRow As GridViewRow = gvYourCart.Rows(e.RowIndex())
CType(CType(Session("ShoppingCart"), ShoppingCart).Orders(e.RowIndex), Order).Quantity = CType(thisRow.Cells(1).Controls(0), TextBox).Text

gvYourCart.EditIndex = -1
BindData()
End Sub

Ditto.

Public Sub DeleteOrder(ByVal sender As Object, ByVal e As GridViewDeleteEventArgs)
CType(Session("ShoppingCart"), ShoppingCart).Orders.RemoveAt(e.RowIndex())
BindData()
End Sub


And that is it! You should now be able to view the contents of your ShoppingCart or other ArrayList in GridView form, and make easy edits and deletes.


How To Fill a GridView with Custom Objects from an Arraylist using a DataTable

Friday, May 28, 2010

How To Access the Data Key When Editing A GridView Row

Usually, when I create a GridView, I use an ID field that represents a unique ID tied to each row that I can use to update the database, as seen in the picture below.


However, I recently had a client request that I remove this field as it was confusing their customers. Fair enough. I removed the field, but now I had trouble updating or deleting the database. The field I was formerly using for my WHERE clause is no longer available. Luckily, I've fed the same ID field into the gridview as a Key:

<asp:GridView
ID="GridView1"
AutoGenerateColumns="false"
CellPadding="3"
DataKeyNames="orderID"
AllowSorting="true"
AutoGenerateSelectButton="true"
AutoGenerateEditButton="true"
AutoGenerateDeleteButton="true"
OnRowEditing="GridView1_RowEditing"
OnRowUpdating="GridView1_RowUpdating"
OnRowCancelingEdit="GridView1_RowCancelingEdit"
runat="server">


It would seem like common sense to be able to use the GridViewUpdateEventArgs parameter of my GridView1_RowUpdating Sub procedure a la e.Keys("orderID") to get the id of that particular row, but it return nothing. I'm not sure why this function of GridViewUpdateEventArgs exists, but I've never been able to get it to produce a value.

Exploring it a bit more, I looped through different arrays the GridView would provide and finally found the value using this method:

GridView1.DataKeys(e.RowIndex).Value

GridView1.DataKeys will provide a list of all the Keys of the gridview and feeding in the e.RowIndex will indicate that you want the key for the row you are editing.

Wednesday, May 5, 2010

How To Sort With a Custom GridView ItemTemplate

I've been playing around a lot today with the GridView control and wanted to try to use it as a listing tool to show results from a search.

I would prefer to display the results in a stylized panel rather than the usual spreadsheet format. To accomplish this, I can use the <asp:TemplateField> column object and build any kind of HTML my heart desires:

<asp:TemplateField>
<ItemTemplate>
<div style="background-color: #e4e4e4; color: #303030; padding: 10px;">
<div>
<%#Eval("row_image")%>
</div>
<div>
<%#Eval("row_name")%>
</div>
<div>
<%#Eval("row_description")%>
</div>
<div>
<%#Eval("row_priceRange")%>
</div>
</div>
</ItemTemplate>
</asp:TemplateField>


Now, I want to use the GridView control's Sorting feature that is usually handled by the HeaderText. I want to allow users to sort by Name, Description and Price Range. I can create a a column for each of these fields that will also create HeaderText that I can then use for the SortExpression value:
<asp:TemplateField HeaderText="Name" SortExpression="row_name" />
<asp:TemplateField HeaderText="Description" SortExpression="row_description" />
<asp:TemplateField HeaderText="Price Range" SortExpression="row_priceRange" />

I used the TemplateField control rather than the BoundField so that I wouldn't actually populate any data into the field. It's just a header and a blank cell with the width of the HeaderText. This will create a lot of empty space to the right of my GridView which I don't want, plus the clickable Headers are way to the right of the GridView itself.

A little bit of CSS easily fixed the issue. I'll set my CssClass of the GridView to CssClass="gv_list" and add the following css structure:
.gv_list th {
float: left;
}

Now, I've got three links at the top of my GridView that I can use for sorting the content either Ascendingly or Descendingly. Just to make sure all my site visitors understand what the links are for, I'll add some HeaderText to my first column (with all the divs) HeaderText="Sort By" and we are golden.


Getting rid of the top and bottom border on the GridView in FireFox

You'll soon find that I'm not one of those developers who praises FireFox and bashes Internet Explorer. I've always developed for IE since it has long been the most used browser; but within recent history, the red fox has snuck up and become the most used browser on the market.

By default, FireFox adds a border at the top and bottom of GridView cells.


Try all the CSS you want, but you won't be able to get rid of these cursed little lines. It is actually a setting in the GridView control itself. All you need to do is set GridLines="None" and the lines disappear.

Just that easy!