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

6

10

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?

Varrian answered 8/1, 2012 at 17:22 Comment(4)
Why don't you just populate the list once from your database, including every item? Is that viable?Moonfaced
"making a WPF control and using that in your WINFORMs project"..Hurd
@minitech Well, i guess its one way to do it, but loading 10,000 rows every single time the client wants to check on a customer...i dont know how i feel about that...i may have to ask some professionals here about loading that much data on show. There's a chance that the client doesn't even want to edit the box, just copy the street name or something. As a temporary measure it could work, but is there any way to do it the way we originally wanted?Varrian
@Andris: Well, try it and see how fast it is... I also think you can bind it to a database and let it do it itself, but I'm not able to try that right now.Moonfaced
E
4

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.

Elanaeland answered 9/1, 2012 at 1:55 Comment(1)
I just used the WinForms ComboBox with the suggestions from competent_tech and my form is working fine now.Holmen
D
4

It's Possible!!! About 3 hours searching and according to information in this post I found solution. You have to delete almost all elements from AutoCompleteCustomSource (or ComboBox.Items), Then AddRange() and finaly remove 0-index item:

private void comboBox1_PreviewKeyDown(...) {
        while (comboBox1.Items.Count > 1) {
                 comboBox1.Items.RemoveAt(comboBox1.Items.Count - 1);
        }
        comboBox1.Items.AddRange(<your_new_items>);
        comboBox1.Items.RemoveAt(0);
}

But this method too slow (in autocomplete time), maybe Cause you have to remove elements one-by-one. Sorry for my English.

Dneprodzerzhinsk answered 26/4, 2013 at 13:15 Comment(1)
This works pretty well. Had to add a check that Items has an extra item compared to <your_new_items> to avoid an issue when Items was empty before my update, got no more trouble after that.Miliary
A
2

Whilst 6 years old - this question poses no real, or at least accepted, answer; so I'll add my two cents on how I overcome this problem.

What causes this error?

The error occurs when you dynamically change the AutoCompleteStringCollection() data whilst it is still attached to an object (ie the textbox) as Visual Studio will fail to dispose of the data from memory - and therefore when reassigning, it falls into a heap and throws an error.

The workaround

Whilst you COULD implement a system to catch these errors and ultimately hide them from the end user; the underlying error still occurs, so this is far from best practice.

The obvious answer here is to abandon changing the source on the fly; though this is not always possible - especially when an application relies on a source change to work as intended.

Whenever you need to change the source on the fly; you should place the following code before you change the source.

textbox1.AutoCompleteSource = AutoCompleteSource.None;

Once you have repopulated the source using AutoCompleteStringCollection() then you should revert the text box back to a custom source;

textbox1.AutoCompleteSource = AutoCompleteSource.CustomSource;

By doing this; you will prevent the error from occurring!

EDIT: Occasionally, for some users, you may find that you need to empty the auto complete string collection before re-assigning new values - this can be achieved by assigning it to null then re-populating!

Affiant answered 9/2, 2018 at 16:11 Comment(3)
I think you mean 6 years old, not 6 months oldVarrian
and still a problem like a decade later! >.< I swear I tried this and it didn't help.. I was also setting AutoCompleteMode and AutoCompleteCustomSource each time though- maybe that brings additional grief. Added thanks though, this is still helpful to understand why the error occurs.Anaxagoras
Oh God..How about this.. Enable the Debug Unmanaged Code setting for the project and there is no problem, but without it the AutoComplete doesn't pop up first time round, and the Violation is still possible! what the heck? :< I'm assuming the published version of the program will have the same problem so im sol and jwf.Anaxagoras
S
1

Create a private variable outside your key event that will hold all your AutoCompleteStringCollection data.

Private dataAutocompleteCollection As New AutoCompleteStringCollection()

Then in your key event do the following:

        Dim names As String() = GetSuggested() //get your data from your source

        Dim namesToAdd As New List(Of String)

        For Each name As String In names
            If Not dataAutocompleteCollection.Contains(name) Then
                namesToAdd.Add(name)
            End If
        Next
        dataAutocompleteCollection.AddRange(namesToAdd.ToArray)

        If ctr_Data.AutoCompleteCustomSource.Count = 0 Then
            ctr_Data.AutoCompleteCustomSource = dataAutocompleteCollection 
        End If

Note that the following properties for your control must be set:

  • AutoCompleteMode must not be set to None
  • AutoCompleteSource must be to CustomSource
Sweptwing answered 28/4, 2014 at 8:1 Comment(0)
S
1

I was getting the same issue until I figured out you had to change the autocompletesource to none until you've added all the items you want, and then turn it back to customsource after you're finished. Here is the code I used below. Please excuse the SQL statement as we build a wrapper DLL file to make SQL requests easier.

Private Sub TextBox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
    If TextBox1.Text.Length > 1 Then
        TextBox1.AutoCompleteSource = AutoCompleteSource.None
        Dim TempTable As DataTable = sqlt.ReadDB("select * from AddressBook where FirstName like '" & TextBox1.Text & "%'")
        If TempTable.Rows.Count <> 0 Then
            For Each r As DataRow In TempTable.Rows
                TextBox1.AutoCompleteCustomSource.Add(r.Item("DisplayName").ToString)
            Next
            TextBox1.AutoCompleteSource = AutoCompleteSource.CustomSource
        End If
    End If
End Sub
Stirling answered 1/6, 2017 at 15:41 Comment(0)
M
0
On general
Dim textme as string

On textchange
If textme =text1.text then exit sub
Textme=text1.text
Text1.autocompletecustomesource.clear
Text1.autocompletecustomesource.add ...
Mcnair answered 1/12, 2016 at 17:21 Comment(1)
Even if this could possibly be the correct answer, please add commentary to your code for a better chance of being accepted.Efficacy

© 2022 - 2024 — McMap. All rights reserved.