Reputation: 4823
When my Gridview is bound with "few rows" of data, my current approach for adding a new row is an adequate design and works "well". However, if bound with "many rows" of data, my current approach for adding a new row is flawed: the EmptyDataTemplate I am using is exposed using the FooterTemplate. Thus, if I have 3 rows of data and click "Add New Record", the grid is redisplayed with a 4th row all "prepared" for data entry. Yet if I have 30 rows, the display of the row to be inserted is so far down it requires a scroll.
Protected Sub AddNewRecord(ByVal sender As Object, ByVal e As EventArgs)
GridView1.ShowFooter = True
'rebind data so GridView1_RowDataBound gets a chance to populate the footer
iSubscriberID = Session("SubscriberID")
LoadDataGrid(iSubscriberID)
End Sub
I'm hoping to be able to improve the operation of adding a new row BUT still use the FooterTemplate.
Is there any code that could be added to my GridView1_RowDataBound handler to hide existing data rows but still expose the EmptyDataTemplate for insertion via the FooterTemplate? I've tried hacking a few things there without success. Here is my existing code for that handler:
Protected Sub GridView1_RowDataBound(ByVal sender As Object, ByVal e As GridViewRowEventArgs) _
Handles GridView1.RowDataBound
'-------------------------------------------------------------------------------------------*
' Handle 'Insert' requirements:
' - Bind dropdownlist controls with the possible incumbents and backups for some new position
'-------------------------------------------------------------------------------------------*
If e.Row.RowType = DataControlRowType.Footer Then
' Finding the Dropdown control.
Dim ctrl As Control = e.Row.FindControl("ddlUsers")
If ctrl IsNot Nothing Then
Dim dd As DropDownList = TryCast(ctrl, DropDownList)
dd.DataSource = allUsers
dd.DataBind()
End If
Dim ctrlB As Control = e.Row.FindControl("ddlUsersBackup")
If ctrlB IsNot Nothing Then
Dim ddB As DropDownList = TryCast(ctrlB, DropDownList)
ddB.DataSource = allUsers
ddB.DataBind()
End If
End If
End Sub
I'm using TemplateField definitions for all the columns; here is one example that shows a part of the FooterTemplate:
<asp:TemplateField HeaderText="Incumbent">
<ItemTemplate>
<asp:Label ID="lblUser" runat="server" Text='<%# Eval("Incumbent")%>'></asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:Label ID="lblUser" runat="server" Text='<%# Eval("Incumbent")%>' Visible = "false"></asp:Label>
<asp:DropDownList Width="100%" runat="server"
id="ddlUsers" AutoPostBack="true"
DataTextField="FullName" DataValueField="UserID"
OnSelectedIndexChanged="ddlUsers_SelectedIndexChanged">
</asp:DropDownList>
</EditItemTemplate>
<FooterTemplate>
<asp:Label ID="lblUser" runat="server" Text='Set Incumbent'></asp:Label>
<br />
<asp:DropDownList Width="100%" runat="server"
id="ddlUsers" AutoPostBack="true"
DataTextField="FullName" DataValueField="UserID"
OnSelectedIndexChanged="ddlUsers_SelectedIndexChanged">
</asp:DropDownList>
</FooterTemplate>
</asp:TemplateField>
Another idea I had that might work would be to scroll the page to the bottom so the "insertion" line is always visible when repopulating the gridview with ShowFooter=True. However, that seems to involve some Javascript which I'd prefer to avoid on this page.
EDIT: 18 Feb 2016 - Attempt to add Paging to Gridview - a new complication
@Lesmian - adding a pager as you suggested was easy but now it totally breaks the Gridview:
I researched that error and I cannot see why my strongly-typed collection for Positions cannot support paging; here is the code that instantiates my data source for the Gridview:
Public Class Positions
Implements IEnumerable(Of Position)
Public List As New List(Of Position)
Public Function GetEnumerator() As IEnumerator(Of Position) _
Implements IEnumerable(Of Position).GetEnumerator
Return List.GetEnumerator()
End Function
Private Function GetEnumerator1() As IEnumerator _
Implements IEnumerable.GetEnumerator
Return List.GetEnumerator()
End Function
Public Sub New(ByVal subscriberID As Integer, Optional ByVal filterOnUserID As Integer = 0)
Dim sConnDatabase As String = ConfigurationManager.ConnectionStrings("DatabaseConnString").ConnectionString
Dim connection As New SqlConnection(sConnDatabase)
Dim cmd As SqlCommand
Try
cmd = New SqlCommand("dbo.GetPositionsBySubscriberID", connection)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("@SubscriberID", subscriberID)
cmd.Parameters.AddWithValue("@UserID", filterOnUserID) 'non-zero UserID returns only positions where User is Incumbent or Backup
connection.Open()
Dim objReader As SqlDataReader = cmd.ExecuteReader()
Do While objReader.Read()
Dim p As Position = New Position(objReader)
List.Add(p)
Loop
objReader.Close()
connection.Close()
The approach of adding a pager to solve the original problem has opened up a new issue. Any thoughts on that?
Upvotes: 2
Views: 851
Reputation: 9166
The actual question in the OP seems to boil down to "How do I keep the row for inserting in view" after post back.
The Page
object already has a method called Focus
which can be used to cause a specific control to receive focus after the page rendering is complete.
When a control that is outside of the visible area of the browser receives focus, the browser will scroll that control into view so the user can see what is focused.
So to make the insert row visible, just make sure to set focus on a suitable control in the row when you make it visible in the code behind.
Upvotes: 2
Reputation: 3952
Scrolling gridview requires javascript, but if you don't want to use it maybe other approach will be suitable for your needs. If you add a pager to your gridview and set a small number of items per page then adding new item won't cause scrollbar to appear. With this approach we have however other flaw - new item could appear on a new page so you should force grid to show last page then new row will be always visible:
Protected Sub GridView1_RowCommand(sender As Object, e As GridViewCommandEventArgs) _
Handles GridView1.RowCommand
' Insert data if the CommandName == "Insert"
' and the validation controls indicate valid data...
If e.CommandName = "Insert" AndAlso Page.IsValid Then
' Insert new record
GridView1DataSource.Insert()
' Indicate that the user needs to be sent to the last page
SendUserToLastPage = True
End If
End Sub
Protected Sub GridView1_DataBound(sender As Object, e As EventArgs) _
Handles GridView1.DataBound
' Send user to last page of data, if needed
If SendUserToLastPage Then
GridView1.PageIndex = GridView1.PageCount - 1
End If
End Sub
Here you can find complete example of this approach: http://www.asp.net/web-forms/overview/data-access/enhancing-the-gridview/inserting-a-new-record-from-the-gridview-s-footer-vb
EDIT: 18 Feb 2016 - data source does not support server-side data paging error
Your custom collection must implement ICollection interface in order to work with GridView server side paging. IEnumerable doesn't implement ICollection that's why you get error. For an example of implementation in vb you can read this: https://support.microsoft.com/en-us/kb/306961 and this: http://www.codeproject.com/Articles/265692/Having-fun-with-custom-collections. After that GridView should do the rest for you.
Upvotes: 0
Reputation: 9470
General idea:
1. Place your GridView in a fixed-height scrollable div
2. Below the div place single line table with all controls you need to insert new record and "Add" button
3. If possible use SqlDataSource to populate GridView and include insert command to the SqlDataSource
4. In InsertParameters block use asp:controlParameter to bind to new values.
5. Add code to Add_Click handler
mySqlDataSource.Insert()
PS: Even better, rewrite your GridView to ListView (supported by .NET 3.5). This will give you much more flexibility. For example, you can place insert into first row.
Upvotes: 0