How can I add group heading rows to a data bound GridView - asp.net

I have a GridView with three databound columns, like this:
<Columns>
<asp:BoundField HeaderText="Type" DataField="Type" />
<asp:BoundField HeaderText="Amenity" DataField="Amenity" />
<asp:BoundField HeaderText="Distance" DataField="Distance" DataFormatString="{0:0.00} Km" />
</Columns>
Records are sorted by type, and I want to remove the Type column, but then insert a header row for each type when the value of type for the next set of rows changes. How can I do this?

This is not the prettiest, but it solves the problem without going outside of the GridView paradigm. Thanks to carlj for Adding (or Inserting) Subheader Rows into a Gridview
Dim _currentAmenityType = String.Empty
Protected Sub amenitiesGrid_RowDataBound(ByVal sender As Object, ByVal e As GridViewRowEventArgs) Handles amenitiesGrid.RowDataBound
If (e.Row.RowType = DataControlRowType.DataRow) Then
Dim drv = e.Row.DataItem
If (drv("Type") <> _currentAmenityType) Then
_currentAmenityType = drv("Type")
Dim parentTable = TryCast(e.Row.Parent, Table)
If Not parentTable Is Nothing Then
Dim row = New GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal)
Dim cell = New TableCell()
cell.ColumnSpan = amenitiesGrid.Columns.Count
cell.Width = Unit.Percentage(100)
cell.Style.Add("font-weight", "bold")
cell.Style.Add("background-color", "#c0c0c0")
cell.Style.Add("color", "white")
Dim span = New HtmlGenericControl("span")
span.InnerHtml = _currentAmenityType
cell.Controls.Add(span)
row.Cells.Add(cell)
parentTable.Rows.AddAt(parentTable.Rows.Count - 1, row)
End If
End If
End If
End Sub

With the standard GridView control, I believe that you cannot dynamically add extra rows once the control has been databound, so you would need to alter the datasource prior to databinding. However, this probably isn't a very good solution to what you need.
I think that using the GridView control might not be your best option in this situation, as the HTML that is rendered would be
<table>
<thead>
<tbody>
So adding extra "header" rows between rows wouldn't actually be Header rows. They would simply be an extra row <tr><td>. Knowing this, I think that you might be better using a Repeater control, building up the HTML for the table within the control, and then have a Placeholder within the Content Template which you can use to mimic adding a new Row.
e.g.
<table>
<asp:Repeater ID="rpt1" runat="server">
<HeaderTemplate>
<thead>
<tr>
<th> </th>
<th>Amenity</th>
<th>Distance</th>
</tr>
</thead>
<tbody>
</HeaderTemplate>
<ItemTemplate>
<asp:PlaceHolder id="phRow" runat="server" />
<tr>
<td> </td>
<td>Amenity Value</td>
<td>Distance Value</td>
</tr>
</ItemTemplate>
<FooterTemplate>
</tbody>
</FooterTemplate>
</asp:Repeater>
</table>
In your code, loop through each Item in the Repeater. If the Type is different, then add a Literal to the Placeholder in that Row.
Literal lt = new Literal() { Text = "<tr><td colspan='3'>Type Value</td></tr>" };

Related

How to get specific value from data row while binding into a data list ? asp.net vb.net

