全局变量和局部变量在不同subs中的使用

Usage of global variables and local variables in different subs

我正在使用 VBA 将内容从 Excel 复制粘贴到 PowerPoint。它正在运行,但我需要清理我的代码。问题是,对于在多个过程中使用的变量(或对象),我不知道什么是好的做法。我想我在这方面缺乏一些基本的了解。

  1. 将变量从一个过程传递到另一个过程时,在两个过程中使用相同的名称是否是个坏主意?例如

    Sub 1()
        Dim a As Long, b As Long
        Call Sub2(a, b)
        '...
    End Sub
    
    Sub2(a As Long, b As Long)
        '...
    End Sub
    

    是否应该Sub2 以不同的方式调用变量,例如 x 和 y?如果不是,我回到问题 1):将这些变量直接从 Sub1 传递到 Sub2 而不是全局声明它们的原因是什么?当我打算在调用 Sub2 后在 Sub1 中使用 ab 的原始值时我得到它(所以基本上将它作为 ByVal 传递给 Sub2),但在我的情况下并非如此。

  2. 是否有理由限制全局变量的使用?我在我的代码中将它们保留为本地,但我应该在全局定义 lRowAnlRowData 等吗?

  3. 我什么时候应该将一个变量从一个 sub 传递到另一个?在我下面的代码中,使用 iSlides 执行此操作对我来说很有意义,但对于 wsEm.

    则不然

    以下是我的部分实际代码。 Subs EmDataEmDataAn 非常相似,我会看看是否可以合并它们,但它们很好地说明了我遇到的问题,因为它们使用了许多相同的变量。

    Public mySlide As PowerPoint.Slide
    Public PowerPointApp As PowerPoint.Application
    Public myPresentation As PowerPoint.Presentation
    Public MonatNum As String, JahrNum As String, MonatStr As String
    
    Sub CreateReport()    
        Dim DestinationPPT As String
        Dim iSlides As Integer
        Dim fRowAn As Long, lRowAn As Long, lRowData As Long
        Dim wbEm As Workbook
        Dim wsEm As Worksheet
    
        Set PowerPointApp = New PowerPoint.Application
        DestinationPPT = "C:\VBA\ReportTemplate.pptm"
        Set myPresentation = PowerPointApp.Presentations.Open(DestinationPPT)
    
        Set wbEm = Workbooks.Open("C:\VBA\Report.xlsx")
        Set wsEm = wbEm.Sheets("Sheet1")
    
        lRowAn = wsEm.Cells(Rows.Count, 3).End(xlUp).Row
        fRowAn = wsEm.Cells(Rows.Count, 3).End(xlUp).End(xlUp).Row + 1
    
        If lRowAn >= 127 Then
            If lRowData <= 127 Then '4 Slides, but separate Annotations from Data
                iSlides = 1
                Call EmData(wsEm, iSlides)
                Call EmDataAn(wsEm, iSlides)
            Else '4 Slides
                iSlides = 3
                Call EmData(wsEm, iSlides)
            End If
        Else '3 Slides
            Call EmData(wsEm, iSlides)
        End If
    
        Application.DisplayAlerts = False
        wbEm.Close SaveChanges:=False
        Application.DisplayAlerts = True
    
        PowerPointApp.Visible = True
        PowerPointApp.Activate
        Application.CutCopyMode = False
    End Sub
    
    
    
    Sub EmData(wsEm As Worksheet, iSlides As Integer)
        Dim i As Integer
        Dim fRowDataCalc As Long, lRowDataCalc As Long, lRowCopy As Long
        Dim rowHght As Long
        Dim rng As Range
    
        For i = 0 To iSlides
            fRowDataCalc = 4 + 40 * i + i * 1
            lRowDataCalc = 4 + 40 * (i + 1) + i * 1
    
            With wsEm
                .Range("B2:K3").Copy .Range("B500")
                .Range("B" & fRowDataCalc & ":K" & lRowDataCalc).Copy .Range("B502")
                rowHght = .Range("B3").EntireRow.Height
                .Range("B501").RowHeight = rowHght
                lRowCopy = .Cells(Rows.Count, "C").End(xlUp).Row
                Set rng = .Range("B500:K" & lRowCopy)
            End With
    
            Set mySlide = myPresentation.Slides.AddSlide(myPresentation.Slides.Count + 1, PPLayout("LayoutEmittenten"))
            mySlide.Shapes.Placeholders(1).TextFrame.TextRange.Text = "Headline (" & i + 1 & ")"
            Call PasteEm(mySlide, rng)
            rng.Clear
        Next i
    
    End Sub
    
    Sub EmDataAn(wsEm As Worksheet, iSlides As Integer)
        Dim lRowAn As Long, fRowAn As Long, lRowData As Long, fRowDataCalc As Long, lRowDataCalc As Long
        Dim rng As Range
        Dim rowHght As Long, lRowCopy As Long
    
        lRowAn = wsEm.Cells(Rows.Count, 3).End(xlUp).Row
        fRowAn = wsEm.Cells(Rows.Count, 3).End(xlUp).End(xlUp).Row - 1
        lRowData = wsEm.Cells(Rows.Count, 10).End(xlUp).Row
    
        iSlides = iSlides + 1
        fRowDataCalc = 4 + 40 * iSlides + iSlides * 1
        lRowDataCalc = lRowData
    
        'Last sheet with data
        With wsEm
            .Range("B2:K3").Copy .Range("B500")
            .Range("B" & fRowDataCalc & ":K" & lRowDataCalc).Copy .Range("B502")
            rowHght = .Range("B3").EntireRow.Height
            .Range("B501").RowHeight = rowHght
            lRowCopy = .Cells(Rows.Count, "C").End(xlUp).Row
            Set rng = .Range("B500:K" & lRowCopy)
        End With
    
        Set mySlide = myPresentation.Slides.AddSlide(myPresentation.Slides.Count + 1, PPLayout("LayoutEmittenten"))
    
        mySlide.Shapes.Placeholders(1).TextFrame.TextRange.Text = "Headline (" & iSlides + 2 & ")"
        Call PasteEm(mySlide, rng)
        rng.Clear
        'Annotations
        Set rng = wsEm.Range("B" & fRowAn & ":K" & lRowAn)
        rng.Copy
        Set mySlide = myPresentation.Slides.AddSlide(myPresentation.Slides.Count + 1, PPLayout("LayoutEmittenten"))
        mySlide.Shapes.Placeholders(1).TextFrame.TextRange.Text = "Headline (" & iSlides + 2 & ")"
        Call PasteEm(mySlide, rng)
    End Sub
    
    
    
    Sub PasteEm(mySlide As PowerPoint.Slide, rng As Range)
        Dim myShape As PowerPoint.Shape
    
        rng.Copy
        DoEvents
        mySlide.Shapes.PasteSpecial DataType:=ppPasteEnhancedMetafile ' = 2
        Set myShape = mySlide.Shapes(mySlide.Shapes.Count)
        With myShape
            .Width = 683
            .Top = 70
            .Left = 5
        End With
    End Sub
    

