Dropdownlist results in gridview - asp.net

I'm having some trouble getting the results of the drop-downs in a gridview, and haven't found any helpful articles for VB code.
What I'm trying to do is build a site for tests. So far I have the gridview built w/the extra column for a drop-down list where the true/false answer will be selected. When the test is completed, there is a submit button. All is well except I need to be able to get the results of each drop-down list on post-back to a variable or array (each test contains 10 questions) so the results can be checked/graded. When the results are determined I would like to display a label in it's place and change the text value accordingly (correct or incorrect), so I'll need to be able to enumerate these as well (i.e. label1.text="Correct!", label2.text="Incorrect!", etc...).
Code so far.
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataSourceID="SqlDataSource1" BackColor="White" BorderColor="#3366CC"
BorderStyle="None" BorderWidth="1px" CellPadding="1">
<RowStyle BackColor="White" ForeColor="#003399" />
<Columns>
<asp:BoundField DataField="Question" HeaderText="Question"
SortExpression="Question" />
<asp:TemplateField HeaderText="Answer">
<ItemTemplate>
<% If Not IsPostBack Then%>
<asp:DropDownList ID="DropDownList1" runat="server" DataSourceID="SqlAnswer"
DataTextField="torf" DataValueField="torf">
</asp:DropDownList>
<%Else%>
<asp:Label ID="Label1" runat="server" Text=""></asp:Label>
<%End If%>
</ItemTemplate>
</asp:TemplateField>
</Columns>
<FooterStyle BackColor="#99CCCC" ForeColor="#003399" />
<PagerStyle BackColor="#99CCCC" ForeColor="#003399" HorizontalAlign="Left" />
<SelectedRowStyle BackColor="#009999" Font-Bold="True" ForeColor="#CCFF99" />
<HeaderStyle BackColor="#003399" Font-Bold="True" ForeColor="#CCCCFF" />
</asp:GridView>
The preceding code loads 10 rows, 2 columns (column1:Question | column2-[DropDownLists]:Answer). I'm a freshman when it comes to development, so if you need additional info let me know.

Here is how I handled it:
created a page level private variable to hold our list of correct/incorrect values
Private Answers as New List(Of Boolean) 'creates a strongly typed list of t/f values
in Page.Load
if IsPostBack then
'iterate through each row
for each gridRow As GridViewRow in GridView1.Rows
'get the selected value for this answer
Dim answer as string = CType(gridRow.FindControl("DropDownList1"),DropDownList).SelectedValue
'add this answer to the list
Answers.Add(IsCorrectAnswer(answer))
next
end if
the IsCorrectAnswer function determines whether or not the answer given for this question is correct and returns a Boolean value. You would need to write this function to suit your needs.
in Button1.Click handler
'rowCounter will act as an index to the answers
Dim rowCounter as Integer = 0
For Each gridRow as GridViewRow in GridView1.Rows
'grid_RowDataBound handles the binding of single row
grid_RowDataBound(gridRow, rowCounter)
rowCounter+=1
Next
finally
Private Sub grid_RowDataBound(gridRow as GridViewRow, rowCounter as Integer)
'make the dropdown invisible
CType(gridRow.FindControl("DropDownList1"),DropDownList).Visible = False
'because we'll be acting on two properties of the label, I shove it in a variable first for ease of access
Dim label As Label = CType(gridRow.FindControl("Label1"),Label)
'set the label to visible
label.Visible = True
'set the text
label.Text = Iif(Answers(rowCounter),"Correct", "Incorrect")
End Sub
Someone probably has a cleaner solution than this, but I do know this works, at least as far as I understand the issue you are facing.
Good luck

What about binding the Visible attribute to Page.IsPostBack (note this is in C# since I'm not familiar with the syntax for VB.NET... I'm sure something similar would work):
<ItemTemplate>
<asp:DropDownList ID="DropDownList1" runat="server" DataSourceID="SqlAnswer"
DataTextField="torf" DataValueField="torf" Visible='<%# !Page.IsPostBack %/>></asp:DropDownList>
<asp:Label ID="Label1" runat="server" Text="" Visible='<%# Page.IsPostBack %/></asp:Label>
</ItemTemplate>

check the RowDataBound event of the gridview object. It takes two parameters:
(byval sender as Object, byval e as GridViewRowEventArgs). With it, you can set the value of the label in each row to 'Correct' or 'Incorrect' on the postback.
see the example at
http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.gridview.rowdatabound.aspx
for more information
as for getting the answers into a variable, here are a couple of options
1) check viewstate. It has some overhead, so be cautious with it.
2) save the data to a Session object

Related

How to check for duplicate entries in a gridview and prevent them