I have a data list control, inside of it I have two tables:
<asp:DataList ID="dataListAccount" runat ="server">
<ItemTemplate >
<table runat ="server" id="tblAccountInfo">
<tr>
<td>
Account Id: <%#Eval("AccountId")%>
</td>
</tr>
</table>
<br>
<table runat="server" id ="tblAccountAmount" border="1">
<tr>
<td><%#Eval("AccountBalance", "{0:C}")%></td>
</tr>
</table>
</ItemTemplate>
</asp:DataList>
Then I populate the data list control with:
'create datatable
Dim dataTableAccount As DataTable = New DataTable()
dataTableAccount.Columns.Add("AccountID")
dataTableAccount.Columns.Add("AccountBalance")
'populate data table
Dim dataRow As DataRow = dataTableAccount.NewRow()
dataRow(0) = 1 'Account ID
dataRow(1) = 100 'Balance on the Account with Id=1
dataTableAccount.Rows.Add(dataRow)
Dim dataRow1 As DataRow = dataTableAccount.NewRow()
dataRow1(0) = 2 'Account Id
dataRow1(1) = 0 'Balance on the Account with Id=2
dataTableAccount.Rows.Add(dataRow1)
dataListAccount.DataSource = dataTableAccount
dataListAccount.DataBind()
In the event dataListAccount_ItemDataBound I want to know how can I get the "AccounId" for the current item binding.
Private Sub dataListAccount_ItemDataBound(sender As Object, e As
System.Web.UI.WebControls.DataListItemEventArgs) Handles
dataListAccount.ItemDataBound
Dim CurrentAccountId= ???????
End Sub
Better put value into label
Account Id: <asp:label id="lblAccountID" runat="server" Text ='<%#Eval("AccountId")%>' />
On code behind, you can easy to find it use
Dim CurrentAccountId = e.item.FindControl("lblAccountID")
I think I found one way to do it:
Dim currentRowBinding As System.Data.DataRowView = CType(e.Item.DataItem, System.Data.DataRowView)
Dim currentAccountId = currentRowBinding("AccountId")

Unable to Get Element with ID in VB.Net

I have the below code to retrieve the table element when the checkbox is checked.
With this, I could get the table ID. When I try to get the HtmlTable with the ID, it throws the null reference exception.
Once I get the table element with its ID, I need to loop through the rows of that table. Could anyone help me out?
ASPX.VB
Protected Sub CheckBox2_CheckedChanged(sender As Object, e As EventArgs)
Dim tab As String
Dim check As CheckBox = CType(sender, CheckBox)
tab = check.Parent.Parent.Parent.ID
Dim tabb As HtmlControl = Me.FindControl(tab)
Dim myTab As HtmlTable = CType(tabb, HtmlTable)
For Each cell As HtmlTableRow In myTab.Rows
//My Code Here
Next
End Sub
ASPX:
<table id="itemlist" class="mel-table" runat="server">
<tr>
<td>
<asp:CheckBox runat="server" ID="CheckBox2" OnCheckedChanged="CheckBox2_CheckedChanged" AutoPostBack="true" />
</td>
</tr>
</table>
Use the parent element ID under which the table is nested like below.
Protected Sub CheckBox2_CheckedChanged(sender As Object, e As EventArgs)
Dim tab As String
Dim check As CheckBox = CType(sender, CheckBox)
tab = check.Parent.Parent.Parent.ID
Dim myTab As HtmlTable = CType(mydiv.FindControl(tab), HtmlTable)
For Each cell As HtmlTableRow In myTab.Rows
//My Code Here
Next
End Sub
<div id="mydiv" runat="server">
<table id="itemlist" class="mel-table" runat="server">
<tr>
<td>
<asp:CheckBox runat="server" ID="CheckBox2" OnCheckedChanged="CheckBox2_CheckedChanged" AutoPostBack="true" />
</td>
</tr>
</table>
</div>

How to add a table row to thead section of a gridview?

CREATE A THEAD SECTION IN YOUR GRIDVIEW
Protected Overrides Sub OnPreRenderComplete(ByVal e As EventArgs)
If (gv.Rows.Count > 0) Then
gv.HeaderRow.TableSection = TableRowSection.TableHeader
End If
End Sub
INSERT A ROW WITH CONTENT INTO YOUR GRIDVIEW
Protected Sub gv_OnDataBound(sender As Object, e As EventArgs) Handles gv.DataBound
Dim row As New GridViewRow(0, 0, DataControlRowType.Header, DataControlRowState.Normal)
For i As Integer = 0 To gv.Columns.Count - 1
Dim cell As New TableHeaderCell()
Dim txtBx As New TextBox()
cell.Controls.Add(txtBx)
row.Controls.Add(cell)
Next
gv.HeaderRow.Parent.Controls.AddAt(1, row)
End Sub
End Class
This will insert a row with a textbox within each cell <th> at row 1.
QUESTION
How to insert a row (as in the above example) into the thead section of a gridview?
Doing this on the aspx page or in the VB is fine.
In your OnDataBound event, try setting the row you created to be in the "TableHeader" section (like you did in the PreRenderComplete event) prior to adding it to the GridView:
row.TableSection = TableRowSection.TableHeader
Since this sets the section of the table where the row belongs (header, footer, body), it should cause your row to wind up in the right place.
I couldn't find a way to do this in vb.
To apply content to the existing tr th row in the thead section you can use.
<asp:TemplateField>
<HeaderTemplate>
<p>COL TITLE</p>
<asp:TextBox ID="txBx" runat="server"/>
</HeaderTemplate>
<ItemTemplate>
CELL DATA
</ItemTemplate>
</asp:TemplateField>
Which will result in:
<thead>
<tr>
<th>
<p>COL TITLE</p>
<input id="txBx" type="text"/>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
COL DATA
</td>
</tr>
</tbody>

