【问题标题】:Excel 2010 vba code - a cleaner codeExcel 2010 vba 代码 - 更简洁的代码
【发布时间】:2016-08-06 20:51:31
【问题描述】:

我设计了以下代码。我想了解是否可以在ws.cells(Y,2) 中使用命名范围?我试图将代码命名为ws.Range("Name"),但失败了。目的是搜索一列数据,找出特定标准(粗体和

  X = 12
  Y = 4
  Z = 0

 Set ws = Worksheets("Schedule")

Do Until Z = 7
  If ws.Cells(Y, 2).font.Bold = True And ws.Cells(Y, 2) < 1 Then
      ws.Activate
      ws.Cells(Y, 2).Offset(rowOffset:=0, columnOffset:=1).Activate
      ActiveCell.Copy Destination:=Worksheets("Project Status").Cells(X, 3)
      ws.Cells(Y, 2).Offset(rowOffset:=0, columnOffset:=3).Activate
      ActiveCell.Copy Destination:=Worksheets("Project Status").Cells(X, 6)
      ws.Cells(Y, 2).Offset(rowOffset:=0, columnOffset:=4).Activate
      ActiveCell.Copy Destination:=Worksheets("Project Status").Cells(X, 7)
      ws.Cells(Y, 2).Offset(rowOffset:=0, columnOffset:=0).Activate
      ActiveCell.Copy Destination:=Worksheets("Project Status").Cells(X, 8)
      X = X + 1
      Y = Y + 1
      Z = Z + 1
  Else
    Y = Y + 1
  End If
Loop

【问题讨论】:

  • 名称范围不是工作表级别范围,而是工作簿级别范围,因此您不必ws.range("name"),只需range("name")

标签: vba excel excel-2010


【解决方案1】:

名称范围是工作簿级别范围,而不是工作表级别范围。

如果名称范围指的是活动工作表,则ws.range("name") 将起作用。但如果它引用非活动工作表,ws.range("name") 会抛出错误。

因为名称范围是工作簿级别范围,所以您可以简单地使用Range("name")。那么你就不会得到上面的错误了。

P/S:Range("Name") 的另一种写法是[Name],它看起来更干净但缺少智能感知。

【讨论】:

    【解决方案2】:

    以下代码没有解决与*命名范围有关的“子问题”,因为我不理解那部分。

    然而,下面的代码有点短,甚至更容易阅读。此外,在速度方面也做了一些小的改进:

    Option Explicit
    
    Public Sub tmpSO()
    
    Dim WS As Worksheet
    Dim X As Long, Y As Long, Z As Long
    
    X = 12
    Z = 0
    
    Set WS = ThisWorkbook.Worksheets("Schedule")
    
    With Worksheets("Project Status")
        For Y = 4 To WS.Cells(WS.Rows.Count, 2).End(xlUp).Row
            If WS.Cells(Y, 2).Font.Bold And WS.Cells(Y, 2).Value2 < 1 Then
                WS.Cells(Y, 2).Offset(0, 1).Copy Destination:=.Cells(X, 3)
                WS.Cells(Y, 2).Offset(0, 3).Copy Destination:=.Cells(X, 6)
                WS.Cells(Y, 2).Offset(0, 4).Copy Destination:=.Cells(X, 7)
                WS.Cells(Y, 2).Offset(0, 0).Copy Destination:=.Cells(X, 8)
                X = X + 1
                Z = Z + 1
    '        Else
    '            Y = Y + 1
            End If
            If Z = 7 Then Exit For
        Next Y
    End With
    
    End Sub
    

    也许您可以详细说明一下为什么要使用命名范围,以及您希望用它们实现什么,而上述代码无法按原样实现。

    更新:

    Miqi180 让我意识到,通过直接引用单元格来避免 Offset 时可能存在性能差异。因此,我在我的系统(Office 2016,64 位)上进行了一个小型性能测试来测试这个假设。显然,存在约 14% 的主要性能差异(比较使用 Offset 的 10 次迭代的平均值和避免它的另外 10 次迭代)。

    这是我用来测试速度差异的代码。如果您认为此设置有缺陷,请告诉我:

    Option Explicit
    
    ' Test whether you are using the 64-bit version of Office.
    #If Win64 Then
        Declare PtrSafe Function getTickCount Lib "kernel32" Alias "QueryPerformanceCounter" (cyTickCount As Currency) As Long
    #Else
        Declare Function getTickCount Lib "kernel32" Alias "QueryPerformanceCounter" (cyTickCount As Currency) As Long
    #End If
    
    Public Sub SpeedTestDirect()
    
    Dim i As Long
    Dim ws As Worksheet
    Dim dttStart As Date
    Dim startTime As Currency, endTime As Currency
    
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual
    Application.EnableEvents = False
    
    Set ws = ThisWorkbook.Worksheets(1)
    ws.Cells.Delete
    
    dttStart = Now
    getTickCount startTime
    
    For i = 1 To 1000000
        ws.Cells(i, 1).Value2 = 1
        ws.Cells(i, 2).Value2 = 1
        ws.Cells(i, 3).Value2 = 1
        ws.Cells(i, 4).Value2 = 1
        ws.Cells(i, 5).Value2 = 1
        ws.Cells(i, 6).Value2 = 1
    Next i
    
    Application.EnableEvents = True
    Application.Calculation = xlCalculationAutomatic
    Application.ScreenUpdating = True
    
    getTickCount endTime
    Debug.Print "Runtime: " & endTime - startTime, Format(Now - dttStart, "hh:mm:ss")
    
    
    End Sub
    
    Public Sub SpeedTestUsingOffset()
    
    Dim i As Long
    Dim ws As Worksheet
    Dim dttStart As Date
    Dim startTime As Currency, endTime As Currency
    
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual
    Application.EnableEvents = False
    
    Set ws = ThisWorkbook.Worksheets(1)
    ws.Cells.Delete
    
    dttStart = Now
    getTickCount startTime
    
    For i = 1 To 1000000
        ws.Cells(i, 1).Offset(0, 0).Value2 = 1
        ws.Cells(i, 1).Offset(0, 1).Value2 = 1
        ws.Cells(i, 1).Offset(0, 2).Value2 = 1
        ws.Cells(i, 1).Offset(0, 3).Value2 = 1
        ws.Cells(i, 1).Offset(0, 4).Value2 = 1
        ws.Cells(i, 1).Offset(0, 5).Value2 = 1
    Next i
    
    Application.EnableEvents = True
    Application.Calculation = xlCalculationAutomatic
    Application.ScreenUpdating = True
    
    getTickCount endTime
    Debug.Print "Runtime: " & endTime - startTime, Format(Now - dttStart, "hh:mm:ss")
    
    End Sub
    

    基于这一发现,改进后的代码应该是(感谢 Miqi180):

    Public Sub tmpSO()
    
    Dim WS As Worksheet
    Dim X As Long, Y As Long, Z As Long
    
    X = 12
    Z = 0
    
    Set WS = ThisWorkbook.Worksheets("Schedule")
    
    With Worksheets("Project Status")
        For Y = 4 To WS.Cells(WS.Rows.Count, 2).End(xlUp).Row
            If WS.Cells(Y, 2).Font.Bold And WS.Cells(Y, 2).Value2 < 1 Then
                WS.Cells(Y, 3).Copy Destination:=.Cells(X, 3)
                WS.Cells(Y, 5).Copy Destination:=.Cells(X, 6)
                WS.Cells(Y, 6).Copy Destination:=.Cells(X, 7)
                WS.Cells(Y, 2).Copy Destination:=.Cells(X, 8)
                X = X + 1
                Z = Z + 1
    '        Else
    '            Y = Y + 1
            End If
            If Z = 7 Then Exit For
        Next Y
    End With
    
    End Sub
    

    然而,应该注意的是,通过转移到 (1) 仅复制值/直接使用 .Cells(X, 3).Value2 = WS.Cells(Y, 2).Value2(例如)和 (2) 进一步使用数组来代替,速度仍然可以大大提高。

    当然这还不包括Application.ScreenUpdating = FalseApplication.Calculation = xlCalculationManualApplication.EnableEvents = False等标准建议。

    【讨论】:

    • 我同意您建议的大部分代码清理,但是当您可以直接设置引用时,为什么还要使用.Offset(0, 1) 等?这应该同样有效,并且速度会稍微快一些:WS.Cells(Y, 3).Copy Destination:=.Cells(X, 3) 等。
    • @Miqi180:通常情况下,每当我提供答案时,我都会尽可能地坚持原始代码。我这样做有两个原因:(1)只是为了确保 OP 可以理解/遵循解决方案,并可以在之后维护/改进代码。 (2) 我不想把我的编码风格强加给别人。因此,如果我没有看到性能或稳定性方面的差异,那么我更愿意保留 OP 代码。但是,您似乎是对的,并且存在性能差异。我相应地改变了答案。好收获!
    • 谢谢。您测试这两种情况的基本方法是合理的,因为您只需要测试offset 是否按预期减慢速度。一些提示:VBA 有一个内置的计时器函数,称为...Timer,它在 Windows 机器上返回秒的小数部分。 (在 Mac 上,分辨率为 1 秒)。通过将startTimeendTime 标注为Single 并将它们设置为等于TimerDebug.Print 语句将返回经过的秒数(精确到小数点后两位),这看起来更直观。另外,这样你就不需要声明 getTickCount 函数了。
    • @Miqi180 你可能是对的。当这篇文章仍然适用时,我刚回来:stackoverflow.com/questions/3162826/… 据此,微软建议使用 GetTickCount。此外,(当时)Timer 被认为不如 GetTickCount 准确/快速。更多背景信息:randomascii.wordpress.com/2013/05/09/… 但我想我现在会切换到Timer。谢谢。
    • 当然只是一个建议,但它只是让编写性能测试代码更容易,而且我认为在这种情况下使用Timer 是相当安全的,即使它比其他可用时间稍微不准确函数,因为我们只处理一百万次迭代之前/之后的两次时间测量。如果这两种情况之间的性能差异如此之小,以至于需要更细粒度的计时函数,我会使用更容易阅读的代码;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-07
    • 2011-06-17
    • 2015-10-27
    • 1970-01-01
    相关资源
    最近更新 更多