问题
I have a ASP.Net web page that includes a nested GridView. Within the nested GridView is a template field with a CheckBox. This AutoPostBack is set to True in order to fire the CheckedChanged event. When you click a CheckBox to change it from the checked to unchecked state it reverts back to being checked.
I need to know what CheckBox is unchecked so that I can remove it from my DataTable that includes currently selected items.
Download entire project .zip here
The code for this problem is in the CreateSchedule.aspx file in the Views folder.
Here is the ASP.Net for the GridView:
<asp:GridView ID="GridView1" runat="server" AllowPaging="False" AutoGenerateColumns="False" Height="25px" Width="250px" CellPadding="4" ForeColor="#333333" GridLines="None">
<AlternatingRowStyle BackColor="White" />
<RowStyle Height="25px" />
<Columns>
<asp:BoundField DataField="CourseName" HeaderText="Course" SortExpression="CourseName" >
<HeaderStyle Width="80px" height="25px"/>
</asp:BoundField>
<asp:TemplateField>
<ItemTemplate>
<img alt = "" style="cursor: pointer" src="images/plus.png" />
<asp:Panel ID="pnlSections" runat="server" Style="display: none">
<!-- Table inside the table -->
<asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False" DataKeyNames="CRN" ForeColor="#333333" GridLines="None" ShowHeader="False">
<Columns>
<asp:TemplateField>
<ItemStyle Width="25px" HorizontalAlign="left"/>
<ItemTemplate>
<asp:CheckBox ID="chkCtrl" runat="server" OnCheckedChanged="CheckedClicked" AutoPostBack="True" />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Days" SortExpression="Days" >
<ItemStyle Width="60px" />
</asp:BoundField>
<asp:BoundField DataField="Time" SortExpression="Time" >
<ItemStyle Width="90px" />
</asp:BoundField>
</Columns>
<RowStyle VerticalAlign="Middle" BackColor="white" />
<SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
<SortedAscendingCellStyle BackColor="#F5F7FB" />
<SortedAscendingHeaderStyle BackColor="#6D95E1" />
<SortedDescendingCellStyle BackColor="#E9EBEF" />
<SortedDescendingHeaderStyle BackColor="#4870BE" />
</asp:GridView>
<asp:SqlDataSource ID="SqlDataSource2" runat="server" ConnectionString="<%$ ConnectionStrings:ConnectionString2 %>" ProviderName="<%$ ConnectionStrings:ConnectionString2.ProviderName %>" SelectCommand="SELECT [CRN], [Days], [Time] FROM [ScheduleOfClasses] WHERE ([Course] = ?)">
<SelectParameters>
<asp:Parameter Name=" Course" Type="String" />
</SelectParameters>
</asp:SqlDataSource>
</asp:Panel>
</ItemTemplate>
</asp:TemplateField>
</Columns>
<EditRowStyle BackColor="#2461BF" />
<FooterStyle BackColor="#04488A" Font-Bold="True" ForeColor="White" />
<HeaderStyle BackColor="#04488A" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="#04488A" ForeColor="White" HorizontalAlign="Center" />
<RowStyle VerticalAlign="Middle" BackColor="white" />
<SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
<SortedAscendingCellStyle BackColor="#F5F7FB" />
<SortedAscendingHeaderStyle BackColor="#6D95E1" />
<SortedDescendingCellStyle BackColor="#E9EBEF" />
<SortedDescendingHeaderStyle BackColor="#4870BE" />
</asp:GridView>
Here is my Page_Load
sub for this page. Essentially if it is not a post back it creates a new DataTable/Xml file and overwrites. If it is a post back (ie. when CheckBox is clicked) it should be checking for any rows that are no longer checked and removing it from the DataTable. I cannot test the logic of it because it gets rechecked.
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
Me.SetDataSource("ACCT")
' Create a new DataTable.
Dim table As DataTable = New DataTable("CourseSelection")
' Declare variables for DataColumn and DataRow objects.
Dim column As DataColumn
' Create new DataColumn, set DataType, ColumnName
' and add to DataTable.
column = New DataColumn()
column.DataType = System.Type.GetType("System.Int32")
column.ColumnName = "crn"
column.AutoIncrement = False
column.ReadOnly = True
column.Unique = True '- same CRN does not conflict
' Add the Column to the DataColumnCollection.
table.Columns.Add(column)
' Create course column.
column = New DataColumn()
column.DataType = System.Type.GetType("System.String")
column.ColumnName = "course"
column.AutoIncrement = False
column.ReadOnly = False
column.Unique = False
' Add the column to the table.
table.Columns.Add(column)
' Create instructor column.
column = New DataColumn()
column.DataType = System.Type.GetType("System.String")
column.ColumnName = "instructor"
column.AutoIncrement = False
column.ReadOnly = False
column.Unique = False
' Add the column to the table.
table.Columns.Add(column)
' Create course time column.
column = New DataColumn()
column.DataType = System.Type.GetType("System.String")
column.ColumnName = "coursetime"
column.AutoIncrement = False
column.ReadOnly = False
column.Unique = False
' Add the column to the table.
table.Columns.Add(column)
' Create course day column.
column = New DataColumn()
column.DataType = System.Type.GetType("System.String")
column.ColumnName = "courseday"
column.AutoIncrement = False
column.ReadOnly = False
column.Unique = False
' Add the column to the table.
table.Columns.Add(column)
' Make the ID column the primary key column, taken out to avoid conflict of unique key
Dim PrimaryKeyColumns(0) As DataColumn
PrimaryKeyColumns(0) = table.Columns("crn")
table.PrimaryKey = PrimaryKeyColumns
Dim dsXML As New DataSet("CourseSelections")
dsXML.Merge(table)
dsXML.WriteXml("c:\temp\dt.xml", XmlWriteMode.WriteSchema)
' Else statement to be worked on
Else
Dim nestedCounter As Integer = 0
Dim rowCounter As Integer = 0
Dim subject As String
Dim ds As New DataSet
' XML File Directory
ds.ReadXml("c:\temp\dt.xml")
Dim table As New DataTable
table = ds.Tables("CourseSelection")
Dim CheckedCRNs As New ArrayList
subject = Left(GridView1.Rows(1).Cells(0).ToString(), 4)
For Each row As GridViewRow In GridView1.Rows
Dim NestedGridView As GridView = GridView1.Rows(rowCounter).FindControl("GridView2")
nestedCounter = 0
For Each r As GridViewRow In NestedGridView.Rows
If r.RowType = DataControlRowType.DataRow Then
Dim chkRow As CheckBox = TryCast(r.Cells(0).FindControl("chkCtrl"), CheckBox)
If chkRow.Checked Then
CheckedCRNs.Add(NestedGridView.DataKeys(nestedCounter).Value.ToString())
End If
End If
nestedCounter = nestedCounter + 1
Next
rowCounter = rowCounter + 1
Next
Dim foundRows() As DataRow
If Not table.Select("course like '" & subject & "*'").Length = CheckedCRNs.Count Then
foundRows = table.Select("course like '" & subject & "*'")
For i = 0 To foundRows.GetUpperBound(0)
If Not CheckedCRNs.Contains(foundRows(i)(0)) Then
table.Rows(i).Delete()
End If
Next i
Dim dsXML As New DataSet("CourseSelections")
dsXML.Merge(table)
dsXML.WriteXml("c:\temp\dt.xml", XmlWriteMode.WriteSchema)
End If
End If
End Sub
CheckedClicked sub:
Protected Sub CheckedClicked(sender As Object, e As EventArgs)
' Variables to be pulled from Database
Dim Course As String = ""
Dim CourseTime As String = ""
Dim CourseDay As String = ""
Dim Instructor As String = ""
' Variables used for position of columns and rows
Dim crn As Integer
Dim nestedCounter As Integer = 0
Dim rowCounter As Integer = 0
Dim ds As New DataSet
' Color variables
Dim colorOfClass As String = ""
colorOfClass = getColorOfClasses(colorPosition)
' XML File Directory
ds.ReadXml("c:\temp\dt.xml")
Dim table As New DataTable
table = ds.Tables("CourseSelection")
For Each row As GridViewRow In GridView1.Rows
Dim NestedGridView As GridView = GridView1.Rows(rowCounter).FindControl("GridView2")
nestedCounter = 0
For Each r As GridViewRow In NestedGridView.Rows
If r.RowType = DataControlRowType.DataRow Then
Dim chkRow As CheckBox = TryCast(r.Cells(0).FindControl("chkCtrl"), CheckBox)
If chkRow.Checked Then
If table.Select("CRN='" & NestedGridView.DataKeys(nestedCounter).Value.ToString() & "'").Length = 0 Then
crn = NestedGridView.DataKeys(nestedCounter).Value.ToString()
End If
End If
End If
nestedCounter = nestedCounter + 1
Next
rowCounter = rowCounter + 1
Next
Dim con As New OleDbConnection
Try
Using con
con.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings("ConnectionString2").ConnectionString
con.Open()
Using cmd = New OleDbCommand
cmd.Connection = con
cmd.CommandText = "SELECT Course, Time, Days, Instructor FROM ScheduleOfClasses WHERE CRN= " & crn
Using oRDR As OleDbDataReader = cmd.ExecuteReader
'Gets columns for each value
While (oRDR.Read)
Course = oRDR.GetValue(0)
CourseTime = oRDR.GetValue(1)
CourseDay = oRDR.GetValue(2)
Instructor = oRDR.GetValue(3)
End While
End Using
con.Close()
End Using
End Using
Catch ex As Exception
Throw New Exception(ex.Message)
Finally
con.Close()
End Try
Dim dRow As DataRow
dRow = table.NewRow()
dRow("crn") = CInt(crn)
dRow("course") = Course
dRow("instructor") = Instructor
dRow("coursetime") = CourseTime
dRow("CourseDay") = CourseDay
table.Rows.Add(dRow)
arrayOfCRNs.Add(crn)
Dim NewDs As New DataSet("CourseSelections")
NewDs.Merge(table)
NewDs.WriteXml("c:\temp\dt.xml", XmlWriteMode.WriteSchema)
Dim cDay As String = "" 'Current day when looping through class days
Dim cCol As Integer = 0 'Current column that is set based on the cDay
Dim StartRow As Integer 'Row in the table that the class starts
Dim ClassLength As Integer = 0 'Number of rows in the table to be colored in based on the length of the class
Dim StartHours As String = Mid(CourseTime, 3, 2) 'The hour of the day that the class starts
Select Case StartHours
Case "00" 'When the class starts on the hour, StartRow is calculated to the correlated hour
StartRow = (CInt(Left(CourseTime, 2)) - 8) * 4
Case "30" 'When the class starts at the bottom of the hour, StartRow is calculated to the correlated _
'hour plus 2 to account for thirty minutes
StartRow = (CInt(Left(CourseTime, 2)) - 8) * 4 + 2
End Select
'Class length of a class is the total minutes divided by 15 minutes per hour, rounded down
ClassLength = Math.Floor((CInt(Mid(CourseTime, 6, 4)) - CInt(Left(CourseTime, 4))) / 15)
'Conversion from Class Length = 7, to 5 cells
ClassLength = ClassLength - ((CInt(Mid(CourseTime, 6, 2)) - CInt(Left(CourseTime, 2))) * 2)
'Number of course days as size of String, checking String for each day Monday-Friday
For n As Integer = 1 To CourseDay.Length
cDay = Mid(CourseDay, n, 1) 'Set cDay to the nth day
Select Case cDay
Case "M"
cCol = 0
Case "T"
cCol = 1
Case "W"
cCol = 2
Case "R"
cCol = 3
Case "F"
cCol = 4
End Select
Dim fillRowCounter As Integer = 1
' Populate the table with the correct data
For cRow As Integer = StartRow To StartRow + ClassLength - 1
'If the current row is divisible by 4 then add one; this is due to the row span of the first column
If cRow Mod 4 = 0 Then cCol = cCol + 1
schedule.Rows(cRow).Cells(cCol).BgColor = colorOfClass
If fillRowCounter = 1 Then schedule.Rows(cRow).Cells(cCol).InnerText = CInt(crn)
If fillRowCounter = 2 Then schedule.Rows(cRow).Cells(cCol).InnerText = Course
If fillRowCounter = 3 Then schedule.Rows(cRow).Cells(cCol).InnerText = Instructor
If fillRowCounter > 3 Then schedule.Rows(cRow).Cells(cCol).InnerText = ""
If cRow Mod 4 = 0 Then cCol = cCol - 1
fillRowCounter = fillRowCounter + 1
Next cRow
Next n
UPDATE
I changed my GridViews to data bind on Page_Load
rather than having a SqlDataSource. However I am still having the same issue as before. Below is my sub that I call to initially bind the GridView.
Protected Sub SetDataSource(Subject As String)
Dim connectionString As String = ConfigurationManager.ConnectionStrings("ConnectionString2").ConnectionString
Dim queryString As String = "SELECT [CourseName] FROM [Courses] WHERE ([SubjectID] = '" & Subject & "')"
Dim DataKeyArray As String() = {"CourseName"}
Dim ds As New DataSet()
Try
' Connect to the database and run the query.
Dim connection As New OleDbConnection(connectionString)
Dim adapter As New OleDbDataAdapter(queryString, connection)
' Fill the DataSet.
adapter.Fill(ds)
Catch ex As Exception
' The connection failed. Display an error message.
'Message.Text = "Unable to connect to the database."
End Try
' Run the query and bind the resulting DataSet
' to the GridView control.
If (ds.Tables.Count > 0) Then
GridView1.DataSource = ds
GridView1.DataKeyNames = DataKeyArray
GridView1.DataBind()
ds.Dispose()
ds.Clear()
Else
'Message.Text = "Unable to connect to the database."
End If
Dim rowCounter As Integer = 0
Dim DataKeyArray2 As String() = {"CRN"}
For Each row As GridViewRow In GridView1.Rows
Dim NestedGridView As GridView = GridView1.Rows(rowCounter).FindControl("GridView2")
Dim gView = GridView1
queryString = "SELECT [CRN], [Days], [Time] FROM [ScheduleOfClasses] WHERE ([Course] = '" & GridView1.DataKeys(rowCounter).Value & "')"
Try
' Connect to the database and run the query.
Dim connection As New OleDbConnection(connectionString)
Dim adapter As New OleDbDataAdapter(queryString, connection)
' Fill the DataSet.
adapter.Fill(ds)
Catch ex As Exception
' The connection failed. Display an error message.
'Message.Text = "Unable to connect to the database."
End Try
' Run the query and bind the resulting DataSet
' to the GridView control.
If (ds.Tables.Count > 0) Then
NestedGridView.DataSource = ds
NestedGridView.DataKeyNames = DataKeyArray2
NestedGridView.DataBind()
ds.Dispose()
ds.Clear()
Else
'Message.Text = "Unable to connect to the database."
End If
rowCounter = rowCounter + 1
Next
End Sub
UPDATE 2
When debugging the Page_Load
event after unchecking a box, the checkbox on the web page appears to be unchecked but chkRow.Checked
has a value of true even though the checkbox appears unchecked on the web page. (chkRow is the checkbox that was being unchecked)
UPDATE 3
I have made somewhat of a breakthrough. I figured out that the problem with the checkboxes is that the nested gridview is located within a panel that collapses and expands using JavaScript. Essentially it is a bug in ASP.Net when a control is disable on Page_Load
and then enabled later. I figured this out because my GridView2
and chkCtrl
are not able to be referenced in VB and therefore the GridView2
is not initialized on Page_Load
. I also figured it out by reading this. The solution mentions creating a hidden field and setting it to the value of the other checkbox but I am unsure of how to do that with my situation.
When I comment out the panel, the checkboxes uncheck as expected and ClickedChanged is fired. However, I need the collapsible panel because it is a lot of data with everything expanded. I was thinking that if I could initially load all of GridViews and then collapse them on Page_Load
but I don't know how to do that either.
Here is the most up to date aspx for my GridView, this time including the code for my panel:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<%'Using plus and minus images to show drop down of classes'%>
<script type="text/javascript">
$("[src*=plus]").live("click", function () {
$(this).closest("tr").after("<tr><td></td><td colspan = '999'>" + $(this).next().html() + "</td></tr>")
$(this).attr("src", "images/minus.png");
});
$("[src*=minus]").live("click", function () {
$(this).attr("src", "images/plus.png");
$(this).closest("tr").next().remove();
});
</script>
<%'Grid View Table'%>
<asp:GridView ID="GridView1" runat="server" AllowPaging="False" AutoGenerateColumns="False" Height="25px" Width="250px" CellPadding="4" ForeColor="#333333" GridLines="None">
<AlternatingRowStyle BackColor="White" />
<RowStyle Height="25px" />
<Columns>
<asp:BoundField DataField="CourseName" HeaderText="Course" SortExpression="CourseName" >
<HeaderStyle Width="80px" height="25px"/>
</asp:BoundField>
<asp:TemplateField>
<ItemTemplate>
<img alt = "" style="cursor: pointer" src="images/plus.png" />
<asp:Panel ID="pnlSections" runat="server" Style="display: none">
<!-- Table inside the table -->
<asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False" DataKeyNames="CRN" ForeColor="#333333" GridLines="None" ShowHeader="False">
<Columns>
<asp:TemplateField>
<ItemStyle Width="25px" HorizontalAlign="left"/>
<ItemTemplate>
<asp:CheckBox ID="chkCtrl" runat="server" OnCheckedChanged="CheckedClicked" AutoPostBack="True" />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Days" SortExpression="Days" >
<ItemStyle Width="60px" />
</asp:BoundField>
<asp:BoundField DataField="Time" SortExpression="Time" >
<ItemStyle Width="90px" />
</asp:BoundField>
</Columns>
<RowStyle VerticalAlign="Middle" BackColor="white" />
<SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
<SortedAscendingCellStyle BackColor="#F5F7FB" />
<SortedAscendingHeaderStyle BackColor="#6D95E1" />
<SortedDescendingCellStyle BackColor="#E9EBEF" />
<SortedDescendingHeaderStyle BackColor="#4870BE" />
</asp:GridView>
<asp:SqlDataSource ID="SqlDataSource2" runat="server" ConnectionString="<%$ ConnectionStrings:ConnectionString2 %>" ProviderName="<%$ ConnectionStrings:ConnectionString2.ProviderName %>" SelectCommand="SELECT [CRN], [Days], [Time] FROM [ScheduleOfClasses] WHERE ([Course] = ?)">
<SelectParameters>
<asp:Parameter Name=" Course" Type="String" />
</SelectParameters>
</asp:SqlDataSource>
</asp:Panel>
</ItemTemplate>
</asp:TemplateField>
</Columns>
<EditRowStyle BackColor="#2461BF" />
<FooterStyle BackColor="#04488A" Font-Bold="True" ForeColor="White" />
<HeaderStyle BackColor="#04488A" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="#04488A" ForeColor="White" HorizontalAlign="Center" />
<RowStyle VerticalAlign="Middle" BackColor="white" />
<SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
<SortedAscendingCellStyle BackColor="#F5F7FB" />
<SortedAscendingHeaderStyle BackColor="#6D95E1" />
<SortedDescendingCellStyle BackColor="#E9EBEF" />
<SortedDescendingHeaderStyle BackColor="#4870BE" />
</asp:GridView>
回答1:
This strongly sounds like you forgot to check if the page is posted back or not in the Page_Load
event where you bind the gridview data. Because of this, you are unintentionally re-binding gridview each time a checkbox is unchecked, which is why it reverts back to the original status (checked).
If Not IsPostBack Then
//bind gridview
Else
//other stuff
End
UPDATE
I think this is a Postback
issue when gridviews are used with SqlDataSource. You may want to check the following resources, and may want to even change your data-binding method:
https://stackoverflow.com/a/18165161/1845408
http://www.codeproject.com/Articles/20520/Workaround-when-SQLDataSource-doesn-t-Maintain-Sel
Hope this helps!
回答2:
Use OnChange instead of OnCheckedChanged.
ASPX
<asp:CheckBox ID="chkCtrl" runat="server" OnChange="CheckedClicked" AutoPostBack="True" />
回答3:
The problem is that the GridView2 that contains the checkboxes is disabled on Page_Load
. It is initially disabled because the gridview is nested inside a collapsible panel. I removed the panel and everything works perfectly.
I may post a different question to inquire about getting the collapsible panel to work.
回答4:
The solution for the collapsible panel to to not do it with javascript. Use an ImageButton and a Panel surrounding the inner Gridview. This keeps the ViewState in sync with what is actually happening.
<asp:TemplateField>
<ItemTemplate>
<asp:ImageButton ID="ExpandButton" runat="server" OnClick="ExpandButton_Click" ImageUrl="~/Images/plus.png" />
</ItemTemplate>
</asp:TemplateField>
Put the panel inside the last column of the outer GridView, after any items. In my case this was Cell[5], after some other buttons. Note this panel closes the cell and row of the outer Gridview and inserts a new row and starts a new cell for the inner GridView, spanning all columns.
<asp:TemplateField HeaderText="Key Options" HeaderStyle-CssClass="text-center bg-info">
<ItemTemplate>
<asp:Button ID="AssignButton" runat="server" CssClass="btn btn-primary btn-xs" Text="Assign Keys" Enabled="false" />
<asp:Button ID="RecallButton" runat="server" CssClass="btn btn-primary btn-xs" Text="Recall Key" Enabled="false" />
<asp:Panel ID="KeysPanel" runat="server" Visible="false">
</td></tr><tr>
<td colspan="999">
<asp:GridView ID="KeysGV" runat="server" AutoGenerateColumns="false" CssClass="table table-striped"
EmptyDataText="No assignments have been made.">
<Columns>
.
.
.
</Columns>
</asp:GridView>
</asp:Panel>
</ItemTemplate>
</asp:TemplateField>
The ExpandButton postback looks like this:
protected void ExpandButton_Click(object sender, EventArgs e)
{
GridViewRow myRow = ((Control)sender).NamingContainer as GridViewRow;
ImageButton expandBtn = sender as ImageButton;
if (expandBtn.ImageUrl == "~/Images/plus.png")
{
expandBtn.ImageUrl = "~/Images/minus.png";
Panel pnl = myRow.Cells[5].Controls[0].FindControl("KeysPanel") as Panel;
pnl.Visible = true;
}
else
{
expandBtn.ImageUrl = "~/Images/plus.png";
Panel pnl = myRow.Cells[5].Controls[0].FindControl("KeysPanel") as Panel;
pnl.Visible = false;
}
}
来源:https://stackoverflow.com/questions/29596907/checkbox-reverts-to-checked-after-being-unchecked