Deleting rows from listview bases on a value for a column

I got a datatable in list view.
ID TelcoName Reload Value(RM) Quantity Total(RM)
1 Maxis 5 1 5 Delete
2 Digi 5 1 5 Delete
This is basically what my table looks like.
My html code is this:
<asp:ListView ID="ListView1" runat="server" OnSorting="ListView1Sorting" OnItemCommand="ListView1_ItemCommand">
<LayoutTemplate>
<table border="0" cellpadding="1">
<tr style="background-color:#FFFFFF">
<th align="center"><asp:Label ID="lblId" runat="server">Id </asp:Label></th>
<th align="center"><asp:Label ID="lblName" runat="server">TelcoName </asp:Label></th>
<th align="center"><asp:Label ID="lblReloadValue" runat="server">Reload Value(RM) </asp:Label></th>
<th align="center"><asp:Label ID="lblQuantity" runat="server">Quantity </asp:Label></th>
<th align="center"><asp:Label ID="lblTotal" runat="server">Total (RM) </asp:Label></th>
<th></th>
</tr>
<tr id="itemPlaceholder" runat="server"></tr>
</table>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td align="center"><asp:Label runat="server" ID="lblId"><%#Eval("ID") %></asp:Label></td>
<td align="center"><asp:Label runat="server" ID="lblTelcoName"><%#Eval("TelcoName") %></asp:Label></td>
<td align="center"><asp:Label runat="server" ID="lblReloadValue"><%#Eval("ReloadValue")%></asp:Label></td>
<td align="center"><asp:Label runat="server" ID="lblQuantity"><%#Eval("Quantity")%></asp:Label></td>
<td align="center"><asp:Label runat="server" ID="lblTotal"><%#Eval("Total")%></asp:Label></td>
<td align="center"><asp:LinkButton ID="lnkDelete" runat="server" CommandName="Sort" CommandArgument="Delete">Delete</asp:LinkButton></td>
</tr>
</ItemTemplate>
</asp:ListView>
This is my behind code for deleting:
Protected Sub ListView1_ItemCommand(ByVal sender As Object, ByVal e As ListViewCommandEventArgs)
If (e.CommandName) = "Sort" Then
Dim txteno As Label = DirectCast(e.Item.FindControl("ID"), Label)
Dim deletecommand As String = "delete from dt where ID=" & Convert.ToInt32(txteno.text)
Session("dt").DeleteCommand = deletecommand
End If
End Sub
My problem here is i coud'nt get the ID of the row which the user choose. The "ID" is the name of the first column but when i run the website, this statement return nothing.
Dim txteno As Label = DirectCast(e.Item.FindControl("ID"), Label)
My question is how do i retrieve the value of the column("Id") to use for the deleting data? i ask around and someone suggest to me to use e.item.DataItemIndex but i coudnt find this command anywhere. Any idea how to solve this?
This has just been added
Dim dt As New DataTable
Dim i = 0
Dim ID As New DataColumn("ID")
dt.Columns.Add(ID)
Dim TelcoName As New DataColumn("TelcoName")
dt.Columns.Add(TelcoName)
Dim ReloadValue As New DataColumn("ReloadValue")
dt.Columns.Add(ReloadValue)
Dim Quantity As New DataColumn("Quantity")
dt.Columns.Add(Quantity)
Dim Total As New DataColumn("Total")
dt.Columns.Add(Total)
ListView1.DataSource = dt
ListView1.DataBind()
Session("dt") = dt
After i binded to the listview, do i need to delcare a name for the datatable so that i can use it as a reference for sql statement or i can just leave it as dt and in sql statement, when i refer to dt, it refer to the table in the listview?
EDIT: Addressing two questions separately - one is that a control handle could not be grabbed, the other question is around deleting from a DataTable
Control Reference
If you're trying to get a handle on the control using this:
Dim txteno As Label = DirectCast(e.Item.FindControl("ID"), Label)
Then I think the control ID is incorrect as in your markup you've defined it as "lblId". So perhaps try:
Dim txteno As Label = DirectCast(e.Item.FindControl("lblId"), Label)
Also, I'm not sure what impact it would have but I would give the controls in the LayoutTemplate (so I am guessing the header for your ListView) different ID's to the controls in your ItemTemplate, if only for maintenance/cleanliness if nothing else.
The label should also be bound like:
<asp:Label id="lblId" runat="server" Text='<%# Eval("ID") %>'></asp:Label>
So that in code-behind the text can be retrieved.
Deleting from DataTable
Based on the poster's comments, the DataTable's data is not coming from a database.
Since you aren't connecting back to a database, you don't have to write SQL to remove items from the DataTable.
It looks like you're storing the DataTable in session. And also that you are binding totally in code. As such, what you could do in your ItemCommand method is:
Iterate over the data table to find the item to delete
Delete the item (you could consult, for example, this forum post)
Once the item has been removed from the DataTable, re-bind it to the list view. As an example of someone who seems to have been in a similar situation please see this
HTH,
Nathan

