Динамическое изменение списка автозаполнения текстового поля вызывает исключение AccessViolationException, какие-либо советы?

#c# #winforms #autocomplete #textbox

#c# #.net #vb.net #исключение #нарушение доступа

Вопрос:

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

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

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

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

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

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

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

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

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

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

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

Комментарии:

1. Почему бы вам просто не заполнить список один раз из вашей базы данных, включая каждый элемент? Это жизнеспособно?

2. «создание элемента управления WPF и его использование в вашем проекте WinForms»..

3. @minitech Ну, я думаю, это один из способов сделать это, но загрузка 10 000 строк каждый раз, когда клиент хочет проверить клиента … я не знаю, как я к этому отношусь … возможно, мне придется спросить некоторых профессионалов о загрузке такого количества данных на шоу. Есть вероятность, что клиент даже не захочет редактировать поле, просто скопируйте название улицы или что-то в этом роде. В качестве временной меры это может сработать, но есть ли какой-нибудь способ сделать это так, как мы изначально хотели?

4. @Andris: Ну, попробуй и посмотри, насколько это быстро… Я также думаю, что вы можете привязать его к базе данных и позволить ему делать это самому, но я не могу попробовать это прямо сейчас.

Ответ №1:

Это возможно!!! Около 3 часов поиска и, согласно информации в этом сообщении, я нашел решение. Вам нужно удалить почти все элементы из AutoCompleteCustomSource (или ComboBox .Элементы), затем добавить диапазон () и окончательно удалить элемент с индексом 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. Это работает довольно хорошо. Пришлось добавить проверку того, что в элементах есть дополнительный элемент по сравнению с <your_new_items>, чтобы избежать проблемы, когда элементы были пустыми до моего обновления, после этого проблем больше не возникало.

Ответ №2:

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

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

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

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

 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 символа, но это только наша реализация.

Комментарии:

1. Я только что использовал выпадающий список WinForms с предложениями от competent_tech, и теперь моя форма работает нормально.

Ответ №3:

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

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

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

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

Хотя вы МОГЛИ бы реализовать систему для обнаружения этих ошибок и, в конечном счете, скрыть их от конечного пользователя; основная ошибка все еще возникает, так что это далеко не лучшая практика.

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

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

 textbox1.AutoCompleteSource = AutoCompleteSource.None;
  

После того, как вы повторно заполнили исходный код, используя AutoCompleteStringCollection() then, вы должны вернуть текстовое поле обратно в пользовательский источник;

 textbox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
  

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

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

Комментарии:

1. Я думаю, вы имеете в виду 6 лет, а не 6 месяцев

2. и все еще проблема, как десять лет спустя! >. < Клянусь, я пробовал это, и это не помогло.. Я также каждый раз устанавливал AutoCompleteMode и AutoCompleteCustomSource — возможно, это приносит дополнительные проблемы. Добавлено спасибо, хотя это все еще полезно, чтобы понять, почему возникает ошибка.

3. О Боже .. как насчет этого.. Включите параметр Debug Unmanaged Code для проекта, и проблем не будет, но без него автозаполнение не появляется в первый раз, и нарушение все еще возможно! какого черта? : < Я предполагаю, что опубликованная версия программы будет иметь ту же проблему, поэтому я sol и jwf.

Ответ №4:

Создайте закрытую переменную вне вашего ключевого события, которая будет содержать все ваши данные 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
  

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

  • Режим автозаполнения не должен иметь значения None
  • AutoCompleteSource должен быть в CustomSource

Ответ №5:

У меня была та же проблема, пока я не понял, что вам нужно изменить autocompletesource на none, пока вы не добавите все нужные элементы, а затем вернете его обратно в customsource после завершения. Вот код, который я использовал ниже. Пожалуйста, извините за инструкцию 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 '" amp; TextBox1.Text amp; "%'")
        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
  

Ответ №6:

 On general
Dim textme as string

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

Комментарии:

1. Даже если это может быть правильным ответом, пожалуйста, добавьте комментарий к вашему коду, чтобы повысить вероятность принятия.