【问题标题】:Explanation on How Macro to Rearrange Columns Works Excel VBA宏如何重新排列列的解释 Excel VBA
【发布时间】:2021-07-05 21:42:35
【问题描述】:

我从其他地方找到了这段代码,并对其进行了一些修改以满足我的需要,但我并不完全确定它是如何工作的。我想知道它是如何工作的原因是因为我想进一步修改它,但我是 VBA 新手,不明白 Resize、Index 和 Evaluate 函数如何在这个宏中协同工作。

Sub RearrangeColumns()
    Dim newColumnOrder As Variant
    newColumnOrder = Array(1, 2, 3, 4, 41, 42, 43, 44, 5, 6, 49, _
           7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, _
           21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, _
           35, 36, 37, 38, 39, 40, 45, 46, 47, 48, 50, 51, 52)
    Range("A1").Resize(Cells.Find("*", , xlFormulas, , xlRows, xlPrevious).Row, UBound(newColumnOrder) + 1) = _
       Application.Index(Cells, Evaluate("ROW(1:" & Cells.Find("*", , xlFormulas, _
       , xlRows, xlPrevious).Row & ")"), newColumnOrder)
End Sub

此外,是否可以修改此代码以使用已使用 VBA 创建的表?我一直在想办法重新排列 ListColumns,但似乎最好的选择是重新排列实际的字母/数字列。

谢谢!

