【问题标题】:Dynamically changing Textbox's AutoComplete List causes AccessViolationException, any advice?动态更改文本框的自动完成列表会导致 AccessViolationException,有什么建议吗?
【发布时间】:2012-02-05 11:09:09
【问题描述】:

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

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

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

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

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

一旦在窗口上设置了 AutoComplete 候选列表对象,底层的 AutoComplete 实现就不允许更改它。为了允许更改列表,WinForms 会破坏 Edit 控件或 ComboBox 并重新创建它。如果底层控件在 AutoComplete 窗口仍在使用时被销毁,则会导致异常。

我在MSDN 上读到了这个,他们的决议:

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

我也尝试了this线程中的所有内容

那么,如果我坚持逐个按键提供适用的街道名称,我该怎么做呢?

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

【问题讨论】:

  • 为什么不从数据库中填充一次列表,包括每个项?这可行吗?
  • “制作一个 WPF 控件并在你的 WINFORMs 项目中使用它”..
  • @minitech 好吧,我想这是一种方法,但是每次客户想要检查客户时加载 10,000 行...我不知道我对此有何感受...我可能不得不向这里的一些专业人士询问有关加载这么多数据的问题。客户可能甚至不想编辑该框,只需复制街道名称或其他内容。作为一种临时措施,它可能会起作用,但有什么方法可以按照我们最初想要的方式进行吗?
  • @Andris:好吧,试试看它有多快......我也认为你可以将它绑定到数据库并让它自己完成,但我无法尝试现在。

标签: c# .net vb.net exception access-violation


【解决方案1】:

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

我们使用 Infragistics 组合框,但我怀疑标准窗口也可以。

这里的技巧是在下拉模式下使用组合框本身作为自动完成列表,并在用户键入时填充它。

这是我们使用的逻辑:

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 个字符之前不会开始搜索,但这只是我们的实现。

【讨论】:

  • 我刚刚使用了 WinForms ComboBox 并结合了 Competitive_tech 的建议,我的表单现在工作正常。
【解决方案2】:

有可能!!! 大约 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);
}

但是这种方法太慢了(在自动完成时间),可能是因为你必须一个一个地删除元素。 对不起我的英语。

【讨论】:

  • 这很好用。必须添加一个检查项目是否与 相比有一个额外的项目,以避免在我更新之前项目为空时出现问题,之后不再有麻烦。
【解决方案3】:

在您的关键事件之外创建一个私有变量,该变量将保存您的所有 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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      虽然 6 岁 - 这个问题没有提出真正的或至少被接受的答案;所以我会在如何克服这个问题上加两分钱。

      是什么导致了这个错误?

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

      解决方法

      虽然您可以实施一个系统来捕获这些错误并最终对最终用户隐藏它们;潜在错误仍然存​​在,因此这远非最佳实践。

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

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

      textbox1.AutoCompleteSource = AutoCompleteSource.None;
      

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

      textbox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
      

      通过这样做;您将防止错误发生!

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

      【讨论】:

      • 我认为你的意思是 6 岁,而不是 6 个月大
      • 十年后仍然是一个问题! >.
      • 天哪..这个怎么样..为项目启用调试非托管代码设置没有问题,但是没有它,自动完成不会在第一次弹出,并且违规是还是可以的!有没有搞错? :
      【解决方案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 ...
      

      【讨论】:

      • 即使这可能是正确的答案,也请在您的代码中添加注释以提高被接受的机会。
      猜你喜欢
      • 2020-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-14
      • 2013-05-25
      • 2012-05-21
      • 1970-01-01
      相关资源
      最近更新 更多