【问题标题】:Can I make this macro more efficient or faster?我可以让这个宏更高效或更快吗?
【发布时间】:2022-04-08 03:49:20
【问题描述】:

我是编码新手。这个宏运行缓慢,我希望有人可以帮我清理它。在此先感谢您的帮助。

我开发了代码来更新我公司的“呼叫路由器”工作表,其中包含从外部来源购买的新线索。潜在客户以原始格式在名为 Fresh Agents Leads 的工作表中提供给我们。一旦“新代理线索”表被复制到包含“呼叫路由器”工作表的“MSS 呼叫路由主列表”文件中,宏会减少原始数据,以便消除我们不使用的部分。然后它重新格式化剩余的内容以匹配旧呼叫路由器工作表的格式并将两者合并。然后它将新的主表重命名为呼叫路由器。

代码旨在从包含新鲜代理潜在客户表的工作簿中开始。指示用户在执行代码之前在桌面上打开 Fresh Agents Leads File 和 MSS Call Routing Master List。

    Sheets("Fresh Agent Leads").Select
    Sheets("Fresh Agent Leads").Copy After:=Workbooks( _
        "MSS Call Routing Master List.xlsx").Sheets(1)
    Columns("F:F").Select
    Selection.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
    Range("A1").Select
    Selection.Copy
    Columns("F:F").Select
    ActiveSheet.Paste
    Columns("A:A").Select
    Application.CutCopyMode = False
    Selection.Delete Shift:=xlToLeft
    Columns("C:C").Select
    Selection.Delete Shift:=xlToLeft
    Columns("E:E").Select
    Selection.Delete Shift:=xlToLeft
    Selection.Delete Shift:=xlToLeft
    Columns("G:S").Select
    Selection.Delete Shift:=xlToLeft
    Rows("1:1").Select
    Selection.Delete Shift:=xlUp
    Columns("C:C").Select
    Selection.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
    Range("C1").Select
    ActiveCell.FormulaR1C1 = "=CONCATENATE(RIGHT(RC[1],4))"
    Range("C1").Select
    Selection.AutoFill Destination:=Range("C1:C1048575")
    Range("C1:C1048575").Select

    Sheets("Call Router").Select
    Rows("1:1").Select
    Selection.Copy
    Sheets("Fresh Agent Leads").Select
    Rows("1:1").Select
    Selection.Insert Shift:=xlDown
    Application.CutCopyMode = False
    Application.Run "PERSONAL.xlsb!MergeIdenticalWorksheets"
    Columns("C:C").Select
    Selection.NumberFormat = "0000"
    Range("A:A,B:B,F:F").Select
    Range("F1").Activate
    Selection.ColumnWidth = 14
    Columns("E:E").Select
    Selection.ColumnWidth = 25
    Columns("C:C").Select
    Selection.ColumnWidth = 8.29
    With Selection
        .HorizontalAlignment = xlCenter
        .VerticalAlignment = xlBottom
        .WrapText = False
        .Orientation = 0
        .AddIndent = False
        .IndentLevel = 0
        .ShrinkToFit = False
        .ReadingOrder = xlContext
        .MergeCells = False
    End With
    Rows("1:1").Select
    Selection.RowHeight = 30
    With Selection
        .VerticalAlignment = xlBottom
        .WrapText = True
        .Orientation = 0
        .AddIndent = False
        .ShrinkToFit = False
        .ReadingOrder = xlContext
        .MergeCells = False
    End With
    With Selection.Interior
        .Pattern = xlSolid
        .PatternColorIndex = xlAutomatic
        .ThemeColor = xlThemeColorLight1
        .TintAndShade = 0
        .PatternTintAndShade = 0
    End With
    With Selection.Font
        .ThemeColor = xlThemeColorDark1
        .TintAndShade = 0
    End With
    Columns("D:D").Select
    Selection.EntireColumn.Hidden = True
    Range("E2").Select
    ActiveWindow.FreezePanes = True
    Sheets(Array("Call Router", "Fresh Agent Leads")).Select
    Sheets("Call Router").Activate
    ActiveWindow.SelectedSheets.Delete
    Sheets("Master").Select
    Sheets("Master").Name = "Call Router"
    Range("C23").Select
    ActiveSheet.Protect DrawingObjects:=True, Contents:=True, Scenarios:=True
    ActiveWorkbook.Save
