在 ComBox 更改后使用时间计算填充 ListBox

Populate ListBox with Time Calculation after ComBox change

提问人:Shiela 提问时间:9/30/2023 最后编辑:Shiela 更新时间:10/5/2023 访问量:236

问:

我这里有一个简单的用户表单,它根据组合框的变化填充列表框。

combobox 中唯一列表的代码:

Private Sub UserForm_Initialize()
    'used this code to get a dynamic combobox unique Task list in Sheet1 Column A
    'but I wonder why there is an extra space after the last item in combobox
    Dim v, e
    With Sheets("Sheet1").Range("A2:A10000")
        v = .value
    End With
    With CreateObject("scripting.dictionary")
        .CompareMode = 1
        For Each e In v
            If Not .Exists(e) Then .Add e, Nothing
        Next
        If .Count Then Me.ComboBox1.List = Application.Transpose(.Keys)
    End With
End Sub

userform1

原始数据更新(在 excel 中添加了 F 列和 G 列) 请不要介意列的排列方式,因为它们是有目的的。

Task     ||ID    ||PARAGRAPH #|| START        ||END       || Month    || Name
Writing  ||4823  ||  1        ||13:00:00      ||13:15:00  || January  || Larry
Reading  ||4823  ||  1        ||13:16:00      ||13:18:00  || February || Larry 
Writing  ||4823  ||  2        ||13:20:00      ||13:30:00  || March    || Larry
Reading  ||4823  ||  2        ||13:31:00      ||13:50:00  || April    || Larry
Writing  ||4824  ||  1        ||14:00:00      ||14:10:00  || October  || Cole
Reading  ||4824  ||  1        ||14:11:00      ||14:14:00  || October  || Cole

原始图像(添加了 F 列和 G 列):image of raw updated

这是我使用当前月份和列名称进行组合框更改的更新期望结果(不需要在 excel 工作表中具有总时间列,只需在列表框中):

Private Sub ComboBox1_Change()
    If ComboBox1.value = "Writing" And Month = current month Then '***
    'if values are present then
    'calculate time (end - start) for Writing rows
    'populate listbox of Writing entries with Total Time Column, Month Column, Name Column
    'no need to populate start and end cols       

    'if there are no values found in Sheet1
    'ListBox1 is just blank

ElseIf ComboBox1.value = "Reading" and Month = current month Then '***
    'if values are present then
    'calculate time (end - start) for Reading rows
    'populate listbox of Reading entries with Total Time Column, Month Column, Name Column
    'no need to populate start and end cols     

    'if there are no values found in Sheet1
    'ListBox1 is just blank
End If
End Sub

更新了 ListBox 所需的写入结果以及当前月份和列名称:

writing outcome

更新了 ListBox 所需的读取结果以及当前月份和列名称:

读数:reading outcome

注意:月份格式为“现在,”mmmm” 筛选中不需要名称。只需要提出清单。

这个问题也有计算,但它是针对唯一 ID 的。当前问题不需要是唯一的,只要列表框根据组合框选择填充即可。此处的答案 1 满足组合框更改后的显示,但列表框中没有时间计算或总计列(结束-开始)。编辑前的答案 2 有时间计算,但没有月份和列名;答案 2 中编辑的答案返回空白列表框。提前致谢。

Excel VBA 组合

评论

0赞 FaneDuru 9/30/2023
如果只提取相应范围的部分,通常数组是最佳方法。但是使用其属性加载列表框不允许放置标头。仅当您有另一个(可能是隐藏的)工作表,或同一工作表中的另一个自由范围时,才能粘贴提取的数组并将结果范围链接到列表......你喜欢什么?List
0赞 Shiela 9/30/2023
@FaneDuru我可以通过标签列表制作静态标题就可以了
0赞 FaneDuru 9/30/2023
那么,如果我要创建一段代码,只能返回两种情况(读取和写入)的必要行,那就足够了吗?
0赞 Shiela 10/1/2023
@FaneDuru我会选择一个较短的代码。下面的答案几乎是完美的。时间计算以小数为单位
1赞 VBasic2008 10/4/2023
只是填充组合框,即摆脱空白项:将 If 语句包装在另一个 If 语句中的循环中:。这里吹毛求疵,但与对象一起使用,在这里有点不合适。此外,动态引用该列。例如,您可以使用 .If Len(e) > 0 Then: If Not .Exists(e) Then .Add e, Empty: End IfNothingWith ThisWorkbook.Sheets("Sheet1").Range("A1").CurrentRegion: With .Resize(.Rows.Count - 1, 1).Offset(1): v = .Value: End With: End With

