SelectedIndexChanged annoyance... what am I doing wrong?

SaintJimmy

Member
Joined
Jul 7, 2006
Messages
24
Programming Experience
5-10
I've got this application I'm working on, and there's a little annoyance I'm having with the damned ListView control. Here's basically what I've got:

The application is a client program that downloads data from SQL Server and stores it in a local DataSet. I query a customer table and populate a ListView control (lvCustomerList in the code below) with that data, and I have a panel on the form with several controls that display data related to the customer that is currently selected in the ListView control. The user can make changes to the data, and those changes are stored in the local DataSet.

Now... when the user selects a new customer from the ListView, I do some validation in the SelectedIndexChanged event (which I set up using AddHandler in the Form's Load event) to determine if any data was changed, and if it was, I prompt the user to save or reject the changes... and there's an option to cancel and remain on the customer that was changed so they can double-check their entry if they need to.

My problem comes in when the user selects "Cancel" from the prompt. I get the prompt twice when Cancel is selected, and I think it may have something to do with my changing the selection in the cancel routine, even though I remove the handler before changing selection and then add it back after changing.

And to make matters more confusing... when I use the debugger and step through the event handler, I only get the prompt once. Go figure.

Any thoughts on this? I've put the entire SelectedIndexChanged event below for you guys to check out. It's commented out the ass, so you should be able to make sense of what I'm doing. Let me know if I'm doing something wrong here... I've been racking my brain on this for days.

VB.NET:
    Private Sub lvCustomerList_SelectedIndexChanged(ByVal sender As System.Object, _
        ByVal e As System.EventArgs)
        
        ' Dump some data to the Output window to determine what the **** is going
        ' on here
        Debug.WriteLine("Entered handler...")
        Debug.IndentLevel += 1
        Debug.WriteLine("Selected Index Changed... ")
        Debug.IndentLevel += 1
        If lvCustomerList.SelectedItems.Count > 0 Then
            Debug.WriteLine("Customer Selected: " & _
                DirectCast(lvCustomerList.SelectedItems(0), _
                CustomerListViewItem).GetDataRow.Item("CustomerName").ToString.Trim())
            If Not _currentcustomerrecord Is Nothing Then
                Debug.WriteLine("Previous Customer: " & _
                    _currentcustomerrecord.Item("CustomerName").ToString.Trim())
            Else
                Debug.WriteLine("First Selection after Execution")
            End If
        Else
            Debug.WriteLine("No items selected")
        End If
        Debug.IndentLevel -= 1
        ' There should only be one item selected, since the MultiSelect property
        ' has been turned off.  However, events are also fired when deselecting an item,
        ' so, check that there is a selected item before proceeding
        If lvCustomerList.SelectedItems.Count > 0 Then
            ' We need to check the current customer record for a value before
            ' attempting to validate.  We only need to validate if this is a 
            ' non-Nothing value, since otherwise it is the first selection made
            ' upon running the application.
            If Not _currentcustomerrecord Is Nothing Then
                ' If we make it here, then a customer was already selected before
                ' the index change.  Let's verify that the new selection is actually
                ' a different customer
                ' We need to be able to gain access to the DataRow associated with
                ' the newly selected item, so cast the currently selected item to the
                ' necessary type.
                Dim newitem As CustomerListViewItem = _
                    DirectCast(lvCustomerList.SelectedItems(0), CustomerListViewItem)
                ' Get the customer number for both the previously selected item
                ' and the newly selected item
                Dim PrevItem As String = _
                    _currentcustomerrecord.Item("CustomerNumber").ToString
                Dim NextItem As String = _
                    newitem.GetDataRow.Item("CustomerNumber").ToString
                ' If both items refer to the same customer, then we do not need to
                ' do anything further.  The associated data is still shown in the
                ' customer data panel, and no save prompt is required.
                If PrevItem = NextItem Then
                    Debug.IndentLevel -= 1
                    Debug.WriteLine("Exiting hander...")
                    Exit Sub
                End If
                ' However, if the newly selected item is different, we need to determine
                ' if any data associated with the previously selected customer has
                ' changed.  If it has, let's prompt the user for some saving instructions.
                If CLIDDataSet.HasChanges Then
                    Debug.WriteLine("Records were changed... prompting.")
                    ' We have some changes to deal with.  We need to ask the user what
                    ' to do with them
                    Dim dresult As DialogResult = _
                        MessageBox.Show("Save changes to " & _
                        _currentcustomerrecord.Item("CustomerName").ToString.Trim() & _
                        "?", "Save changes?", MessageBoxButtons.YesNoCancel, _
                        MessageBoxIcon.Question)
                    Select Case dresult
                        Case DialogResult.OK
                            ' The user wants to save the data, use DataAdapters to
                            ' merge changes to the database on the server.
                            Debug.WriteLine("Saving changes...")
                        Case DialogResult.No
                            ' The user wants to discard changes.
                            Debug.WriteLine("Rejecting changes...")
                            CLIDDataSet.RejectChanges()
                        Case DialogResult.Cancel
                            ' The user wants to cancel the prompt.  In this case, we
                            ' want to move the selection back to the previous one.
                            ' This is where it gets tricky.
                            Debug.WriteLine("Cancelled... remain on initial customer")
                            ' We want to make sure that when we move the selection, we
                            ' don't raise another index change event, so temporarily
                            ' remove the SelectedIndexChanged handler
                            RemoveHandler lvCustomerList.SelectedIndexChanged, _
                                          AddressOf lvCustomerList_SelectedIndexChanged
                            Debug.WriteLine("Killed selection handler...")
                            ' Now we cancel the selection on the newly selected item
                            Debug.Write("Deselected new item...")
                            Debug.WriteLine(newitem.GetDataRow.Item("CustomerName").ToString.Trim)
                            newitem.Selected = False
                            ' And set selection back to the previously selected item.
                            ' To do this, we need to iterate through all items until
                            ' we find the one with an underlying DataRow that matches
                            ' the current customer record.
                            For Each itm As ListViewItem In lvCustomerList.Items
                                ' We need to cast to our inherited item class so we
                                ' can access the DataRow
                                Dim castitm As CustomerListViewItem = _
                                    DirectCast(itm, CustomerListViewItem)
                                If castitm.GetDataRow.Equals(_currentcustomerrecord) Then
                                    ' We've found our item.  Select it, and make sure
                                    ' the list is scrolled so that it's visible
                                    Debug.Write("Select initial item... ")
                                    Debug.WriteLine(castitm.GetDataRow.Item("CustomerName").ToString.Trim)
                                    castitm.Selected = True
                                    castitm.EnsureVisible()
                                    ' There will only be one, so no need to check the
                                    ' others.
                                    Exit For
                                End If
                            Next
                            ' Now our selections should be correct.  Add the handler
                            ' back so that subsequent selection changes will be handled
                            Debug.WriteLine("Reapplying handler...")
                            AddHandler lvCustomerList.SelectedIndexChanged, _
                                       AddressOf lvCustomerList_SelectedIndexChanged
                            ' The data panel should still be populated with data for
                            ' the customer that is now re-selected. No other processing
                            ' needed.
                            Debug.IndentLevel -= 1
                            Debug.WriteLine("Exiting handler...")
                            Exit Sub
                    End Select
                End If
            End If
 
            btnIPAddRemove.Enabled = True
            Dim custitm As CustomerListViewItem
            custitm = DirectCast(lvCustomerList.SelectedItems(0), CustomerListViewItem)
            _currentcustomerrecord = custitm.GetDataRow()
            AppStatus.Text = "Current selection:  " & _
                custitm.GetDataRow.Item("CustomerName").ToString
            Debug.WriteLine("Populating customer data for " & _
                custitm.GetDataRow.Item("CustomerName").ToString.Trim)
            ' Populate the "Network Info" (Notes) textbox
            UpdateNotes(custitm.GetDataRow)
            ' Update the contents of the Contact Info panel
            UpdateContacts(custitm.GetDataRow)
            ' Update the contents of the IP Info panel
            UpdateIPInfo(custitm.GetDataRow)
            ' Update the contents of the Antenna Info panel
            UpdateAntennaInfo(custitm.GetDataRow)
        Else
            ' If there is no customer selected, we need to disable the add and 
            ' remove buttons for IP address and MAC address.
            If _currentcustomerrecord Is Nothing Then
                btnIPAddRemove.Enabled = False
                btnAddMAC.Enabled = False
                btnRemoveMAC.Enabled = False
            End If
        End If
        Debug.IndentLevel -= 1
        Debug.WriteLine("Exiting handler...")
    End Sub
 
What a truly horrible control! I couldnt find a nice solution despite trying a variaety of things for 4 hours - if your list only contains a single column, use a Listbox, if it needs to contain multiples, consider a grid instead. I posted a couple of times about a listbox (my bad; didnt read the question) and if johnh has soft-deleted them then he might be able to restore them if you choose Listbox instead. For now, they'll stay deleted cause they arent really relevant to this thread
 
Yeah, the ListView sucks

I actually took out the ListView and went to a DataGrid instead, and initially I had the same damn problem... go figure. But with the DataGrid all you have to do is prompt to save from the associated BindingManagerBase object's PositionChanged event, and if cancelled, set the position back to the previous one. This automatically updates the selection in the DataGrid. However, I haven't found a really good way to keep it from screwing up if you select an item, and then choose to sort by a different column.

I'm actually to the point where I'm considering writing my own control from scratch to do exactly what I tell it to.


Here's a thought on the ListView issue though, and I haven't looked into this too thoroughly.... but is there maybe a way to subclass the control to insert the validation logic BEFORE the change event actually happens? Because if there was a way to get a notification that the selected index is ABOUT TO CHANGE, then we'd be able to cancel the change before it actually happens if we so choose.
 
The Form

Sure... why not?

The code's not as tidy as it could be, and it definitely won't be functional, but you should be able to make out what I'm trying to accomplish.

Change the extension to .vb and you should be able to browse it using Visual Studio.
 

Attachments

  • MainForm.txt
    89.9 KB · Views: 41
Back
Top