End Sub

【问题讨论】:

  • 你试过看看这条线有多贵吗? Selection.AutoFill Destination:=Range("C1:C1048575")
  • 我同意 J Trana 的观点。但是你的大部分代码都需要改进。我没有看到你的Variable 声明,但我猜你没有声明。此外,您使用 Select 过多会影响代码的速度。尝试适应here 所讨论的关于避免选择和提高代码速度的内容。如果您遇到无法理解的事情,请更新您的问题并粘贴您的代码、错误和/或遇到的困难。
  • 一个更有代表性的标题会有很大帮助。
  • L42 是对的。此外,您要避免引用 ActiveSheet 对象。要么用ThisWorkBook.Sheets("SheetNameHere") 准确地告诉它去哪里,要么如果你在循环,首先声明它。就自动填充部分而言,您是否尝试过查找如何找到最后一行? ThisWorkbook.Sheets("Reference").Range("A:A").Rows.Count.End(xlUp).Row 而不是最后一行可能会加快速度。
  • 目前尚不清楚此代码是在找到Fresh Agent Leads 的工作簿中执行,还是在MSS Call Routing Master List 工作簿中执行。我可以在一开始看到您将 FAL 复制到另一个工作簿,但不清楚这些操作是在源还是目标中执行(即使您说您在处理它们,我们需要在哪个工作簿上工作' 正在发生)。 ;)

标签: vba excel


【解决方案1】:

一些提示:

  1. 尽可能避免使用.Select.Activate。录制宏是 VBA 的一个良好开端,但第一步是离开这些属性提供的“舒适区”。一开始它们很好,但从长远来看,它们必然会产生问题。

  2. 阅读以下“基本”过程:复制/粘贴/插入范围、创建/删除工作表以及确定具有相关数据的工作表或范围的最后行/列。这三个是你最好的朋友。通过背诵这三个,您可以在 Excel VBA 中进行很多操作。

  3. 在 (2) 之后,开始学习如何标注变量和/或对象。撇开行话不谈,这基本上类似于给你正在处理的每一件重要的事情起“昵称”。假设您正在处理 3 张纸。您不想继续引用ThisWorkbook.Sheets("Sheet1") 等等。您更愿意使用Sh1Sh2

  4. 了解如何使用UnionWith 等将类似的程序组合在一起。这与上面的 (1) 密切相关。稍后您将看到一个示例。

  5. Application.ScreenUpdating - Excel VBA 中最好的节省时间的技巧之一。

现在,一些示例:

(1) 避免.Select ||学习使用非常好的.Copy one-liner

这部分...

Range("A1").Select
Selection.Copy
Columns("F:F").Select
ActiveSheet.Paste

...可以简化为:

Range("A1").Copy Range("F:F")

从四行到只有一行。而且它更具可读性。上面的第二个代码 sn-p 基本上是“将 A1 的值复制到整个 F 列”。请注意,这实际上会占用大量内存,就像在 Excel 2010 中一样,您实际上是在使用该命令粘贴到一百万行甚至更多行。最好具体一点,比如Range("F1:F1000")

(2) 将命令捆绑在一起

在“书面”VBA 中将命令捆绑在一起与在宏中执行此操作的方式不同。由于记录了宏,因此一切都基于实时修改。在“书面”VBA 中,您可以指定允许您对多个对象应用单个操作的操作。例如,假设您想删除 A 列和 C 列,同时将所有相关数据向左移动。

录制宏来执行此操作时,您可以同时选择 A 和 C 并同时删除它们。然而,大多数初学者选择安全路径并记录删除列一次一个,这虽然安全,但非常违反直觉。在删除之前同时选择两者是最好的选择。

在书面 VBA 中,上面的第二种方法是一个巨大的禁忌(或者至少,这不是常态)。除非有特定和必要的原因,否则将相似的命令组合在一起是惯例,因为它既能在很大程度上消除错误,又不会占用大量资源。

在您的代码中...

Selection.Delete Shift:=xlToLeft
Columns("C:C").Select
Selection.Delete Shift:=xlToLeft
Columns("E:E").Select
Selection.Delete Shift:=xlToLeft
Selection.Delete Shift:=xlToLeft
Columns("G:S").Select
Selection.Delete Shift:=xlToLeft

... 读起来很痛苦。我们不知道为什么那里有两次删除,我们不确定 S 列中的数据最初在哪里,等等。在这种情况下,提前确定要删除的范围并执行删除是完美的方法。

假设您想从 A、C、E 和 F 到 O 中删除列。如下所示的简洁方法可以快速有效地完成此操作。

Union(Range("A:A"),Range("C:C"),Range("E:E"),Range("F:O")).Delete

Union 是你早期最好的朋友之一。与数学中的集合表示法一样,您指定的范围会一起放入一组范围中,并同时进行操作(在这种情况下,.Deleted 同时)。由于默认移位是向左,我们可以完全删除 Shift:=xlToLeft 行(另一个漂亮的 VBA 事实)。

(3) With - 一件你离不开的东西

此时,您可能会想,在这些范围内执行多个操作会怎样?我们只在多个范围内执行了单个操作,而不是相反。这就是With 的用武之地。在这种情况下,With 将仅用于Ranges,但它几乎可以用于 VBA 中的任何东西。对象、范围、外部应用程序等。我不会对此进行深入研究,但我只想说,使用With 就像在您想要通过几个程序处理的事情上使用锚。

在您的代码中,我们发现...

Columns("C:C").Select
Selection.ColumnWidth = 8.29
With Selection
    .HorizontalAlignment = xlCenter
    .VerticalAlignment = xlBottom
    .WrapText = False
    .Orientation = 0
    .AddIndent = False
    .IndentLevel = 0
    .ShrinkToFit = False
    .ReadingOrder = xlContext
    .MergeCells = False
End With
Rows("1:1").Select
Selection.RowHeight = 30
With Selection
    .VerticalAlignment = xlBottom
    .WrapText = True
    .Orientation = 0
    .AddIndent = False
    .ShrinkToFit = False
    .ReadingOrder = xlContext
    .MergeCells = False
End With

...可以简化为:

With Columns("C:C")
    .ColumnWidth = 8.29
    .HorizontalAlignment = xlCenter
End With
With Rows(1:1)
    .RowHeight = 30
    .WrapText = True
End With

基本上,我们在这里做了两件事。首先,我们锚定在 C 列上,并对其进行了两个操作:设置列宽,然后设置水平对齐。在锚定到 C 列并对其进行修改后,我们将锚点更改为整个第 1 行,并修改其高度并将其设置为将文本换行为单元格宽度。我们将宏块从 24 行减少到只有 8 行。怎么这么简洁? :)