答:

1赞 Shai Rado 9/30/2023 #1

找到下面的代码,它将仅显示与组合框中选择的标准匹配的相关项目。

代码位于用户窗体模块中的几个子例程中。 代码注释中的详细说明。

代码(已测试)

Option Explicit

Dim LBDataArr                   As Variant
Dim CBDataArr                   As Variant
Dim TaskSelectedStr             As String

' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Private Sub ComboBox1_Change()

TaskSelectedStr = Me.ComboBox1.Value  ' save in User-Form Public variable

' ~~ Call Sub that loads only relevant Array items to List-Box, by matching the searched String in the current Combo-Box ~~~
LoadRelevantItemsToListBox

End Sub

' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Private Sub UserForm_Initialize()

' ~~~ Call Sub that saves the data in "Sheet1" to arrays ~~~
ReadSheet1ToArray
    
   
With Me.ComboBox1
    .Clear
    .List = CBDataArr
End With

' --- populate List-Box ---
With Me.ListBox1
    .Clear
    .List = LBDataArr
End With

End Sub

' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Sub ReadSheet1ToArray()

'======================================================================================================================
' Description : Sub reads all rows in "Sheet1" worksheet, to 'LBDataArr' 2-D array, and unique values of "Task" in
'               'CBDataArr' array.
'
' Caller(s)   : Sub 'UserForm_Initialize' (in this module)
'======================================================================================================================

Dim i As Long, LastRow As Long, ArrIndex As Long, MatchRow As Variant

Application.ScreenUpdating = False

' === Save "Materials DB" worksheet fields in 'TempArr' 2-D Array  ===
With ThisWorkbook.Sheets("Sheet1")
    LastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
    LBDataArr = .Range(.Cells(2, "A"), .Cells(LastRow, "E")).Value  ' save entire "Sheet1" worksheet contents in 2-D array
    
    ReDim CBDataArr(1 To LastRow)
    ArrIndex = 0
    
    ' --- Loop over LB Array and save only unique values in column A ---
    For i = 1 To UBound(LBDataArr, 1)
        If LBDataArr(i, 1) <> "" Then ' include only rows with text in them
            MatchRow = Application.Match(LBDataArr(i, 1), CBDataArr, 0)
            If IsError(MatchRow) Then
                ArrIndex = ArrIndex + 1
                CBDataArr(ArrIndex) = LBDataArr(i, 1)
            End If
        End If
    Next i
  
    If ArrIndex > 0 Then
        ReDim Preserve CBDataArr(1 To ArrIndex)
    End If
End With


Application.ScreenUpdating = True

End Sub

' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Sub LoadRelevantItemsToListBox()

'======================================================================================================================
' Description : Sub scans through the entire 'LBDataArr' (read from "Sheet1" worksheet).
'               Per record tries to match the record's data with the values entered in 'Combo-Box1'
'
' Caller(s)   : ComboBox1_Change (Combo-Box Change event in this Module)
'======================================================================================================================

Dim i As Long, j As Long, LastRow As Long, Col As Long, ArrIndex As Long, MatchRow As Variant
Dim tempArr As Variant


Application.ScreenUpdating = False

' ~~~ Call Sub that reads all "Sheet1" to 'LBDataArr' 2-D Array ~~~
ReadSheet1ToArray

tempArr = LBDataArr ' save contents of array in 'Temp' array

