动态更改文本框的自动完成列表会导致 AccessViolationException,有什么建议吗?

发布于 2024-12-25 07:59:50 字数 1012 浏览 3 评论 0原文

我的客户希望在应用程序的客户表单中有一个文本框,它为开始的街道名称提供适用的结尾。他开始输入街道名称,文本框提供了以他在文本框中输入的字符序列开头的街道列表。

我对自己说:没关系,文本框具有 AutoCompleteCustomSource 属性,即使常见街道名称列表比启动时预填充的长度要长,我也可以使用查询访问数据库,填充 AutoCompleteStringCollection 并显示给用户。

现在事情是这样的:如果我在每次按键/按键时填充列表,程序就会崩溃并抛出 AccessViolationException。

我发现这是因为: 该控件在显示自动完成列表的过程中同时被修改,导致崩溃。

当您刷新自动完成列表时,将使用新指针重新创建控件。键盘和鼠标事件(KeyPress、MouseOver、MouseLeave、MouseHover)尝试引用旧控件的指针,这些指针现在在内存中无效,从而导致发生内存访问冲突。

一旦在窗口上设置了自动完成候选列表对象,底层自动完成实现就不允许更改它。为了允许更改列表,WinForms 销毁编辑控件或组合框并重新创建它。如果基础控件被销毁而自动完成窗口仍在使用它,则会导致异常。

我在 MSDN 上读到了这一点,他们的解决方案是:

不要在关键事件期间动态修改自动完成候选列表。

我还尝试了 中的所有内容这个 线程

那么,如果我坚持逐个按键地提供适用的街道名称,我怎样才能完成这项工作呢?

注意:我知道您可以通过创建自定义控件等来做到这一点,但是可以仅通过纯编码魔法来完成吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

絕版丫頭 2025-01-01 07:59:50

这是可能的!
大约3个小时的搜索,根据这篇文章中的信息我找到了解决方案。
您必须从 AutoCompleteCustomSource (或 ComboBox.Items)中删除几乎所有元素,然后 AddRange() 并最终删除 0-index 项:

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

但是此方法太慢(在自动完成时间内),也许是因为您必须逐个删除元素 -一。
对不起我的英语。

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.

怪异←思 2025-01-01 07:59:50

我们在应用程序中解决这个问题的方法(我们需要从可能的 100,000 个项目中进行选择)是放弃自动完成功能并使用组合框。

我们使用 Infragistics 组合框,但我怀疑标准 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 个字符后才开始搜索,但这只是我们的实现。

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.

倾城花音 2025-01-01 07:59:50

6岁时——这个问题没有提出真正的、或者至少是被接受的答案;所以我将补充我如何克服这个问题的两分钱。

导致此错误的原因是什么?

当您动态更改 AutoCompleteStringCollection() 数据,同时它仍然附加到对象(即文本框)时,就会发生错误,因为 Visual Studio 将无法处理内存中的数据 - 因此在重新分配时,它落入堆中并抛出错误。

解决方法

虽然您可以实现一个系统来捕获这些错误并最终向最终用户隐藏它们;潜在的错误仍然会发生,因此这远非最佳实践。

这里显而易见的答案是放弃动态更改源;尽管这并不总是可能的 - 特别是当应用程序依赖于源更改才能按预期工作时。

每当您需要即时更改源时;您应该在更改源之前放置以下代码。

textbox1.AutoCompleteSource = AutoCompleteSource.None;

使用 AutoCompleteStringCollection() 重新填充源后,您应该将文本框恢复为自定义源;

textbox1.AutoCompleteSource = AutoCompleteSource.CustomSource;

通过这样做;你将阻止错误的发生!

编辑:有时,对于某些用户来说,您可能会发现在重新分配新值之前需要清空自动完成字符串集合 - 这可以通过将其分配给 null 然后重新填充!

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!

蹲墙角沉默 2025-01-01 07:59:50

在按键事件外部创建一个私有变量,用于保存所有 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

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
画骨成沙 2025-01-01 07:59:50

我遇到了同样的问题,直到我发现您必须将 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 '" & 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

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
花开雨落又逢春i 2025-01-01 07:59:50
On general
Dim textme as string

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

On textchange
If textme =text1.text then exit sub
Textme=text1.text
Text1.autocompletecustomesource.clear
Text1.autocompletecustomesource.add ...
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文