为什么我没有其他线?与前面的示例 (Union) 一样,我们可以处理一些无论如何都是默认值或未修改的行。这些会有例外,但它们会非常少,而且现在有点偏离你的水平。你会到达那里的。

(4) 创建/修改工作表并避免.Activate,以及触摸尺寸

VBA 初学者的一个陷阱是他们经常使用ActiveWorkbookActiveSheet.Activate。这本身并不坏,但也不好。使用起来很方便,但是如果你把它结合到非常复杂的子程序和函数中,会让人头疼。

为了解决这个问题,我们首先考虑对您的对象进行尺寸标注或限定。这是通过首先声明一个关键字然后声明一个数据类型来完成的。我不会进一步深入研究,因为有很多 VBA 教程可供您阅读,所以我只指出一些重要的。

假设您正在处理两个打开工作簿。我们可以为它们中的每一个创建一个“昵称”,这样您就可以引用它们而无需输入整行引用。

Dim SourceWbk As Workbook
Dim TargetWbk As Workbook

上面的两行内容为,“SourceWbk/TargetWbk 是我的昵称,我被定义为一个工作簿,所以我期待被称为工作簿”。现在我们已经为它们创建了维度,我们可以将它们指向它们所代表的含义。

Set SourceWbk = ThisWorkbook
Set TargetWbk = Workbooks("I AM THE MASTER REPORT")

