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