【问题讨论】:

    标签: excel vba


    【解决方案1】:

    写这行的人一定笑得像个疯狂的科学家。它结合了许多功能和方法,可以在一行中执行整个子代码。我将尝试解释每个步骤。

    Range("A1") :从单元格 A1 开始,因此范围可以从这个顶角延伸到表格的其余部分。

    .Resize( :这是将Range("A1") 从单个单元格延伸到包含目标区域的属性。 Resize 有两个参数,行和列,它们决定最终范围的大小。

    Cells.Find("*", , xlFormulas, , xlRows, xlPrevious).Row :此.Find 方法在Cells 中搜索,这是工作表中的所有单元格。它正在搜索*,这意味着任何东西。它指定xlPrevious,这意味着它正在向后搜索。但由于它正在搜索所有单元格,它是从右下角到左上角搜索。这是一种快速找到工作表底行的方法。

    .Row :这会从 .Find 的结果中获取第 # 行,这将是工作表的底行。

    UBound(newColumnOrder) + 1 :这会计算数组 newColumnOrder 中的项目数。由于使用Array() 定义的数组从 0 开始,因此您需要在上限加 1 才能获得计数。

    Resize( :回到这个属性,现在我们已经理解了输入参数,我们正在调整 Range("A1") 的大小以从“A1”扩展到等于数组大小的列数 newColumnOrder 并且足够了行以包含工作表的所有非空白单元格。

    = Application.Index( :索引函数返回单个值,但因为我们将此值分配给多单元格范围,VBA 自动将其解释为“对范围内的每个单元格执行此操作”。 VBA 在将公式值应用到范围时也很智能,自动调整引用以跟随当前单元格的地址。这就是函数“迭代”数组newColumnOrder 而不实际循环的方式。

    Cells : Index 的第一个参数。这是 Excel 从中获取值的地方。 Cells 是工作表上的所有单元格。值得注意的是,由于 Cells 也是从“A1”开始的,因此这与我们的目标范围之间没有偏移。如果您将其替换为不同的范围,请确保使用 Index 的其他两个参数来考虑偏移量。

    Evaluate(Index 函数的第二个参数是要从第一个参数中定义的范围返回的值的行号。 Evaluate 方法要求 VBA 解释输入表达式,类似于 Excel 工作表评估输入公式/引用的方式。这用于将作为 Excel 公式的输入字符串转换为值数组。

    "ROW(1:" __ ")":这是 Excel 中的 ROW 函数,它返回输入引用中的行号。由于输入是一个范围而不是单个引用,因此我们得到一个值数组,该数组对应于范围中每个单元格的 ROW #。当 VBA 用 Index 函数的结果填充我们的目标范围时,它会自动“迭代”这个数组。这将在Index 中输入对应于每个单元格当前行# 的行#,因为填充的目标范围和索引范围之间没有偏移。

    Cells.Find("*", , xlFormulas, , xlRows, xlPrevious).Row : 同上,这是工作表最底部单元格的第 # 行。

    newColumnOrderIndex 的第三个参数要求列号。我们输入的是整个数组,而不是输入单个值。与在范围内自动填充公式时单元格引用如何自动移动类似,VBA 在填充我们的范围时迭代数组,并在这样做时直接从中获取列号。所以我们的目标范围的第一列将从数组第一个元素的#列开始填充,第二列将从数组第二个元素的#列开始填充。

    结论:

    这一行说“进入从 A1 开始的范围,一直延伸到数组 newColumnOrder 的大小和工作表的最后填充行。用同一行中的单元格的值重新填充每个单元格,但其列 # 可以在数组 newColumnOrder 中找到,数组的索引是您当前的列号。"

    要回答您关于如何为不同的表编写此代码的第二个问题,我建议您不要编写这样的代码。虽然它可能是代码高尔夫赢家,因为完成任务的行数最少,但它是神秘的,难以阅读或编辑。相反,我会建议类似于以下的代码:

    Sub ReOrderingColumnsExample()
       'Define the desired column order
       Dim newColumnOrder() As Variant
       newColumnOrder = Array(1, 3, 5, 2, 4, 6)
       
       'Define the output range, manually or dynamically
       Dim OutputRange As Range
       Set OutputRange = Sheet1.Range("A1:F20")
       
       'Define the input range, manually or dynamically
       Dim InputRange As Range
       Set InputRange = Sheet1.Range("R34:W53")
       
       'Create an array to hold the columns
       Dim ColArrs() As Variant
       ReDim ColArrs(1 To InputRange.Columns.Count)
       
       'Fill the array with arrays of each column's values
       Dim i As Long
       For i = 1 To InputRange.Columns.Count
           ColArrs(i) = InputRange.Columns(i).Value
       Next i
       
       'Output the arrays into the output range with the column based on newColumnOrder
       For i = 1 To OutputRange.Columns.Count
           OutputRange.Columns(i) = ColArrs(newColumnOrder(i - 1))
       Next i
    End Sub
    

    显着减少混乱,所有部分都可以轻松编辑以适合您的特定参数。

    【讨论】:

    • 我为快速编辑道歉。我只是想确保我所说的内容是 100% 准确且明确说明的。希望我已经做到了。我还没有完成任何编程方面的学术课程,所以我的术语或知识可能会有所欠缺。我只能以我理解的方式描述事物。
    • @BobtheBuilder 感谢您的回复;至于我最近的帖子:它们涵盖了Application.Index 令人惊讶的重组可能性,我只是通过尝试(重新)发现的。因此,您可以构建任何想要的顺序,省略甚至重复其他行或列。在我上面提到的链接中阅读一些有趣的进一步提示;另一个带有预定列表对象标头的示例here
    • @BobtheBuilder 我会避免编写这样的代码,因为它的结构很神秘。如果您只需要重新排列表格,您可以将每一列放入一个数组中,然后将列数组输出回新位置的范围中。这样更容易看到正在发生的事情,也更加灵活。
    • @BobtheBuilder 我在答案中添加了一个示例。
    • 除了您赞成的答案之外的友好提示:imo 列替换应该是OutputRange.Columns(i) = ColArrs(newColumnOrder(i - 1))(而不是OutputRange.Columns(newColumnOrder(i - 1)) = ColArrs(i))来重新排列newColumnOrder 中引用的列。 @Toddleson - 顺便说一句,30k 行 26 列的速度比较导致时间非常相似(OP 2.8 秒,您的帖子约 3.1 秒,更好的可读方法 Reordering columns 需要约 3.2 秒)。
    猜你喜欢
    • 1970-01-01
    • 2010-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-04
    相关资源
    最近更新 更多