注意这里的“=”。现在,我们基本上已经声明,从现在开始,SourceWbk 将引用包含此代码的工作簿,TargetWbk 将引用 open 名为“我是主报告”的工作簿。现在让我们看看将工作表从SourceWbk 复制到TargetWbk 的简单操作。

SourceWbk.Sheets("Sheet1").Copy After:=TargetWbk.Sheets("Sheet1")

看起来很眼熟?那是因为这与您记录的代码块几乎相同:

Sheets("Fresh Agent Leads").Select
Sheets("Fresh Agent Leads").Copy After:=Workbooks( _
    "MSS Call Routing Master List.xlsx").Sheets(1)

现在,您可以更进一步,自己命名工作表,然后复制它们。示例如下:

Dim FAL As Worksheet 'Expects a worksheet.
Dim LastSheet As Worksheet

Set FAL = SourceWbk.Sheets("Fresh Agent Leads")
Set LastSheet = TargetWbk.Sheets("Sheet1") 'You can use a number index or specific name

FAL.Copy After:=LastSheet

此时的代码已经非常非常简短和可爱了。没有麻烦,您真正需要的唯一努力就是记住“昵称”所指的内容。请注意,您应该将某些特定词用作变量名称。尽可能使其个性化但合理。简单地将工作表命名为 Sh 很好,但它会让你在一个包含 100 个工作表的文件中无处可去,每个工作表都有不同的用途。

(5) Application Trickbook

在 Excel VBA 中,您可以通过一些操作来提高代码效率。毕竟,宏只是一个重复的动作。运行记录或书面的都将带您完成操作。 .Select 将选择特定范围,您会看到它们被选中。 .Activate 或多或少会做同样的事情。 .Copy 将向您展示那些“蚂蚁”以及它们留下的亮点。所有这些都导致代码的视觉执行时间更长且通常草率。这里是ScreenUpdating“技巧”的步骤。

请注意,这并不是真正的把戏。大多数人认为它们是代码中非常重要的部分,但将它们包含在“外行”VBA 模块中仍然很有帮助。最佳实践之一是在子例程的开头设置Application.ScreenUpdating = False,然后在最后将其设置回True

ScreenUpdating 会“冻结”你的屏幕,让一切在你看不见的情况下发生。您不会看到项目被复制或范围被选中。您不会看到正在打开和关闭的已关闭工作簿。虽然这只会在您调用 Excel 时影响它,但它仍然是无价的。

Application 技巧的快速而肮脏的列表(不要将此作为绝对参考!):

  • .ScreenUpdating (False/True):消除False时Excel的视觉更新。复制粘贴或删除行时绝对必要。
  • .Calculation (xlCalculationAutomatic/xlCalculationSemiautomatic/xlCalculationManual):类似于Formulas > Calculation Options功能区功能,将其设置为手动将暂停所有计算。强烈推荐,尤其是当您更新依赖于大量 VLOOKUPINDEX 公式的范围时。
  • .EnableEvents(False/True):禁用触发基于事件的过程。有点高级,但我只想说,如果您在基于事件的更改上有一些自动宏触发,这将暂停它们以支持当前正在运行的宏。

