Динамически изменяющийся список автозаполнения Textbox вызывает AccessViolationException, любой совет? - PullRequest
9 голосов
/ 08 января 2012

Мой клиент хотел иметь текстовое поле в форме «Клиент» приложения, которое предлагает соответствующие окончания названию улицы. Он начинает вводить название улицы, и в текстовом поле появляется список улиц, которые начинаются с последовательности символов, которую он набрал в текстовом поле.

Я сказал себе: все в порядке, у текстовых полей есть свойство AutoCompleteCustomSource, и хотя список общих названий улиц будет длиннее, чем он может быть предварительно заполнен при запуске, я мог бы просто нажать на базу данных с запросом, заполнить AutoCompleteStringCollection и показать это пользователю.

Теперь вот что: если я сделаю список заполненным при каждом нажатии / нажатии клавиши, программа вылетает и выдает AccessViolationException.

Я узнал, что это потому, что: Элемент управления находится в середине отображения списка автозаполнения, когда он одновременно изменяется, что приводит к сбою.

При обновлении списка автозаполнения элемент управления воссоздается с новыми указателями. События клавиатуры и мыши (KeyPress, MouseOver, MouseLeave, MouseHover) пытаются сослаться на указатели старого элемента управления, которые теперь недопустимы в памяти, вызывая нарушение доступа к памяти.

Базовая реализация автозаполнения не позволяет изменять объект списка кандидатов автозаполнения после его установки в окне. Чтобы разрешить изменение списка, WinForms уничтожает элемент управления Edit или ComboBox и воссоздает его. Это вызывает исключение, если базовый элемент управления уничтожен, пока окно автозаполнения все еще использует его.

Я читал об этом на MSDN , их разрешение:

Не изменяйте список кандидатов автозаполнения динамически во время ключевых событий.

Я также пробовал все из этой темы

Так, как я мог сделать эту работу, если я настаиваю на предложении подходящих названий улиц при нажатии клавиш?

Примечание : Я знаю, что вы можете сделать это, создав пользовательский элемент управления и все такое, , но можно ли это сделать просто с помощью волшебного кода?

Ответы [ 6 ]

4 голосов
/ 09 января 2012

Способ, которым мы решили эту проблему в нашем приложении (где нам нужно выбрать из, возможно, 100 000 элементов), заключался в том, чтобы воспользоваться функцией автозаполнения и использовать вместо этого комбинированный список.

Мы используем комбинированный список Infragistics, но я подозреваю, что стандартные окна также будут работать.

Хитрость здесь в том, чтобы использовать сам комбинированный список в режиме DropDown в качестве списка автозаполнения и заполнять его по мере ввода пользователем.

Вот логика, которую мы используем:

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

Из-за большого количества элементов мы не начинаем поиск, пока пользователь не введет 4 символа, но это только наша реализация.

3 голосов
/ 26 апреля 2013

Это возможно !!! Около 3 часов поиска и по информации в этом посте я нашел решение. Вы должны удалить почти все элементы из AutoCompleteCustomSource (или ComboBox.Items), затем AddRange () и окончательно удалить 0-индексный элемент:

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);
}

Но этот метод слишком медленный (во время автозаполнения), возможно потому, что вам придется удалять элементы по одному. Извините за мой английский.

1 голос
/ 28 апреля 2014

Создайте личную переменную вне вашего ключевого события, которая будет содержать все ваши данные AutoCompleteStringCollection.

Private dataAutocompleteCollection As New AutoCompleteStringCollection()

Затем в вашем ключевом событии сделайте следующее:

        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

Обратите внимание, что следующеесвойства для вашего элемента управления должны быть установлены:

  • AutoCompleteMode не должен быть установлен в None
  • AutoCompleteSource должен быть в CustomSource
0 голосов
/ 09 февраля 2018

В течение полугода - на этот вопрос нет реального или, по крайней мере, принятого ответа; поэтому я добавлю свои два цента о том, как мне преодолеть эту проблему.

Что вызывает эту ошибку?

Ошибка возникает, когда вы динамически изменяете данные AutoCompleteStringCollection () , пока они все еще прикреплены к объекту (то есть к текстовому полю), поскольку Visual Studio не удастся удалить данные из памяти - и, следовательно, когда при переназначении попадает в кучу и выдает ошибку.

Обходной путь

Пока вы МОЖЕТЕ внедрить систему, чтобы перехватывать эти ошибки и в конечном итоге скрывать их от конечного пользователя; основная ошибка все еще возникает, так что это далеко от передовой практики.

Очевидный ответ здесь - отказаться от изменения источника на лету; хотя это не всегда возможно - особенно когда приложение полагается на изменение источника для работы, как предполагалось.

Всякий раз, когда вам нужно изменить источник на лету; Вы должны поместить следующий код перед тем, как смените источник.

textbox1.AutoCompleteSource = AutoCompleteSource.None;

После того, как вы заново заполнили источник с помощью AutoCompleteStringCollection(), вам следует вернуть текстовое поле обратно к пользовательскому источнику;

textbox1.AutoCompleteSource = AutoCompleteSource.CustomSource;

Делая это; Вы предотвратите возникновение ошибки!

РЕДАКТИРОВАТЬ: Иногда для некоторых пользователей может потребоваться очистить коллекцию строк автозавершения перед повторным назначением новых значений - этого можно достичь, назначив его null, затем повторно -populating!

0 голосов
/ 01 июня 2017

У меня возникала та же проблема, пока я не понял, что вам нужно изменить autocompletesource на none, пока вы не добавите все нужные элементы, а затем вернитесь к таможенному источнику после того, как закончите. Вот код, который я использовал ниже. Прошу прощения за оператор SQL, когда мы создаем DLL-файл оболочки, чтобы упростить запросы SQL.

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
0 голосов
/ 01 декабря 2016
On general
Dim textme as string

On textchange
If textme =text1.text then exit sub
Textme=text1.text
Text1.autocompletecustomesource.clear
Text1.autocompletecustomesource.add ...
...