I am relatively new to programming and I am having trouble figuring out how to detect and prevent a user from entering in duplicate values into a gridview I have containing multiple controls. Currently I am using a foreach loop to loop through the controls that contain the values that cannot be duplicates and it works for the most part, however when I edit the first record in a table and try to "update" the record, even if I haven't changed any values, I receive a null reference exception and I don't know why. I have attached any pertinent code below and would appreciate any and all help to come to a solution.
Please excuse the long post, I am trying to be as thorough as possible without adding irrelevant information.
To start I have a grid view with a series of controls in it allowing a user to enter information into our database. The gridview and the relevant controls can be seen below.
<asp:GridView ID="GridView2" runat="server"
EmptyDataText="No Miscellaneous" AutoGenerateColumns="False"
ShowHeaderWhenEmpty="True" DataKeyNames="RecID"
ShowFooter="True" DataSourceID="SqlDataSource7" Width="95%"
OnRowDataBound="OnRowDataBound" >
<EmptyDataTemplate>
<asp:DropDownList ID="ddlMiscEquipmentNew" runat="server" DataSourceID="SqlDataSourcePartEquipment" DataValueField="RecID"
DataTextField="EquipmentPart" AppendDataBoundItems="True" AutoPostBack="True" Width="220px" Height="20px" style="margin-left: 70px;" >
<asp:ListItem Value="0">---SELECT---</asp:ListItem>
</asp:DropDownList>
<asp:DropDownList ID="ddlMiscNew" runat="server" DataSourceID="SqlDataSourceMisc" DataValueField="RecID" DataTextField="Description"
AppendDataBoundItems="true" Width="220px" Height="20px" AutoPostBack="True" OnSelectedIndexChanged="ddlMiscNew_SelectedIndexChanged"
OnDataBound="ddlMiscNew_DataBinding">
<asp:ListItem Value="0">---SELECT---</asp:ListItem>
</asp:DropDownList>
<asp:TextBox ID="txtMiscQuantityNew" runat="server" Width="95px" Style="margin-left: 68px;" AutoPostBack="True" OnTextChanged="txtMiscQuantityNew_TextChanged" />
<asp:TextBox ID="txtMiscCostNew" runat="server" Width="95px" Text="0" ReadOnly="True" />
<asp:TextBox ID="txtMiscRequestedNew" runat="server" Width="95px" Text="0" ReadOnly="True" />
<asp:TextBox ID="txtMiscTaxNew" runat="server" Width="95px" Text="0" />
<asp:Button ID="InsertDetail" runat="server" CommandName="InsertDetail" Height="25px" Text="Add Detail" Width="85px" UseSubmitBehavior="True" />
</EmptyDataTemplate>
<AlternatingRowStyle BackColor="#CCCCCC" />
<Columns>
<asp:CommandField ShowEditButton="True" FooterText="Add -->" ShowDeleteButton="true" HeaderStyle-Width="70px" />
<asp:BoundField DataField="RecID" HeaderText="RecID" SortExpression="RecID" ReadOnly="True" Visible="False" />
<asp:TemplateField HeaderText="Miscellaneous" ItemStyle-HorizontalAlign="center">
<ItemTemplate>
<asp:Label ID="lblMiscEquipmentAdd" Text='<%# Bind("EquipmentType") %>' runat="server" Width="220px" />
<asp:Label ID="lblMiscDescriptionAdd" Text='<%# Bind("MiscDescription") %>' runat="server" Width="220px" />
</ItemTemplate>
<EditItemTemplate>
<asp:DropDownList ID="ddlMiscEquipmentEdit" runat="server" DataSourceID="SqlDataSourcePartEquipment" DataValueField="RecID" DataTextField="EquipmentPart" SelectedValue='<%# Eval("EquipmentID") %>'
AppendDataBoundItems="True" AutoPostBack="True" Width="220px" Height="20px">
<asp:ListItem Value="0">---SELECT---</asp:ListItem>
</asp:DropDownList>
<asp:DropDownList ID="ddlMiscEdit" runat="server" DataSourceID="SqlDataSourceMisc" DataValueField="RecID" DataTextField="Description" SelectedValue='<%# Eval("MiscType") %>' AppendDataBoundItems="True" Width="220px" Height="20px" AutoPostBack="True" OnSelectedIndexChanged="ddlMiscEdit_SelectedIndexChanged">
<asp:ListItem Value="0">---SELECT---</asp:ListItem>
</asp:DropDownList>
</EditItemTemplate>
<FooterTemplate>
<asp:DropDownList ID="ddlMiscEquipmentInsert" runat="server" DataSourceID="SqlDataSourcePartEquipment" DataValueField="RecID" DataTextField="EquipmentPart"
AppendDataBoundItems="True" Width="220px" Height="20px" style="margin-left: 29px;">
<asp:ListItem Value="0">---SELECT---</asp:ListItem>
</asp:DropDownList>
<asp:DropDownList ID="ddlMiscInsert" runat="server" DataSourceID="SqlDataSourceMisc" DataValueField="RecID" DataTextField="Description" AppendDataBoundItems="True" Width="220px" Height="20px" AutoPostBack="True" OnSelectedIndexChanged="ddlMiscInsert_SelectedIndexChanged">
<asp:ListItem Value="0">---SELECT---</asp:ListItem>
</asp:DropDownList>
</FooterTemplate>
</asp:TemplateField>
There are more controls in the gridview but these are where the problem lies. As you can see there are a number of drop down lists and textboxes, the controls in question are the drop down list "ddlMiscEdit" in the edit item template and the label "lblMiscDescriptionAdd". When the user makes a selection in the aforementioned ddl and proceeds to hit the insert detail button at the end of each row it adds the values to the gridview, and in turn they are added to my database through a row command event. This works as intended, the problem I run into is when a user trys to edit the first row of the gridview and changes the value for ddlMiscEdit, it triggers a null reference exception for the label lblMiscDescriptionAdd.
In order to detect duplicates I have a For Each loop in the selectedIndexChanged event for the ddlMiscEdit drop down as shown below.
For Each row In GridView2.Rows
Dim miscdescription As Label = (CType(row.FindControl("lblMiscDescriptionAdd"), Label))
If miscdescription.Text = ddlMiscEdit.SelectedItem.Text Then
lblErrorMisc.Text = "Miscellaneous items can only be claimed once"
Return
Else
lblErrorMisc.Text = ""
Return
End If
Next
I am thinking this exception has to do with the fact that when the user clicks edit it activates the drop down list and hides the label, thus when it comes to the loop "lblMiscDescriptionAdd" doesn't exist in that specific context.
I have also tried putting the foreach loop in the row command event for the gridview in the "Update" commandName section, which leads to the same issue but in a different place. When this loop is in the Update section mentioned above, instead of receiving this exception on the first record of the gridview I receive it in the second/third/fourth record, basically everything except the first record.
I use that same foreach loop in the InsertDetail command and it works as intended preventing duplicate values from being entered into the gridview, the exception only appears when I am trying to edit a given row depending on where I put the loop.
I apologize for the long post but I don't really know how to simplify my issue beyond what I have done here. I am pretty new to programming so I don't know if I could go about doing this in a better/more efficient manner, but any help/critiques would be greatly appreciated as I am at a loss as to how to fix this problem. Thank you in advance for any effort on your part for helping me solve this.
To make a long story short?
Don't try and process the GV.What happens if other users are adding data. You may well not have re-loaded the grid with updated data.
In your save code for the one record, simple hit/pull/query the main database and check for the record. That way you not messing around all day trying to process and pull values out of the GV.
Always try to think in terms of data management, not trying to use the actual GV as some kind of database - it only for display, and selecting the data to edit, but to check for duplicates, then simple query the database for that information and check if such records already exist.
For example, I have a simple GV with some hotels. (that working example you can try here: http://www.kallal.ca/Website11/WebForm2 ).
So, I have a add button to allow adding of a new reocrd. And I also have a "save" button. It would be in that save button that I simple add code to check say if the hotel name already exists in the database, and not allow that addition.
And what happens with edits? If a user edits existing, and again changes a name to some existing value, then again you have to deal with code to test/check that case.
So, be it adding, or editing, you need to check for that duplicate.
The code thus becomes rather simple. My save button code thus with code to check for the hotel already existing could/would look/work like this:
Protected Sub cmdSave_ServerClick(sender As Object, e As EventArgs)
' check for dupliate hotel name when saving or adding new reocrd.
Dim intPK As Integer = ViewState("PK")
Dim strSQL As String =
"SELECT HotelName FROM tblHotelsA
WHERE HotelName = #HotelName AND ID <> #ID"
Dim cmdSQL As New SqlCommand(strSQL)
cmdSQL.Parameters.Add("#HotelName", SqlDbType.NVarChar).Value = txtHotel.Text
cmdSQL.Parameters.Add("#ID", SqlDbType.NVarChar).Value = intPK
Dim rstDataCheck As DataTable = MyrstP(cmdSQL)
If rstDataCheck.Rows.Count > 0 Then
' we have a duplicate hotel, don't allow adding!
' code here to inform user that this is a duplicate
' hotel and you can't add it.
Me.MyCustomMessage.InnerText = "Can't add this hotel - duplicate"
Exit Sub
End If
' code here to save the edit back to database
strSQL = "SELECT * FROM tblHotelsA WHERE ID = " & intPK
Using conn = New SqlConnection(My.Settings.TEST4)
Using cmdSQL2 = New SqlCommand(strSQL, conn)
conn.Open()
Dim da As New SqlDataAdapter(cmdSQL2)
Dim daU As New SqlCommandBuilder(da)
Dim rstData As New DataTable
rstData.Load(cmdSQL2.ExecuteReader)
With rstData.Rows(0)
.Item("HotelName") = txtHotel.Text
.Item("FirstName") = tFN.Text
.Item("LastName") = tLN.Text
.Item("City") = tCity.Text
.Item("Province") = tProvince.Text
.Item("Description") = txtNotes.Text
.Item("Active") = chkActive.Checked
.Item("Balcony") = chkBalcony.Checked
.Item("Smoking") = chkSmoking.Checked
End With
da.Update(rstData)
End Using
End Using
LoadGrid() ' refrsh grid to show any new edits
ShowGrid(True) ' hide edit area, show grid
End Sub
So, in action, then the above will look like this.
We will edit the record and try to create a duplicate.

Filter gridview with OnChecked Event

I have a gridview loaded from a database with a checkbox in the first column. What I want is that when clicking on any row the grid will be filtered to show only the rows in which the value of the second cell is the same as the one I clicked on (having the option to select more rows with that value). Removing the checks he would reload all the information again. I tried doing it using the OnChecked event, but due to AutoPostBack it filters but loses the value of the checkbox checked. What is the best way to do what I want? I hope I have been explicit. Thank you
asp:GridView ID="gridview" runat="server" AutoGenerateColumns="false" DataKeyNames="Id">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:CheckBox ID="chkRow" OnCheckedChanged="chkRow_CheckedChanged" AutoPostBack="true" runat="server" />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Document" ItemStyle-Width="10%" HeaderText="Documento" />
<asp:BoundField DataField="Entidad" ItemStyle-Width="55%" HeaderText="Entidad" />
</Columns>
For Each row As GridViewRow In gvEncomendasPendentes.Rows
If row.RowType = DataControlRowType.DataRow Then
Dim chkRow As CheckBox = TryCast(row.Cells(0).FindControl("chkRow"), CheckBox)
If chkRow.Checked Then
'Bind with filter
End If
End If
Next
Does the table display the entire dataset at all times? If so, I'd check into client-side filtering (JavaScript). Not having the round trip to the server/database would make your page a lot more responsive.
But assuming that's not the case, and to actually answer the posted question, here's how I'd approach it in the VB.Net Code. Try changing the Change Event like this:
Protected Sub chkRow_CheckedChanged (sender as object, e As EventArgs)
Dim chk As CheckBox = DirectCast(sender, CheckBox)
dim grv As GridRowView = DirectCast(chk.NamingContainer, GridRowView)
dim hdn AS HiddenField = grv.FindControl("hdnFieldWithValue")
If chk.Checked then
AddValueToFilerList(hdn.Value)
Else
RemoveValueFromFilterList(hdn.Value)
End If
BindGridView()
End Sub
Your way does work, but that has a Log(N) time on processing, as it needs to look at every row in the grid. The above way will KNOW which checkbox was checked, and deal with it appropriately.
And this small change to the markup:
<asp:hiddenField runat='server' id='hdnFilterList' />
<asp:GridView ID="gridview" runat="server" AutoGenerateColumns="false" DataKeyNames="Id">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:CheckBox ID="chkRow" OnCheckedChanged="chkRow_CheckedChanged" AutoPostBack="true" runat="server" checked='<%# CheckValueInFilterList(Eval("Entidad")) %>' />
<asp:hiddenField id="hdnFieldWithValue" runat="server" value='<%# Eval("Entidad")' />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Document" ItemStyle-Width="10%" HeaderText="Documento" />
<asp:BoundField DataField="Entidad" ItemStyle-Width="55%" HeaderText="Entidad" />
</Columns>
You'll also a function something like this:
Protected Function CheckValueInFilterList(input as string) as boolean
Return MyListOfFilterItems.Contains(input)
End Function
The purpose of that would be to see if the input is contained in your list of filters you're applying to your query and returning true/false depending on that.
Which is directly related to why you're not retaining a check in the checkboxes between postbacks. Since you're rebinding the grid, the existing form state is thrown away in favor of the values from the bind. Since your markup lacked a setter for the checkbox's checked state in the databind, it always resorted to false.
I also added a hidden field to the Markup which would contain the filter list as some type of serialized string (JSON, XML, etc). I'd read it from that field on the Page Load where IsPostBack is True, and always write the list to it in the PreRender event. The point is to just have that passed back and forth through the posted form state so that you can remember them between postbacks.
Maintaining that list of filters in a control outside of the gridview as described does add a bit of overhead in the data that is sent to/from the server on roundtrips. But I wouldn't expect that to a large amount, and would expect a preformance increase due to not having to iterate through the entire gridview item list on every postback.

OnCheckChanged not Firing when Unchecked

I've googled this topic as much as i could, ive found similar topics around and even on stackoverflow but none were able to resolve my issue. I have a nested gridview bound to a sqldatasource that populates a few fields as well as a checkbox field from a 'bits' field in the database. The checkboxes populate correctly from the database (checked vs. unchecked) but my 'OnCheckedChanged' event only fires when i check a checkbox but does NOT fire when it is unchecked, the page just goes through its post back. I have 'EnableViewState' set to true on the master page, local page, and on the control as well as auto post back. What i need to do is update the DB with the new value of the checkbox (checked vs unchecked) and reload. Please let me know if anyone has suggestions.
Protected Sub Gridview3_OnCheckedChanged(Sender As Object, e As EventArgs)
Dim checkbox As CheckBox = TryCast(Sender, CheckBox)
Dim gridview3 As GridView = checkbox.Parent.Parent.Parent.Parent
Dim row As GridViewRow = checkbox.Parent.Parent
sql_insert(String.Format("UPDATE [STOREIT2-PORTAL].dbo.AA5VOLUMES SET PROTECTED = '{0}' WHERE GUID = '{1}' AND AGENTID = '{2}'", checkbox.Checked, gridview3.DataKeys(row.RowIndex).Values("GUID").ToString, gridview3.DataKeys(row.RowIndex).Values("AgentID").ToString))
End Sub
<asp:GridView ID="Gridview3" EnableViewState="true" runat="server" AutoGenerateColumns="False" DataKeyNames="AgentID,GUID" OnRowDataBound="GridView3_OnRowDataBound" RowStyle-CssClass ="row" AlternatingRowStyle-CssClass="altrow" RowStyle-HorizontalAlign="Center" CssClass="gvmain"> <AlternatingRowStyle CssClass="altrow"></AlternatingRowStyle>
<columns>
<asp:BoundField DataField="AgentID" HeaderText="AgentID" SortExpression="AgentID" visible="false"/>
<asp:BoundField DataField="GUID" HeaderText="GUID" SortExpression="GUID" visible="false"/>
<asp:BoundField DataField="VolumeName" HeaderText="VolumeName" SortExpression="VolumeName" readonly="true"/>
<asp:BoundField DataField="Label" HeaderText="Label" SortExpression="Label" nulldisplaytext="<i>{No Label}</i>" readonly="true"/>
<asp:BoundField DataField="BaseCount" HeaderText="BaseCount" SortExpression="BaseCount" nulldisplaytext="<i>{No Base Images}</i>" readonly="true"/>
<asp:BoundField DataField="TimeStamp" HeaderText="TimeStamp" SortExpression="TimeStamp" nulldisplaytext="<i>{Volume has no recent snapshots}</i>" readonly="true"/>
<asp:TemplateField headertext="Protected">
<ItemTemplate>
<asp:CheckBox ID="Protected" EnableViewState="true" runat="server" autopostback="true" checked='<%# Eval("Protected")%>' OnCheckedChanged="Gridview3_OnCheckedChanged"/>
</ItemTemplate>
</asp:TemplateField>
</columns>
</asp:GridView>
I'm noticing your code does not have the 'Handles Clause' Maybe this is causing the problem you mention. . .
Handles CheckBox1.CheckedChanged

ASP.Net 4.5 Model Binding Cascading Drop Downs Null Reference (web Forms)

I'm retrofitting an older web forms site with Model Binding using asp.net 4.5.
In a DetailsView I have a drop down list that allows selection of a particular 'client' and another that allows selection of a particular 'project' that belongs to that client. So the drop down for project has to be filtered on the client number and if the user changes the client selection, I want to filter the project list by client number.
I couldn't figure out how to get the SelectedIndexChanged method on the client ddl to fire the Select method for the Project, so I concluded the only way to do it was to filter the project ddl by client number in all cases. I am getting an error message when the client selection is made:
NullReferenceException was Unhandled by User Code
which point in my aspx directly to the ddl for Projects.
This is an abbreviated version of the details view, you can see both the clients ddl and the projects ddl (I am operating in Edit mode):
<asp:DetailsView ID="AdministratorDetailsView" runat="server" AutoGenerateRows="False"
DataKeyNames="AdministratorNumber" ItemType="BusinessLogic.Administrator"
Width="99%"
SelectMethod="AdministratorDetailsView_GetItem"
UpdateMethod="AdministratorDetailsView_UpdateItem"
DeleteMethod="AdministratorDetailsView_DeleteItem"
FieldHeaderStyle-Width="30%" EditRowStyle-Width="99%"
InsertRowStyle-Width="70%" RowStyle-Width="99%" CssClass="admin"
AutoGenerateDeleteButton="true" AutoGenerateEditButton="true" >
<Fields>
<asp:TemplateField HeaderText="AdministratorCode" SortExpression="AdministratorCode">
<EditItemTemplate>
<asp:Label ID="AdministratorCode" Text="<%# Item.AdministratorCode%>" runat="server" />
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="AdministratorCode" Text="<%# Item.AdministratorCode%>" runat="server" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="ClientNumber" SortExpression="ClientNumber">
<EditItemTemplate>
<asp:DropDownList ID="ddClients" runat="server"
AutoPostBack="true"
DataTextField="ClientName" DataValueField="ClientNumber"
ItemType="BusinessLogic.Client"
SelectMethod="ddClients_GetList"
SelectedValue="<%# Item.ClientNumber%>"
OnSelectedIndexChanged="AdministratorDetailsView_ddlClients_SelectedIndexChanged"/>
</EditItemTemplate>
<ItemTemplate>
<asp:DropDownList ID="ddClients" runat="server" Enabled="false"
DataTextField="ClientName" DataValueField="ClientNumber"
ItemType="BusinessLogic.Client"
SelectMethod="ddClients_GetList"
SelectedValue="<%# Item.ClientNumber%>"/>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Projects" >
<EditItemTemplate>
<asp:DropDownList ID="ddProjects" runat="server" AutoPostBack="true"
DataTextField="ProjectName" DataValueField="ProjectNumber"
ItemType="BusinessLogic.Project"
SelectMethod="ddProjects_GetList"
SelectedValue="<%# Item.ProjectNumber%>" />
</EditItemTemplate>
<ItemTemplate>
<asp:DropDownList ID="ddProjects" runat="server" Enabled="false"
DataTextField="ProjectName" DataValueField="ProjectNumber"
ItemType="BusinessLogic.Project"
SelectMethod="ddProjects_GetList"
SelectedValue="<%# Item.ProjectNumber%>"/>
</ItemTemplate>
</asp:TemplateField>
</Fields>
</asp:DetailsView>
There are two SelectMethods, one for each drop down list, and a selectedindexchaneged method for clients:
Public Function ddClients_GetList() As List(Of BusinessLogic.Client)
Dim special As New List(Of Client)
special = CurrentClient.ClientList() 'add whole list
Dim NullClient As New Client()
NullClient.Load(0)
NullClient.ClientName = "<-Not Selected-->"
special.Add(NullClient) 'Had to have a client with 0 in the list since most admins don't have anything but 0 inthis field
Return special
End Function
Public Function ddProjects_GetList(<Control("ddClients")> ByVal ClientNumber As Integer) As List(Of BusinessLogic.Project)
Dim special As New List(Of Project)
If ClientNumber = 0 Then
special = CurrentProject.ProjectList() 'add whole list, refine it if Clients Drop Down selected
Else
special = CurrentProject.ProjectList(ClientNumber) 'add whole list, refine it if Clients Drop Down selected
End If
Dim NullProject As New Project()
NullProject.Load(0)
NullProject.ProjectName = "<-Not Selected-->"
special.Add(NullProject) 'Had to have a Project with 0 in the list since most admins don't have anything but 0 inthis field
Return special
End Function
Protected Sub AdministratorDetailsView_ddlClients_SelectedIndexChanged(sender As Object, e As EventArgs)
Dim ddlProj As DropDownList
ddlProj = AdministratorDetailsView.FindControl("ddProjects")
ddlProj.ItemType = "BusinessLogic.Project"
ddlProj.DataBind()
End Sub
All is well until the user selects a different client which triggers the SelectIndexChanged event and then we get to the DataBind(), where we get the null exception (ddlProj is found).
Need some ideas on how to refresh the projects list based on the new client selection.
How do I force ddProjects to run its SelectMethod again, to avoid the null reference and reload the control?
Turns out this issue is not unique to ModelBinding, but is a General WebForms Issue.
You cannot have cascading dropdowns within another data control where they depend on each other unless you add a bit of code.
In my case, I was trying to bind both dropdowns to the underlying detailsview, and at the same time setting the selected values. Doesn't work because of timing. It seems right until you try to change the selection on the parent dropdown, and then you get null exception on the second because it is not yet loaded. It's all timing. If you try to work around it with saved values, it gets too messy.
My solution is, Bind another item to the underlying details view (you can make it hidden if you wish). Then add some code to keep it in sync with the actual value. DO NOT Bind SelectedValue on the secondary drop down , this is what causes the problem. Manage Selected Value in code using the added field.
<asp:DetailsView ID="AdministratorDetailsView" runat="server" AutoGenerateRows="False"
DataKeyNames="AdministratorNumber" ItemType="BusinessLogic.Administrator"
Width="99%" SelectMethod="AdministratorDetailsView_GetItem" UpdateMethod="AdministratorDetailsView_UpdateItem" DeleteMethod="AdministratorDetailsView_DeleteItem"
FieldHeaderStyle-Width="30%" EditRowStyle-Width="99%" InsertRowStyle-Width="70%" RowStyle-Width="99%" CssClass="admin" AutoGenerateDeleteButton="true" AutoGenerateEditButton="true"
>
<Fields>
<asp:TemplateField HeaderText="Client Name" SortExpression="ClientName">
<EditItemTemplate><asp:DropDownList ID="ddClients" runat="server" AutoPostBack="true" DataTextField="ClientName" DataValueField="ClientNumber" ItemType="BusinessLogic.Client" SelectMethod="ddClients_GetList" SelectedValue="<%# Item.ClientNumber%>" OnDataBound="ddClients_DataBound" OnSelectedIndexChanged="AdministratorDetailsView_ddlClients_SelectedIndexChanged" /> </EditItemTemplate>
<ItemTemplate><asp:DropDownList ID="ddClients" runat="server" Enabled="false" DataTextField="ClientName" DataValueField="ClientNumber" ItemType="BusinessLogic.Client" SelectMethod="ddClients_GetList" SelectedValue="<%# Item.ClientNumber%>"/></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText ="Project Number" >
<EditItemTemplate><asp:Label ID="ProjectNumberLabel" runat="server" Text="<%# BindItem.ProjectNumber%>"></asp:Label></EditItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Project Name" SortExpression="ProjectNumber">
<EditItemTemplate><asp:DropDownList ID="ddProjects" runat="server" AutoPostBack="true" DataTextField="ProjectName" DataValueField="ProjectNumber" ItemType="BusinessLogic.Project" SelectMethod="ddProjects_GetList" OnDataBound="ddProjects_DataBound" OnSelectedIndexChanged="ddProjects_SelectedIndexChanged" />
</EditItemTemplate>
<ItemTemplate><asp:DropDownList ID="ddProjects" runat="server" Enabled="false" DataTextField="ProjectName" DataValueField="ProjectNumber" ItemType="BusinessLogic.Project" SelectMethod="ddProjects_GetList" SelectedValue="<%# Item.ProjectNumber%>" />
</ItemTemplate>
</asp:TemplateField>
</asp:DetailsView>
And in the code behind, not how we set and get the value of the bound projectnumber:
Protected Sub AdministratorDetailsView_ddlClients_SelectedIndexChanged(sender As Object, e As EventArgs)
''If this selection changes we need to change some flags and perhaps theProject ddl
''This causes null exception for Project ddl, can't figure out how to get it to reload. Tried DataBind(), same effect as having a ValueProvider in the parameters to the Select
Dim ProjectNumberLabel As Label = AdministratorDetailsView.FindControl("ProjectNumberLabel")
ProjectNumberLabel.Text = "0" 'Synchronize to no selection
Dim ddProjects As New DropDownList
ddProjects = AdministratorDetailsView.FindControl("ddProjects") 'Have to use Find Control because ddl is buried in DetailsView
ddProjects.SelectedValue = 0 'Cause it to read Select an Item
ddProjects.DataBind() 'Rebinding it causes the SelectMethod to run.
End Sub
Protected Sub ddProjects_DataBound(sender As Object, e As EventArgs)
'Because of the problem with cascading references, we had to remove the SelectValue declaration from ddProjects.
'So we must make sure that once theddProjects is databound, it points to the selected value from the data
Dim ddlProjects As DropDownList
ddlProjects = AdministratorDetailsView.FindControl("ddProjects")
Dim ProjectNumberLabel As Label = AdministratorDetailsView.FindControl("ProjectNumberLabel")
ddlProjects.SelectedValue = CInt(ProjectNumberLabel.Text)
End Sub
Hope someone else sees this as useful. I couldn't find it anywhere.
Great posting, as I faced a similar conundrum and it made me think. I have done this before outside of the model-binding approach, and the solution is essentially the same, as described. I used a ListView, and added the method:
OnItemDataBound="lvWhatever_ItemDataBound"
to fire an event from which to go and get the relevant ID from another bound control in the list view data item:
if (e.Item.ItemType == ListViewItemType.DataItem)
{
// get the ID from from another control in this item record: lblItemID
ListViewDataItem lvdi = (ListViewDataItem)e.Item;
if (lvdi != null)
{
Label iID = (Label)lvdi.FindControl("lblItemID");
if (iID != null)
where lblItemID.Text contains the relevant ID. Then from that I can query to get the relevant list, and manually bind it to the DDL. Brilliant - thanks very much.

Gridview is not updating inside of UpdatePanel

I understand that this is a very common issue, I have been reading documentation on it for days, and I am about to pull my hair out over this issue.
BACKGROUND
I have multiple Gridviews inside of an UpdatePanel. What is happening, is someone is importing an Excel spreadsheet, I am using OpenXML to shread the data and store it in a VB.NET datatable object. I then run all of that data through a custom validation (based on DB information) and then spit out the exceptions (errors) that occur into a Gridview depending on the exception. the max number is 4 Gridviews in one UpdatePanel (each Gridview has it's own functionality). There are two Gridviews that I am using a button to do an action using the data contained in the Gridview. Those two buttons are also located in the Update Panel, just underneath the corresponding Gridviews. Each Gridview is wrapped in an AJAX Collapsible Panel Extender.
Now, when the user clicks on the button, I have a click event in the code behind where I take the information and depending on the exception that occurred, Update or Insert the DB. I loop through the rows, and if no error occurs, I call the datatable.ImportRow and pass the current row to my "Ready" table. I use a ScriptManager.RegisterStartupScript in order to display an alert box letting them know if any errors occurred. Then, I rebind the exception table and the "Ready" table. I have tried just adding an AsyncPostbackTrigger, I have attempted simply calling udpMain.Update() in the code behind, and tried both options of setting the UpdatePanel's UpdateMode property to "Always" and "Conditional".
HTML
<asp:UpdatePanel ID="udpMain" runat="server" UpdateMode="Always">
<ContentTemplate>
<asp:Panel ID="pnlOwnershipDetailsHead" runat="server" Visible="false">
<div class="windHeader" style="cursor: pointer">
<asp:Label id="lblOwnershipDetails" runat="server">Ownership Exceptions</asp:Label>
<asp:ImageButton id="btnOwnershipHead" runat="server"/>
</div>
</asp:Panel>
<asp:Panel ID="pnlOwnershipDetailsBody" runat="server" Visible="false" CssClass="pnl">
<asp:GridView ID="gvOwnershipDetails" runat="server" CssClass="wind" CellPadding="5" AutoGenerateColumns="false">
<HeaderStyle CssClass="windHeader" />
<Columns>
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:BoundField DataField="Serial Number" HeaderText="Serial Number" />
<asp:BoundField DataField="Facility" HeaderText="Facility" />
<asp:BoundField DataField="Department" HeaderText="Department" />
<asp:BoundField DataField="EmpID" HeaderText="EmpID" />
<asp:BoundField DataField="Configuration" HeaderText="Config" />
<asp:BoundField DataField="Error" HeaderText="Errors" />
<asp:TemplateField>
<HeaderTemplate>
<asp:CheckBox ID="chkHeader" ToolTip="Select All" runat="server" onclick="changeAllCheckBoxes(this)" />
</HeaderTemplate>
<ItemTemplate>
<asp:CheckBox ID="chkItem" runat="server" ToolTip="Select this item" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:Button ID="btnOwnershipDetails" Text="Change Information" runat="server" CssClass="btn editBtn" />
<ajax:ConfirmButtonExtender ID="cbeOwnershipDetails" runat="server" TargetControlID="btnOwnershipDetails"
ConfirmText="Are you sure you would like to change the ownership information for the selected items?"
OnClientCancel="CancelClick" />
</asp:Panel>
</ContentTemplate>
<asp:UpdatePanel>
CODE BEHIND
Protected Sub btnOwnershipDetails_Click(sender As Object, e As System.EventArgs) Handles btnOwnershipDetails.Click
Dim importdata As New ImportData
Dim ownershipdt As Data.DataTable = Session("ownershipdt")
Dim finalimportdt As Data.DataTable = Session("finalimportdt")
Dim existsError As Boolean = False
For Each Row As Data.DataRow In ownershipdt.Rows
Dim i As Integer = 0
Dim cb As CheckBox = CType(gvOwnershipDetails.Rows(i).Cells(7).Controls(1), CheckBox)
If cb.Checked Then
If importdata.CheckEmpExists(Row("EmpID").ToString) And importdata.CheckSiteExists(Row("Facility").ToString) And importdata.CheckDeptExists(Row("Department").ToString) Then
importdata.UpdateDBOwnership(Row("Serial Number").ToString, ClientInfo.GetEmpID(Row("EmpID").ToString), ClientInfo.GetSiteID(Row("Facility").ToString), ClientInfo.GetDeptID(Row("Department").ToString), _
Row("Description").ToString, Row("Configuration").ToString, portalUser.EmployeeText)
finalimportdt.ImportRow(Row)
Else
existsError = True
End If
End If
i += 1
Next
If existsError = False Then 'Show alert box
ScriptManager.RegisterStartupScript(udpMain, udpMain.GetType(), "alert", "alert('You have changed the ownership information for the selected rows.')", True)
Else
ScriptManager.RegisterStartupScript(udpMain, udpMain.GetType(), "alert", "alert('There was an issue changing ownership to all of the selected rows.')", True)
End If
bindGV(gvOwnershipDetails, ownershipdt)
bindGV(gvImportDetails, finalimportdt)
'udpMain.Update()
Session("ownershipdt") = ownershipdt
Session("finalimportdt") = finalimportdt
btnEmail.Enabled = True
End Sub
Put your panel code between ContentTemplate under asp:UpdatePanel
<asp:UpdatePanel>
<ContentTemplate>
</ContentTemplate>
</asp:UpdatePanel>
Finally! I Have found the answer! https://sites.google.com/site/arlen4mysite/full-postback-for-templatef
Or a single line of code in the Page_Load() does the same thing
ScriptManager.GetCurrent(this).RegisterPostBackControl(btnOwnershipDetails);

Resources