VBA 从列表中选择项目时出现 Combobox 28 错误

VBA Combobox 28 error when selecting item from the list

我正在尝试使用 Excel 的 ComboBox 对象编写一个特定的搜索(它将分离城市和国家的搜索结果)。

当我使用键盘按钮时,一切都很好,搜索也很完美。

但是,当我尝试使用鼠标按钮 select 下拉列表中的项目时,出现 28 错误“堆栈外 space”。

调试器由于未知原因正在循环并最终停止在

Set destination_short_rng = w_search.Range("Destination_short")

下面提供的 ComboBoxDestinations_Change 子例程行。

如能提供任何有关如何防止此错误发生的提示,我将不胜感激。

Private destination_search_rng As Range
Private destination_short_rng As Range
Private destination_search_col As New Collection

Private Sub ComboBoxDestinations_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    
    Set w_search = Sheets("4c.Travel Costs (Search)")
        
    Set destination_short_rng = w_search.Range("Destination_short")
    
    IsArrowTopDown = (KeyCode = vbKeyUp) Or (KeyCode = vbKeyDown) Or (KeyCode = vbKeyLButton)
    If KeyCode = vbKeyEscape Then UserFormSearchDest.ComboBoxDestinations.list = destination_short_rng.Value

End Sub

Private Sub InitializeDestinationSearchCollection()
    Dim num_rows As Integer
    Dim i As Integer
    num_rows = destination_short_rng.Rows.Count
    Set destination_search_col = Nothing
    For i = 1 To num_rows
        destination_search_col.Add LCase(destination_search_rng.Rows(i).Value)
    Next i
End Sub

Private Function SplitText(Text As String, separator As String) As Variant
    Dim my_array() As String
    Dim i As Integer
    
    my_array = Split(Text, separator)
    For i = LBound(my_array, 1) To UBound(my_array, 1)
        my_array(i) = LTrim(my_array(i))
    Next i
    SplitText = my_array
End Function

Private Function FoundDestination(destinations As Variant, entered_txt As String) As Boolean
    Dim entered_txt_len As Integer
    Dim i As Integer
    entered_txt_len = Len(entered_txt)
    
    FoundDestination = False
    
    For i = LBound(destinations, 1) To UBound(destinations, 1)
        If left(destinations(i), entered_txt_len) = entered_txt Then
            FoundDestination = True
            Exit For
        End If
    Next i
End Function

Private Function LeftDestination(searched_dest As String, entered_txt As String) As Variant
    Dim my_array(1 To 2) As Boolean
    Dim destinations() As String
    Dim cities() As String
    Dim countries() As String
    destinations = SplitText(searched_dest, ",")
    cities = SplitText(destinations(0), "/")
    countries = SplitText(destinations(1), "/")
    my_array(1) = FoundDestination(cities, entered_txt)
    my_array(2) = FoundDestination(countries, entered_txt)
    LeftDestination = my_array
End Function

Private Sub printCollection(txt As String, col As Collection, list As Variant)
    Dim i As Integer
    
    Debug.Print "Entered txt:", txt
    For i = 1 To Application.WorksheetFunction.Min(5, col.Count)
        Debug.Print "List item:", list(i - 1, 0)
        Debug.Print "Collection item:", col.Item(i)
    Next i
End Sub