ReDim LBDataArr(1 To UBound(tempArr, 1), 1 To 5) ' reset Array

ArrIndex = 0
   
' === loop through arrays >> faster ===
For i = 1 To UBound(tempArr, 1)
    If tempArr(i, 1) = TaskSelectedStr Then
        ' make sure current 'row' passes searched criteria --> add to Array (and List-Box)
        ArrIndex = ArrIndex + 1
        
        ' - I added the columns one by one in case you want to manipulate the daa in one of the columns -
        LBDataArr(ArrIndex, 1) = tempArr(i, 1) ' TASK
        LBDataArr(ArrIndex, 2) = tempArr(i, 2) ' ID
        LBDataArr(ArrIndex, 3) = tempArr(i, 3) ' PARAGRAPH #
        LBDataArr(ArrIndex, 4) = Format(tempArr(i, 4), "h:mm:ss") ' START
        LBDataArr(ArrIndex, 5) = Format(tempArr(i, 5), "h:mm:ss") ' END
    End If
Next i

' at least 1 record match the criteria in 'Task' Combo-Box
If ArrIndex >= 1 Then
    ' ~~~ Nice TRICK to redim first Dimension of 2-D array ~~~
    tempArr = LBDataArr
    ReDim LBDataArr(1 To ArrIndex, 1 To UBound(LBDataArr, 2))
    For i = 1 To ArrIndex
        For Col = 1 To UBound(LBDataArr, 2)
            LBDataArr(i, Col) = tempArr(i, Col)
        Next Col
    Next i
    
    With Me.ListBox1
        .Clear
        .List = LBDataArr
    End With
     
Else ' no result match
    Me.ListBox1.Clear

    MsgBox "No matches for the criteria entered in 'Task' Combo-Box '", vbCritical, "Search Null"
End If


Application.ScreenUpdating = True

End Sub

评论

0赞 Shiela 9/30/2023
让我马上试试这个回来。
0赞 Shiela 10/1/2023
运行时,它会得到结果,但是,这里的时间都是小数点。
0赞 Shai Rado 10/1/2023
@Shiela检查编辑后的代码,您可以使用 格式化为 TimeFormat(tempArr(i, 4), "h:mm:ss")
0赞 Shiela 10/1/2023
泰。是的,这将加载开始和结束列。但是,我一直在寻找段落 # 列之后的总时间列,就像我在帖子中想要的输出一样。
0赞 Shai Rado 10/1/2023
@Shiela看看你的帖子“只要列表框根据组合框选择填充,当前问题就不需要是唯一的”,上面的代码就是这样做的。
3赞 FaneDuru 10/1/2023 #2

请测试下一个代码:

Private Sub ComboBox1_Change()
  Dim sh As Worksheet, lastR As Long, arr, arrFin, count As Long, i As Long, j As Long, k As Long
  
  Set sh = ActiveSheet 'use here the necessary one
  lastR = sh.Range("A" & sh.rows.count).End(xlUp).Row
  
      arr = sh.Range("A2:F" & lastR).value 'place the range in an array for faster processing
      count = WorksheetFunction.CountIf(sh.Range("A2:A" & lastR), ComboBox1.value) 'count the specific string occurrences
      If count > 0 Then
        ReDim arrFin(1 To count, 1 To UBound(arr, 2) - 1) 'redim the final aray
        For i = 1 To UBound(arr)
            If arr(i, 1) = ComboBox1.value Then
                k = k + 1
                For j = 1 To UBound(arrFin, 2)
                    If j = UBound(arrFin, 2) Then
                        arrFin(k, j) = Format(arr(i, j + 1) - arr(i, j), "hh:mm:ss")
                    Else
                        arrFin(k, j) = arr(i, j)
                    End If
                Next j
            End If
        Next i
      Else
        listBox1.Clear
      End If

     With listBox1
        .ColumnCount = UBound(arrFin, 2)
        .List = arrFin
     End With
End Sub

当然,您必须根据需要设置每列的宽度。

已编辑

