【问题标题】:VBA Select Filtered CellsVBA 选择过滤的单元格
【发布时间】:2014-03-14 06:25:37
【问题描述】:

我在工作表中有一个用户窗体。 在这种形式中,我有 6 个组合框。

这个组合框由一个有 6 列的工作表填充,每列进入一个组合框。 选择每个组合框后,我在此表上创建一个过滤器并重新填充下一个。

我给你举个例子,让你更清楚。

我有一张有 6 列的表格:
大陆 |国家 |状态 |城市 |街道 |建筑物名称

此表包含所有这些项目的所有可能组合。 例如: 对于街道上的每一栋建筑,我都有一排所有相同的 5 个第一个项目,最后一个发生变化。

当用户打开表单时,我使用工作表的第一列填充第一个组合框(我执行例程来获取唯一项目)。 当用户更改第一个组合框时,我将过滤器应用于第一列中的工作表,然后用过滤后的工作表填充第二个组合框。

我的问题是如何获得过滤范围。 我正在这样做:

lastRow = Sheets("SIP").Range("A65536").End(xlUp).Row
lFiltered = Sheets("SIP").Range("A2:F" & lastRow).SpecialCells(xlCellTypeVisible).Cells

它工作正常。但是,当我应用过滤器并且它仅隐藏第 10 行时,lFiltered 变量将仅返回到第 9 行。 它在第一个隐藏行上中断,之后不再返回任何行。

我想出的解决方案是对每一行执行一次 foreach 并检查其是否可见,但代码变得非常非常慢。填充每个组合框最多需要 10 秒。

有人知道我该如何解决这个问题吗?

非常感谢。

-- 编辑--

这是代码的重要部分

Dim listaDados As New Collection
Dim comboList() As String
Dim currentValue As String
Dim splitValue() As String
Dim i As Integer
Dim l As Variant
Dim lFiltered As Variant
Dim lastRow As Integer

'Here I found the last row from the table
lastRow = Sheets("SIP").Range("A65536").End(xlUp).Row
'I do this because when the filter filters everything, lastRow = 1 so I got an erros on lFiltered range, it becames Range("A2:F1")
If lastRow < 2 Then
    lastRow = 2
End If
'Here I get an array with all the visible rows from the table -> lFiltered(row, column) = value
lFiltered = Sheets("SIP").Range("A2:F" & lastRow).SpecialCells(xlCellTypeVisible).Cells
'I have duplicated entries, so I insert everything in a Collection, so it only allows me to have one of each value
on error resume next
For i = 1 To UBound(lFiltered)
    currentValue = Trim(lFiltered(i, column))
    If currentValue <> 0 Then
        If currentValue <> "" Then
            'Cammel case the string
            currentValue = UCase(Left(currentValue, 1)) & LCase(Mid(currentValue, 2))
            'Upper case the content in between "( )"
            splitValue = Split(currentValue, "(", 2)
            currentValue = splitValue(0) & "(" & UCase(splitValue(1))
            'Insert new item to the collection
            listaDados.Add Item:=currentValue, Key:=currentValue
        End If
    End If
Next i
i = 1
'Here I copy the collection to an array
ReDim Preserve comboList(0)
comboList(0) = ""
For Each l In listaDados
    ReDim Preserve comboList(i)
    comboList(i) = l
    i = i + 1
Next l

'Here I assign that array to the combobox
formPerda.Controls("cGrupo" & column).List = comboList

--- 编辑---

这是我如何管理代码以按我想要的方式工作。

'Get the last row the filter shows
lastRow = Sheets("SIP").Range("A65536").End(xlUp).Row
'To avoid to get the header of the table
If lastRow < 2 Then
    lastRow = 2
End If
'Get the multiple range showed by the autofilter
Set lFilteredAux = Sheets("SIP").Range("A2:F" & lastRow).Cells.SpecialCells(xlCellTypeVisible)

'Check if there is more than 1 no contiguous areas
If Sheets("SIP").Range(lFilteredAux.Address).Areas.Count > 1 Then
    'If Yes, do a loop through the areas
    For i = 1 To Sheets("SIP").Range(lFilteredAux.Address).Areas.Count
        'And add it to the lFiltered array
        ReDim Preserve lFiltered(i - 1)
        lFiltered(i - 1) = Sheets("SIP").Range(lFilteredAux.Address).Areas(i)
    Next i
Else
    'If there is only one area, it goes the old way
    ReDim lFiltered(0)
    lFiltered(0) = Sheets("SIP").Range(lFilteredAux.Address)
End If

现在我的 lFiltered 数组与我使用的方式有点不同,但我调整了我的 foreach 以像这样工作:

For i = 0 To UBound(lFiltered)
        For j = 1 To UBound(lFiltered(i))
            currentValue = Trim(lFiltered(i)(j, columnNumber))
        next j
next i

非常感谢! =D

【问题讨论】:

  • lFiltered 只返回第 9 行时 lastRow 的值是多少?
  • lastRow 值始终正确。例如,我现在做了一个测试,我几乎隐藏了每一行,除了第 79 行和从 763 到 929 的行。lastRow 的值为 929,但 lFiltered 变量只得到了第 79 行。这就像“范围”方法停在第一个间隙。而且我不知道如何解决它。
  • 您是否将 lFiltered 声明为 Range 变量?该名称令人困惑,因为它暗示它是一个 Long 类型的变量,但这在您的代码中不起作用 - 您需要使用 Gary 的学生所指出的 Set。
  • 我将 lFiltered 变量声明为 Variant。交互后的 lFiltered 值是一个二维数组。第一个是行,第二个是列 例如: lFiltered(1,2) 给出了第一行和第二列中单元格的值。 lFiltered 不返回范围。它是值的数组。你认为我应该这样做不同吗?我将编辑我的问题并添加我的一部分代码来向您展示我在做什么。还有表格的一部分,值在哪里。
  • 我想我明白你在说什么,它开始工作得很好。我更改了我的代码来执行此操作: Set lFilteredAux = Sheets("SIP").Range("A2:F" & ultimaLinha).Cells.SpecialCells(xlCellTypeVisible) 但我不能这样做: lFiltered = Sheets("SIP") .Range(lFilteredAux.Address) 因为当我有时它不起作用,例如: lFilteredAux = "$A$2:$F$4,$A$8:$F$10" 只有当我有一个范围时它才会起作用,而不是多个范围。我可以做些什么来转换我在数组中的范围,以便我可以执行 foreach?

标签: vba excel combobox filter


【解决方案1】:

这里明显的性能下降是您在紧密循环中使用 ReDim Preserve。

解释一下,这个小小的 ReDim Preserve 语句做了很多工作。如果您有一个大小为 4 的数组,并且您将其重新调整为大小为 5,它会分配 5 个空间并复制前一个数组中的 4 个值。如果您随后将其重新调整为大小 6,它会分配 6 个空间并复制前一个数组中的 5 个值。

假设您总共有 1000 个值。在编写代码时,您认为您只是在数组中分配 1000 个元素并将它们复制过来。这将是线性时间,一个 O(n) 操作。实际上,您分配了 1 + 2 + 3 + 4 ... + 1000 个元素 = 分配和复制 500,000,这将是多项式时间,O(n^2) 操作。

解决办法是:

1) 在循环之外,计算出数组的大小,然后仅 ReDim Preserve 一次。

也就是说,首先:

Dim totalSize as Long, i as Long 
For i = 1 To Sheets("SIP").Range(lFilteredAux.Address).Areas.Count
    totalSize += 1
Next I

一旦你有了尺寸:

ReDim Preserve lFiltered(totalSize - 1)
For i = 1 To Sheets("SIP").Range(lFilteredAux.Address).Areas.Count
     lFiltered(i - 1) = Sheets("SIP").Range(lFilteredAux.Address).Areas(i)
Next i

2) 不要使用需要调整大小且其 ReDim Preserve 需要特定大小的数组,而是使用 Collection。在内部,Collection 被实现为类似于链表的东西,这样添加一个项目会在恒定时间内发生(因此每个操作的 O(1) 和插入所有 n 个项目的总时间为 O(n))。

Dim c as New Collection
ReDim Preserve lFiltered(totalSize - 1)
For i = 1 To Sheets("SIP").Range(lFilteredAux.Address).Areas.Count
     c.Add Sheets("SIP").Range(lFilteredAux.Address).Areas(i)
Next i

【讨论】:

  • 老兄,这是个老问题了,哈哈。不过谢谢你的回复,很有道理。这张表仍在使用中,即使我可以让它像我在问题中所说的那样工作,我会按照你所说的那样实现它以使其变得更好。谢谢。
【解决方案2】:

我认为你需要一个 Set

Sub dural()
    lastRow = Sheets("SIP").Range("A65536").End(xlUp).Row
    Set lFiltered = Sheets("SIP").Range("A2:F" & lastRow).Cells.SpecialCells(xlCellTypeVisible)
    MsgBox lFiltered.Address
End Sub

【讨论】:

  • 它没有用。它仍然以同样的方式工作。例如,我做了一个过滤器,我只看到了第 82 行和第 173 行。 lFiltered 变量仅返回第 82 行,即使 lastRow 变量值为 173。
猜你喜欢
  • 2017-08-21
  • 2018-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-02
  • 1970-01-01
  • 2013-06-26
相关资源
最近更新 更多