Private Sub ComboBoxDestinations_Change()

    Dim i As Integer

    Dim txt As String
    Dim entered_txt_len As Integer
    Dim entered_txt As String
    Dim searched_dest As String
    Dim left_cities As Boolean
    Dim left_countries As Boolean

    
    Set w_search = Sheets("4c.Travel Costs (Search)")

    Set destination_short_rng = w_search.Range("Destination_short")
    
    InitializeDestinationSearchCollection

    If Not IsArrowTopDown Then
        With UserFormSearchDest.ComboBoxDestinations
            .list = destination_short_rng.Value
            entered_txt = LCase(.Text)
            If Len(entered_txt) > 0 Then
                
                For i = .ListCount - 1 To 0 Step -1
                    searched_dest = destination_search_col.Item(i + 1)

                    left_cities = LeftDestination(searched_dest, entered_txt)(1)
                    left_countries = LeftDestination(searched_dest, entered_txt)(2)
                    
                    If Not (left_cities) And Not (left_countries) Then
                        .RemoveItem i
                        destination_search_col.Remove (i + 1)
                    End If
                Next i

                Dim last_left_ind As Integer
                Dim is_last_ind_found As Boolean
                is_last_ind_found = False
                For i = .ListCount - 1 To 0 Step -1

                    searched_dest = destination_search_col.Item(i + 1)

                    left_cities = LeftDestination(searched_dest, entered_txt)(1)
                    left_countries = LeftDestination(searched_dest, entered_txt)(2)

                    If left_cities And Not (is_last_ind_found) Then
                        is_last_ind_found = True
                        last_left_ind = i
                    End If

                    If left_countries And Not (left_cities) And is_last_ind_found Then
                        .AddItem pvargItem:=.list(i), pvargIndex:=last_left_ind + 1
                        .RemoveItem i
                        destination_search_col.Add Item:=searched_dest, After:=last_left_ind + 1
                        destination_search_col.Remove (i + 1)
                        last_left_ind = last_left_ind - 1
                    End If
                Next i

                If .ListCount = 0 Then
                    .AddItem "No Results"
                End If
                .DropDown
                .ListRows = Application.WorksheetFunction.Min(ListRowsMax, .ListCount)
            End If
        End With
    End If
End Sub

正如评论中已经写的那样,您的问题是更改事件例程正在递归触发自身。

在用户表单上,没有 built-in 机制来防止这种情况发生,但是您自己可以很容易地做到这一点:

声明一个变量(我更喜欢静态变量,因为它们保留在本地,但您也可以将其声明为全局变量)并检查并设置它,以便在递归调用时立即离开例程。

Private Sub ComboBoxDestinations_Change()
    Static changeRunning As Boolean
    If changeRunning Then Exit Sub

    ... do your magic here...
    changeRunning  = False
End Sub

最后,我通过设置 IsMouseDownIsEsc 变量来解决问题。

Private Sub ComboBoxDestinations_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    IsMouseDown = (Button = vbKeyLButton) Or (Button = vbKeyRButton) Or (Button = vbKeyMButton)
End Sub
Private Sub ComboBoxDestinations_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    Set w_search = Sheets("4c.Travel Costs (Search)")
        
    Set destination_short_rng = w_search.Range("Destination_short")
    IsMouseDown = False
    IsArrowTopDown = (KeyCode = vbKeyUp) Or (KeyCode = vbKeyDown)
    IsEsc = KeyCode = vbKeyEscape
    If IsEsc Then
        UserFormSearchDest.ComboBoxDestinations.Clear
        UserFormSearchDest.ComboBoxDestinations.list = destination_short_rng.Value
    End If
End Sub

并且在 ComboBoxDestinations_Change 子程序中我写了以下逻辑:

If Not IsArrowTopDown And Not IsMouseDown And Not IsEsc Then

Private Sub ComboBoxDestinations_Change()
    On Error GoTo Err
    Dim i As Integer

    Dim txt As String
    Dim entered_txt_len As Integer
    Dim entered_txt As String
    Dim searched_dest As String
    Dim left_cities As Boolean
    Dim left_countries As Boolean

    Set w_search = Sheets("4c.Travel Costs (Search)")

    Set destination_short_rng = w_search.Range("Destination_short")


    InitializeDestinationSearchCollection

    If Not IsArrowTopDown And Not IsMouseDown And Not IsEsc Then
        With UserFormSearchDest.ComboBoxDestinations

但是,在极少数情况下(例如,当用户输入其他文本并单击退格键时)可能会发生无限循环。这就是为什么,我写了 On Error GoTo Err 语句,以便在这种情况下停止事件。

总而言之,搜索现在正在运行。