这与其说是代码功能,不如说是一般如何使用变量。

1) When passing a variable from one procedure to another, is it a bad idea to use the same name in both procedures?

有两种方法可以将参数传递给另一个过程,ByValByRef

默认情况下 VBA 使用 ByRef 这样做:

Option Explicit
Sub Test()

    Dim i As Long

    For i = 0 To 1000
        Call Tested(i)
    Next i

End Sub
Sub Tested(i As Long)

    i = i + 1

End Sub

会让你发疯,因为从第一个循环开始, i = 0 会跳转到 i = 2 。为什么?因为 Tested() 会将 iTest() 中的 Next i 加 1。

如何避免这种情况并仍然使用相同的变量?使用 ByVal 这样您就可以将 i 的值赋予 Tested() 并且 Tested() 上的更改不会影响您的初始循环。

全局变量?如果可能,您不应该使用它们。

什么时候使用它们根据我的经验?

例如,在一个工作簿中处理大量工作表和调用它们的不同过程,然后只需获取所有工作表的子设置并将它们声明为全局。

其他情况我认为没有必要,因为就像 QHarr 所说的那样,优化将参数传递给过程更快。

局部变量

首先尽可能将每个变量声明为局部变量。例如,如果只有一个 procedure/function 需要它,请在那里声明它。