还有很多其他内容,学习其中的大部分内容将符合您的最大利益。 ;)

大结局

这是从您录制的宏中提取的示例代码,它使用了上述所有技术,并考虑了您在宏上执行的过程。 这不是你的全部代码。阅读这个,测试这个,修改这个,你会在一天内进步很多。

Sub RefinedCode()

    Dim SourceWbk As Workbook, TargetWbk As Workbook
    Dim FALSht As Worksheet, FALSht2 As Worksheet, MasterSht As Worksheet

    Application.ScreenUpdating = False 'We won't see the copy-paste and column deletion happening but they will happen. 

    Set SourceWbk = ThisWorkbook
    Set TargetWbk = Workbooks("MSS Call Routing Master List")
    Set FALSht = SourceWbk.Sheets("Fresh Agent Leads")

    With TargetWbk          
        Set MasterSht = .Sheets("Master") 'Basically reads as Set MasterSht = TargetWbk.Sheets("Master")
        FAL.Copy After:= .Sheets(1)
        Set FALSht2 = .Sheets("Fresh Agent Leads")
    End With

    With FALSht2
        Union(.Range("A:A"),.Range("C:C"),.Range("E:O")).Delete
        With .Rows(1)
            .RowHeight = 30
            .WrapText = True
        End With
        .Range("A1").Copy .Range("F1:F100")
    End With

    MasterSht.Name = "Call Router"
    TargetWbk.Save
    SourceWbk.Close

    Application.ScreenUpdating = True 'Return to default setting.

End Sub

希望这会有所帮助。

【讨论】:

  • 对新手学习之旅的巨大贡献。当像你这样的人“手头有太多时间”时,这是一件好事。我总是添加几件事:必须使用Option Explicit 来捕获变量中的拼写错误(并且通常在编译期间获得更好的错误检查);尽可能使用特定类型(而不是变体) - 更快,不要忘记Application.ScreenUpdating = False。仅此一项就可以节省大量速度,尤其是在您更改格式等时。
  • @Floris:实际上,我对将上述内容包括在内感到有些不安。首先,文字太多了。而Option Explicit 对于一个真正的初学者来说有点太吓人了。它会在错误之后指出错误,并且可能会妨碍代码。对于我们大多数人来说,调试就像是 90% 的工作,但对于初学者来说,这是他们最终会遇到的事情。 Application“技巧”,那是另一次了。哈哈哈。但是,是的,让我至少输入.ScreenUpdating,因为这实际上是必需的! :)
  • 嗯,扩展它不仅仅是.ScreenUpdating。 :)
  • 哇! BK201,刚刚在这里看到你的帖子。太感谢了。我将咀嚼你分享了很长一段时间的内容。虽然@Floris 帖子的后半部分需要我一些时间来理解,但我赞同她的“了不起的贡献......”非常感谢。
  • @Thomas:不用担心。如果这有帮助,请接受它作为答案。如果您还有其他问题,请告诉我们。 ;)
【解决方案2】:

我总是使用下面一行

Application.ScreenUpdating = False

关闭屏幕更新,使宏运行得更快。只需将其添加到宏的开头即可。

【讨论】:

  • ...并在末尾添加Application.ScreenUpdating = True。这样,屏幕就会更新。
  • @BK201 在程序结束时自动重新开启。
【解决方案3】:

这里有很棒的帮助。我希望有一种简单的方法可以在更换工作表时停止闪烁......无论如何,Application.ScreenUpdating 并不能完全消除 excel 2013 中的闪烁。

【讨论】:

  • 这应该作为评论发布,而不是答案,因为它实际上并没有回答问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-11-06
  • 2011-11-03
  • 1970-01-01
  • 1970-01-01
  • 2013-11-02
  • 1970-01-01
  • 2014-12-09
相关资源
最近更新 更多