How do I best populate an HTML table in ASP.NET?

This is what I've got. It works. But, is there a simpler or better way?
ASPX Page…
<asp:Repeater ID="RepeaterBooks" runat="server">
<HeaderTemplate>
<table class="report">
<tr>
<th>Published</th>
<th>Title</th>
<th>Author</th>
<th>Price</th>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><asp:Literal ID="LiteralPublished" runat="server" /></td>
<td><asp:Literal ID="LiteralTitle" runat="server" /></td>
<td><asp:Literal ID="LiteralAuthor" runat="server" /></td>
<td><asp:Literal ID="LiteralPrice" runat="server" /></td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
ASPX.VB Code Behind…
Protected Sub Page_Load( ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim db As New BookstoreDataContext
RepeaterBooks.DataSource = From b In db.Books _
Order By b.Published _
Select b
RepeaterBooks.DataBind()
End Sub
Sub RepeaterBooks_ItemDataBound( ByVal sender As Object, ByVal e As System.Web.UI.WebControls.RepeaterItemEventArgs) Handles RepeaterBooks.ItemDataBound
If e.Item.ItemType = ListItemType.Item Or e.Item.ItemType = ListItemType.AlternatingItem Then
Dim b As Book = DirectCast(e.Item.DataItem, Book)
DirectCast(e.Item.FindControl("LiteralPublished"), Literal).Text = "<nobr>" + b.Published.ToShortDateString + "</nobr>"
DirectCast(e.Item.FindControl("LiteralTitle"), Literal).Text = "<nobr>" + TryNbsp(HttpContext.Current.Server.HtmlEncode(b.Title)) + "</nobr>"
DirectCast(e.Item.FindControl("LiteralAuthor"), Literal).Text = "<nobr>" + TryNbsp(HttpContext.Current.Server.HtmlEncode(b.Author)) + "</nobr>"
DirectCast(e.Item.FindControl("LiteralPrice"), Literal).Text = "<nobr>" + Format(b.Price, "c") + "</nobr>"
End If
End Sub
Function TryNbsp(ByVal s As String) As String
If s = "" Then
Return " "
Else
Return s
End If
End Function
#Geoff
That sort of Eval statement was actually added in 2.0, but if performance is important Eval should be avoided since it uses Reflection.
The repeater is a pretty good way of doing it, although it might be faster to generate the table in code:
ASPX Page:
<table class="report" id="bookTable" runat="server">
<tr>
<th>Published</th>
<th>Title</th>
<th>Author</th>
<th>Price</th>
</tr>
</table>
Code Behind:
Protected Sub Page_Load( ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostback Then
BuildTable()
End If
End Sub
Private Sub BuildTable()
Dim db As New BookstoreDataContext
Dim bookCollection = from b in db.Books _
Order By b.Published _
Select b
Dim row As HtmlTableRow
Dim cell As HtmlTableCell
For Each book As Books In bookCollection
row = New HtmlTableRow()
cell = New HtmlTableCell With { .InnerText = b.Published.ToShortDateString }
row.Controls.Add(cell)
cell = New HtmlTableCell With { .InnerText = TryNbsp(HttpContext.Current.Server.HtmlEncode(b.Title)) }
row.Controls.Add(cell)
cell = New HtmlTableCell With { .InnerText = TryNbsp(HttpContext.Current.Server.HtmlEncode(b.Author))
row.Controls.Add(cell)
cell = New HtmlTableCell With { .InnerText = Format(b.Price, "c") }
row.Controls.Add(cell)
bookTable.Controls.Add(row)
Next
I guess it depends on how important speed is to you. For simplicity's sake I think I would go with the Repeater.
The ListView control introduced with framework 3.5 might be a little bit better solution. Your markup would look like this:
<asp:ListView runat="server" ID="ListView1"
DataSourceID="SqlDataSource1">
<LayoutTemplate>
<table runat="server" id="table1" runat="server" >
<tr runat="server" id="itemPlaceholder" ></tr>
</table>
</LayoutTemplate>
<ItemTemplate>
<tr runat="server">
<td runat="server">
<asp:Label ID="NameLabel" runat="server"
Text='<%#Eval("Name") %>' />
</td>
</tr>
</ItemTemplate>
</asp:ListView>
You'll want to set your data source ID from a public or private property in the code-behind class.
In .Net 3.0+ you can replace your ItemDataBound to the asp:Literal by doing something like this:
<ItemTemplate>
<tr>
<td><%# Eval("published") %></td>
...
where "published" is the name of a field in the data you have bound to the repeater
Edit:
#Alassek: I think the performance hit of reflection is often over-emphasized. Obviously you need to benchmark performance of your app, but the hit of the Eval is likely measured in milliseconds. Unless your app is serving many concurrent hits, this probably isn't an issue, and the simplicity of the code using Eval, along with it being a good separation of the presentation, make it a good solution.
This is what the GridView is for.
<asp:GridView runat="server" DataSourceID="SqlDataSource1">
<Columns>
<asp:BoundField HeaderText="Published" DataField="Published" />
<asp:BoundField HeaderText="Author" DataField="Author" />
</Columns>
</asp:GridView>
I would use a GridView (or DataGrid, if you are using an older version of ASP.NET).
<asp:GridView ID="gvBooks" runat="server" AutoGenerateColumns="False">
<Columns>
<asp:BoundField HeaderText="Published" DataField="Published" />
<asp:BoundField HeaderText="Title" DataField="Title" />
<asp:BoundField HeaderText="Author" DataField="Author" />
<asp:BoundField HeaderText="Price" DataField="Price" />
</Columns>
</asp:GridView>
With some code-behind:
Private Sub gvBooksRowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles gvBooks.RowDataBound
Select Case e.Row.RowType
Case DataControlRowType.DataRow
''' Your code here '''
End Select
End Sub
You can bind it in a similar way. The RowDataBound event is what you need.
I agree with Geoff, the only time we use Literals is if we want to do something different with the data.
For example, we might want a DueDate field to say "Today" or "Yesterday" instead of the actual date.
ALassek wrote:
…generate the table in code…
I like the look of that! It seems MUCH less likely to produce a run-time exception due to a typo or field name change.
If you don't need ASP.NET handled edit cabilities I would stay away from the DataGrid and the GridView ... they provide unnecessary bloat.

Resources