局部变量(作为参数传递)

如果您需要在多个 procedure/function 中访问变量,那么最好将其作为参数传递给下一个函数。这可以完成 ByRef(默认)或 ByVal.

Sub ProcedureA()
    Dim ParamA As String
    ParamA = "AAA"
    Dim ParamB As String
    ParamB = "BBB"

    ProcedureB ParamA, ParamB

    Debug.Print ParamA 'returns 111
    Debug.Print ParamB 'returns BBB
End Sub

Sub ProcedureB(ByRef Param1 As String, ByVal Param2 As String)
    Param1 = "111" 'this will change ParamA in ProcedureA too
    Param2 = "222" 'this value will only be changed in ProcedureB
End Sub

虽然使用 ByRef(通过引用)可以更改 ProcedureB 中的参数并在 ProcedureA 中更改它,但是传递的参数 ByVal(按值)在 ProcedureA.

中没有变化

从技术上讲,如果您以不同的方式命名变量或使用相同的名称,这没有任何区别。使用在每个过程中最有意义的名称将是一个很好的做法(请参阅下面的标题变量名称)。

实际上我认为总是指定它是 ByRef 还是 ByVal 并且不使用默认值也是一个好习惯。使用默认值时,您始终必须记住它在 VBA 中默认为 ByRef,但在 VB.NET 中默认为 ByVal,这很容易让人混淆(至少我是这样) .

ProcedureA 结束后变量不再可用(数据丢失)。

全局变量

如果您希望数据在多个函数中持久且可访问,请使用全局变量(尽可能少用)。

Dim GlobalVarA As String

Sub ProcedureA()
    GlobalVarA = "AAA"
End Sub

Sub ProcedureB()
    Debug.Print GlobalVarA 'return AAA (if ProcedureA was run before)
End Sub

请注意,在这种情况下 任何 过程都可以更改 GlobalVarA 的值。如果如上所述将其作为参数传递,则只有传递变量的过程才能访问该变量。

当Excel VBA 结束(或文件关闭)时,全局变量将丢失它们的数据。

在过程中使用全局变量的缺点是,您需要在第一次使用之前始终检查它的值。因为如果它还没有初始化它是 EmptyNothing。例如(上图)当 运行ning ProcedureB 时,您不能依赖 ProcedureA 之前已经 运行。所以你需要在 ProcedureB 中使用它之前检查 GlobalVarA 的值,特别是如果它是 object 你必须测试是否没有蜂鸣 Nothing 否则你会很容易运行 进入错误。

本地与全球

所以我们可以总结,尽可能限制对变量的访问可以使您的代码更安全、更可靠(如果仅在本地声明,则其他函数不会意外更改它)。仅在确实需要时才使用全局变量。

Re-Use 变量名

到re-use 变量名如果在本地声明一般是没有问题的。但是如果你对全局变量和局部变量使用相同的名称就会变得棘手(然后 VBA 更喜欢局部变量!)

Dim VarA As String 'global

Sub ProcedureA()
    Dim VarA As String 'same name local
    VarA = "AAA" 'this uses always the local variable!
End Sub

Sub ProcedureB()
    Debug.Print VarA 'this uses the global variable and it is empty (after ProcedureA is run) 
End Sub

一般来说,只使用有意义的变量名是一种很好的做法。这意味着不是调用变量 rng1rng2,而是调用它们,例如 InputRangeOutputRange。此外,如果您需要一个计数器(例如循环遍历行和列),通常会使用 ij,但是如果您使用例如 iRowiCol 作为变量名。

选项显式

为了强制正确的变量声明,我建议始终激活 Option Explicit:在 VBA 编辑器中转到 Tools选项Require Variable Declaration。这可以防止您 mis-typing 变量名和不小心引入新变量。