动态更改文本框的自动完成列表会导致AccessViolationException,任何建议?

时间:2012-01-08 17:22:36

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

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

我对自己说:没关系,文本框具有AutoCompleteCustomSource属性,即使常见的街道名称列表比启动时预先填充的更长,我只能用查询命中数据库,填充一个AutoCompleteStringCollection并向用户显示。

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

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

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

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

我在MSDN上了解了这一点,他们的决议是:

  

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

我也尝试了this线程

中的所有内容

那么,如果我坚持按键提供适用的街道名称,我怎么能做到这一点?

注意:我知道你可以通过创建一个自定义控件来实现这一点,但只能用纯编码魔法来完成吗?

6 个答案:

答案 0 :(得分:4)

我们在应用程序中解决此问题的方式(我们需要从可能的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个字符之后开始搜索,但这只是我们的实现。

答案 1 :(得分:3)

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

但是这种方法太慢(在自动完成时间内),可能因为你必须逐个删除元素。 抱歉我的英文。

答案 2 :(得分:1)

在您的密钥事件之外创建一个私有变量,该变量将保存所有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设置为无
  • AutoCompleteSource必须是CustomSource

答案 3 :(得分:0)

On general
Dim textme as string

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

答案 4 :(得分:0)

我得到了同样的问题,直到我发现你必须将autocompletesource更改为none,直到你添加了你想要的所有项目,然后在你完成后将其转回到海关来源。这是我在下面使用的代码。请原谅我们构建包装DLL文件以使SQL请求更容易的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 :(得分:0)

半年前 - 这个问题没有真正或至少被接受的答案;所以我将把我的两分钱加在我如何解决这个问题上。

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

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

解决方法

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

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

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

textbox1.AutoCompleteSource = AutoCompleteSource.None;

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

textbox1.AutoCompleteSource = AutoCompleteSource.CustomSource;

通过这样做;你会防止错误发生!

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