下一个版本也将过滤第六列(当前月份)上返回的数组,也会带来第七列。注意在 G:G 列中包含 STRINGS 月份名称

Private Sub ComboBox1_Change()
  Dim sh As Worksheet, lastR As Long, arr, arrFin, count As Long, i As Long, j As Long, k As Long
  Dim arrMonths: arrMonths = Split("January,February,March,April,May,June,July,August,September,October,November,December", ",")
  Dim curMonth As String: curMonth = arrMonths(Month(Date) - 1)
  
  Set sh = ActiveSheet 'use here the necessary one
  lastR = sh.Range("A" & sh.Rows.count).End(xlUp).Row 'last row in the range to be processed
  
      arr = sh.Range("A2:G" & lastR).Value 'place the range in an array for faster processing
      
      'calculate the necessary array elements:
      count = WorksheetFunction.CountIfs(sh.Range("A2:A" & lastR), ComboBox1.Value, sh.Range("F2:F" & lastR), curMonth)

      If count > 0 Then
        ReDim arrFin(1 To count, 1 To UBound(arr, 2) - 1) 'redim the necessary array to keep the rows to be loaded in list box
        For i = 1 To UBound(arr)
            If arr(i, 1) = ComboBox1.Value And arr(i, 6) = curMonth Then
                k = k + 1
                For j = 1 To UBound(arrFin, 2)
                    If j = UBound(arrFin, 2) - 2 Then
                        arrFin(k, j) = Format(arr(i, j + 1) - arr(i, j), "hh:mm:ss")
                    ElseIf j = UBound(arrFin, 2) - 1 Then
                        arrFin(k, UBound(arrFin, 2) - 1) = curMonth
                    ElseIf j = UBound(arrFin, 2) Then
                        arrFin(k, UBound(arrFin, 2)) = arr(i, j + 1)
                    Else
                        arrFin(k, j) = arr(i, j)
                    End If
                Next j
            End If
        Next i
      Else
        ListBox1.Clear: Exit Sub
      End If

      With ListBox1
        .ColumnCount = UBound(arrFin, 2)
        .List = arrFin
      End With
End Sub

如果您需要收集更多列,请将它们放在最后四列之前。根据问题设计一段代码。如果必须返回一个新列,代码可以相对容易地调整,但是如果你想要另一个,然后再返回另外两个,就很难处理了。

如上所述,如果您在最后四个问题之前添加所有这些内容,那么在仅调整下一个问题后,它将运行良好:

  • 将要处理的范围扩展到最后一列 (arr = sh.Range("A2:x" & lastR).value)
  • 确定保留月份名称的列,并在计算的第二部分使用它 (countsh.Range("x2:x" & lastR), curMonth)
  • 在数组处理中使用上述列 NUMBER()。arr(i, colNo) = curMonth

评论

0赞 Shiela 10/1/2023
让我也试试这个,我会回来的
1赞 FaneDuru 10/1/2023
@Shiela 请测试更新后的答案并发送一些反馈。我的意思是,编辑后发布的版本......
1赞 FaneDuru 10/1/2023
@Shiela 请测试最后一个(改编的)版本。当在代码设计后需要不同的问题时,调整其初始逻辑以适应新需求并不是那么简单。通常,代码是在对问题需求的清晰理解的基础上设计的。首先学习是件好事,如何问
1赞 FaneDuru 10/1/2023
您需要简单地更改子名称,以成为必要的事件。我不记得有这样的代码行包含你显示的内容。必须将 comboVal 替换为 Combobox1.Vakue。在两个地方。我在工作表列表框上测试了它。
1赞 FaneDuru 10/1/2023
@Shiela 我调整了代码以执行您需要的操作,只是从上面复制它。我在手机上做到了,我希望我能正确地做到这一点。请在测试后发送一些反馈。我认为是时候尝试理解代码并自己了解问题可能来自哪里以及为什么会出现。因此,您应该养成始终在每个模块顶部使用 Option Explicit 的习惯。这样,VBA 将警告未声明相应的变量...