Dynamically changing Textbox's AutoComplete List causes AccessViolationException, any advice?

Qqbt picture Qqbt · Jan 8, 2012 · Viewed 10.3k times · Source

My client wanted to have a textbox in the Customer form of the application, which offers the applicable endings to a started street name. He starts to type a street name and the textbox offers a list of streets which start with the char sequence he typed into the textbox.

I said to myself: thats okay, textboxes have the AutoCompleteCustomSource property and even though a list of common street names will be longer than it could be pre-filled on start, i could just hit a database with a query, populate an AutoCompleteStringCollection and show that to the user.

Now here's the thing: if I make the list populate on every keypress/keydown whatever, the program crashes and throws an AccessViolationException.

I've found out that that's because: The control is in the middle of showing the AutoComplete list when at the same time it is being modified, resulting in the crash.

When you refresh the Autocomplete List, the control is recreated with new pointers. Keyboard and mouse events (KeyPress, MouseOver, MouseLeave, MouseHover) attempt to reference the old control's pointers which are now invalid in memory causing a memory access violation to occur.

The underlying AutoComplete implementation does not allow for changing the AutoComplete candidate list object once it has been set on a window. To allow changing the list, WinForms destroys the Edit control or ComboBox and recreates it. This causes an exception if the underlying control is destroyed while the AutoComplete window is still use it.

I read about this on MSDN, their resolution:

Do not modify the AutoComplete candidate list dynamically during key events.

I've also tried everything from this thread

So how could I make this work, if I insist on offering the applicable street names keypress-by-keypress?

Note: I know that you can do this by creating a custom control and such, but can it be done with just pure coding wizardry?

Answer

competent_tech picture competent_tech · Jan 9, 2012

The way that we solved this issue in our application (where we need to select from possibly 100,000 items) was to bail on the auto-complete functionality and use a combobox instead.

We use the Infragistics combobox, but I suspect that the standard windows one would work as well.

The trick here is to use the combobox itself, in DropDown mode, as the autocomplete list and populate it as the user types.

Here is the logic that we use:

Private m_fOkToUpdateAutoComplete As Boolean
Private m_sLastSearchedFor As String = ""

Private Sub cboName_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles m_cboName.KeyDown
    Try
        ' Catch up and down arrows, and don't change text box if these keys are pressed.
        If e.KeyCode = Keys.Up OrElse e.KeyCode = Keys.Down Then
            m_fOkToUpdateAutoComplete = False
        Else
            m_fOkToUpdateAutoComplete = True
        End If
    Catch theException As Exception
        ' Do something with the exception
    End Try
End Sub


Private Sub cboName_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_cboName.TextChanged
    Try
        If m_fOkToUpdateAutoComplete Then
            With m_cboName
                If .Text.Length >= 4 Then
                    ' Only do a search when the first 4 characters have changed
                    If Not .Text.Substring(0, 4).Equals(m_sLastSearchedFor, StringComparison.InvariantCultureIgnoreCase) Then
                        Dim cSuggestions As StringCollection
                        Dim sError As String = ""

                        ' Record the last 4 characters we searched for
                        m_sLastSearchedFor = .Text.Substring(0, 4)

                        ' And search for those
                        ' Retrieve the suggestions from the database using like statements
                        cSuggestions = GetSuggestions(m_sLastSearchedFor, sError)
                        If cSuggestions IsNot Nothing Then
                            m_cboName.DataSource = cSuggestions
                            ' Let the list catch up. May need to do Thread.Idle here too
                            Application.DoEvents()
                        End If
                    End If
                Else
                    If Not String.IsNullOrEmpty(m_sLastSearchedFor) Then
                        ' Clear the last searched for text
                        m_sLastSearchedFor = ""
                        m_cboName.DataSource = Nothing
                    End If
                End If
            End With
        End If
    Catch theException As Exception
        ' Do something with the exception
    End Try
End Sub

Due to the large number of items, we don't start searching until the user has entered 4 characters, but that is just our implementation.