【问题标题】:Declare a Workbook as a Global variable将工作簿声明为全局变量
【发布时间】:2015-10-10 18:10:55
【问题描述】:

我开始编写适用于多个工作簿的代码,但始终使用相同的参考工作簿。该代码将有许多子,并且由于我试图避免在每个子中将变量变暗到参考工作簿,我想将它们声明为全局。

首先我有:

Global Locations As Excel.Workbook
Set Locations = Workbooks.Open("M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx")

这给了我:

“编译错误:外部过程无效”

经过一番谷歌搜索后,我在某处找到了以下代码:

Public Const Locations As Excel.Workbook = "Workbooks.Open("M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx")"

这给了我:

“编译错误:预期:类型名称”


编辑:

使用:

Public Const Locations As Excel.Workbook = "Workbooks.Open('M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx')"

(Workbooks.Open 语句中的单引号)结果与使用双引号时的错误相同。

谁知道我做错了什么?

编辑2:

我还尝试在“ThisWorkbook”中声明变量,遵循this answer 使用:

Private Sub Workbook_Open()
Dim Locations As Excel.Workbook
Dim MergeBook As Excel.Workbook
Dim TotalRowsMerged As String


Locations = Workbooks.Open("M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx")
MergeBook = Workbooks.Open("M:\My Documents\MSC Thesis\Italy\Merged\DURUM IT yields merged.xlsm")
TotalRowsMerged = MergeBook.Worksheets("Sheet1").UsedRange.Rows.Count
End Sub

然后它返回一个

“需要对象”

在我的模块中。

编辑3:

我现在有了这个可行的方法,但缺点是必须将 SET 行复制到每个 Sub 中,必须有更好的方法来做到这一点?

Global Locations As Workbook
Global MergeBook As Workbook
Global TotalRowsMerged As String

Sub Fill_CZ_Array()
Set Locations = Application.Workbooks("locXws.xlsx")
Set MergeBook = Application.Workbooks("DURUM IT yields merged.xlsm")
TotalRowsMerged = MergeBook.Worksheets("Sheet1").UsedRange.Rows.Count

【问题讨论】:

  • 你不能使用某种个人工作簿吗?然后这样的工作簿在所有其他工作簿中都可用,例如这工作Debug.Print Workbooks("PERSONAL.XLSB").Sheets(1).Name
  • 您将使用来自Locations 的多少个不同的工作表?
  • Locations 有多个正在使用的工作表。我更喜欢能够处理“无限”数量的工作表的解决方案,以便它可以在各种场景和/或问题下重复使用,也可以来自其他成员。
  • 您的带有代码的工作簿是否需要可见?换句话说,您将使用工作簿中的工作表,还是仅使用工作簿中的代码。如果只有代码,则将工作簿另存为加载项。

标签: vba excel scope global-variables


【解决方案1】:

我认为工作簿全局变量最通用的方法是使用Public Property Get 过程创建一个模块。无需先调用任何代码即可引用,也不用担心文件是否打开。

这是其中一个变量的示例模块代码:

Private wLocations As Workbook

Public Property Get Locations() As Workbook
  Const sPath As String = "M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx"
  Dim sFile As String

  If wLocations Is Nothing Then
      'extract file name from full path
      sFile = Dir(sPath)

      On Error Resume Next

      'check if the file is already open    
      Set wLocations = Workbooks(sFile)

      If wLocations Is Nothing Then
        Set wLocations = Workbooks.Open(sPath)
      End If

      On Error GoTo 0
  End If
  Set Locations = wLocations
End Property

您可以在代码中的任何位置使用它作为全局变量:

Sub Test()
  Debug.Print Locations.Worksheets.Count
End Sub

【讨论】:

  • 我认为这最接近我所追求的。然而,Berryl 的回答似乎做同样的事情,但编码更短。你能解释一下这些差异吗?
  • @Luuklag 这两个帖子之间的唯一区别是我的版本检查文件是否已经打开。如果是,使用Workbooks.Open(sPath) 会引发异常。
【解决方案2】:

您的问题暗示您想要一个全局工作簿常量,而不是变量。因为 VBA 不允许在过程之外初始化对象,所以不能有对象常量。您能做的最好的事情就是拥有一个在事件中初始化的公共工作簿变量。


你可以声明一个全局变量,但你不能执行代码在过程之外赋值:

Public myBook As Excel.Workbook

Sub AssignWorkbook()
    Set myBook = Workbooks.Open("C:\SomeBook.xlsx") '// <~~ valid, inside sub
End Sub

Sub TestItWorked()
    MsgBox myBook.Name
End Sub

所以在一个普通的模块中你可以有:

Public myBook As Excel.Workbook

在您的Workbook_Open() 活动中:

Private Sub Workbook_Open()
    Set myBook = Workbooks.Open("C:\SomeOtherBook.xlsx")
End Sub

然后您可以在代码中的其他位置使用myBook,而无需重新分配它。

可能值得看看 Chip Pearson 关于 VBA 中变量作用域的文章 here

【讨论】:

  • 是的,我们已经知道了,但是没有关于如何执行此代码的解决方案。您应该在要运行的任何子程序中引用此 AssignWorkbook() 子程序,或者在打开时运行此子程序。
  • 查看显示如何在打开事件上分配值的编辑。这意味着您只分配一次。
  • 是的,但我在一小时前也与 user3964075 讨论过这种情况。这只是一种解决方法。我正在寻找一个“干净”的解决方案。
  • 就 VBA 而言 - 干净的解决方案。工作簿是一个 object 类,它在使用 Set 关键字分配之前没有任何值。这是在 Excel 中将工作簿分配给变量的唯一方法 - 您不能拥有工作簿常量。
【解决方案3】:

你想要的是某种具有静态属性的工厂,例如在一个单独的模块中

mFactoryWkbs

Private m_WkbLocations           As Workbook
Private m_WkbMergeBook           As Workbook

Public Property Get LOCATIONS() As Workbook
    If m_WkbLocations Is Nothing Then
        Set m_WkbLocations= Workbooks.Open("wherever")
    End If
    Set LOCATIONS = m_WkbLocations
End Property

Public Property Get MERGEBOOK () As Workbook
    If m_WkbMergeBook Is Nothing Then
        Set m_WkbMergeBook = Workbooks.Open("wherever")
    End If
    Set MERGEBOOK = m_WkbMergeBook 
End Property

要使用,只需在需要时调用属性 where & ,不需要额外的变量(或它们的 Set)。

TotalRowsMerged = MERGEBOOK.Worksheets("Sheet1").UsedRange.Rows.Count

【讨论】:

    【解决方案4】:

    这是迄今为止我能想到的最好的。结果是现在只有一个地方可以更改文件名,但是我仍然需要在每个子例程中复制 SET 函数。还不完全理想,但总比没有好。

    Public Const DESTBOOK = "DURUM IT yields merged.xlsm"
    
    Global Locations As Workbook
    Global MergeBook As Workbook
    Global TotalRowsMerged As String
    
    Sub Fill_CZ_Array()
    Set Locations = Application.Workbooks("locXws.xlsx")
    Set MergeBook = Application.Workbooks(DESTBOOK)
    TotalRowsMerged = MergeBook.Worksheets("Sheet1").UsedRange.Rows.Count
    

    【讨论】:

    • I still need to copy the SET function within every subroutine 这是什么意思?如果您的变量是GlobalPublic,放置在Module 并启动一次,您可以从其他每个模块访问这些变量。
    • 您首先必须设置它们,您只能在子目录中执行此操作。因此,您要么必须将其放置在每个子程序中,因此运行该子程序时发生的第一件事就是正在设置的参数。或者通过调用设置参数的第二个 sub 来启动您的 sub。现在我正在寻找一种使这些 SET 全局化的方法,因此无需在每个 sub 中调用它们。
    • Workbook_Open 中调用Fill_CZ_Array 一次,您不必在其他任何地方调用它。
    • 那肯定是一种解决方法。但是是否还有一种“干净”的方法可以做到这一点?
    • 我能想到的初始化全局工作簿变量的唯一“更清洁”方法是使用常量字符串创建公共属性。如果需要,它将检查工作簿是否已经打开并返回工作簿。
    【解决方案5】:

    每当我遇到这种情况时,我都会将 wb 声明为公共常量字符串:

    public wb as string = "c:\location"
    

    那么,整个项目中的代码,大家可以参考

    workbooks(wb).anything
    

    【讨论】:

    • 这其实并不疯狂!不优雅,但它有效。
    【解决方案6】:

    当我有需要正确初始化的全局变量时,我通常会这样做:

    在通用代码模块中放入以下代码:

    Public Initialized As Boolean
    Public Locations As Workbook
    
    Sub Initialize()
        If Initialized Then Exit Sub
        Const fname As String = "M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx"
        On Error Resume Next
            Set Locations = Workbooks(Dir(fname))
        On Error GoTo 0
        If Locations Is Nothing Then
            Set Locations = Workbooks.Open(fname)
        End If
        Initialized = True
    End Sub
    

    然后在工作簿的代码模块中放入:

    Private Sub Workbook_Open()
        Initialize
    End Sub
    

    此外,在任何可能启动您的代码的“网关”子或函数(例如事件处理程序、UDF 等)中,将Initialize(或者可能:If Not Initialized Then Initialize)作为第一行。通常,大多数潜艇不会直接启动,并且可以依靠调用者正确设置的Locations。如果您需要测试在未设置变量的情况下无法正常运行的内容,则可以直接在即时窗口中输入initialize

    【讨论】:

      【解决方案7】:

      你也可以用一个类模块来做这件事,当它在模块中被使用时,依靠类初始化器来为你完成工作:

      名为 cLocations 的类模块:

      Public Workbook As Workbook
      
      Private Sub Class_Initialize()
          Set Workbook = Workbooks.Open("C:\Temp\temp.xlsx")
      End Sub
      

      以及您喜欢在模块中的哪个位置,或任何地方:

      Dim Locations As New cLocations
      
      Sub dosomething()
          Locations.Workbook.Sheets(1).Cells(1, 1).Value = "Hello World"
      End Sub
      

      然后,您可以只使用Locations.Workbook 来引用位置工作簿,使用ThisWorkbook 来引用代码正在运行的工作簿,并使用ActiveWorkbook 来引用具有焦点的工作簿。这样,您可以从一个工作簿 (ThisWorkbook) 运行您的代码,使用位置工作簿 (Locations.Workbook) 作为参考,并迭代其他工作簿 (ActiveWorkbook) 以添加另一个级别的自动化。

      如果您单步执行代码,您将看到该类仅在您遇到需要它的代码行时才被初始化,而不是在加载工作簿时。

      但我必须补充一点,在这种情况下,我认为如果您给我们一个稍微大一点的关于您正在努力实现的目标,我们可能会为您提供一个比您在编码时遇到的问题更好的问题的解决方案。

      您还可以更进一步,抽象到应用程序级别,隐藏位置工作簿,如果您明确知道它们的位置或名称,甚至为命名工作表提供智能感知:

      类模块:

      Private App As Application
      Public Workbook As Workbook
      Public NamedSheet As Worksheet
      
      Private Sub Class_Initialize()
          Set App = New Application
          App.Visible = False
          App.DisplayAlerts = False
          Set Workbook = App.Workbooks.Open("C:\Temp\temp.xlsx") 'maybe open read only too?
          Set NamedSheet = Workbook.Sheets("SomethingIKnowTheNameOfExplicitly")
      End Sub
      
      Public Sub DoSomeWork()
          'ThisWorkbook refers to the one the code is running in, not the one we opened in the initialise
          ThisWorkbook.Sheets(1).Cells(1, 1).Value = Wb.Sheets(1).Cells(1, 1).Value
      End Sub
      
      Public Function GetSomeInfo() As String
          GetSomeInfo = NamedSheet.Range("RangeIKnowTheNameOfExplicitly")
      End Function
      

      然后在您的模块中,第一次使用该变量时,它将在一行代码中初始化:

      Dim Locations As New cLocations
      Dim SomeInfo
      
      Sub DoSomething()
          SomeInfo = Locations.GetSomeInfo 'Initialised here, other subs wont re-initialise
      
          Locations.Workbook.Sheets(1).Cells(1, 1).Value = _ 
              ThisWorkbook.Sheets(1).Cells(1, 1).Value
      
          Locations.NamedSheet.Cells(1,1).Value = "Hello World!"
      
          Locations.Workbook.Save
      End Sub
      

      【讨论】:

        【解决方案8】:

        此解决方案只有在您知道将使用的参考工作簿中的所有工作表的编号和名称时才有效。

        在您的模块中,为您的所有工作表声明工作表公共变量,如下所示:

        Public sht1 As Worksheet
        Public sht2 As Worksheet
        Public sht3 As Worksheet
        ...
        

        在应用程序加载事件中实例化这些公共变量。

        Sub Workbook_Open()
        
            Workbooks.Open ("your referenced workbook")
        
            'Instantiate the public variables
            Set sht1 = Workbooks("Test.xlsm").Sheets("Sheet1")
            Set sht2 = Workbooks("Test.xlsm").Sheets("Sheet2")
            Set sht3 = Workbooks("Test.xlsm").Sheets("Sheet3")
        
        End Sub
        

        现在您可以在您的子目录中引用这些全局工作表。

        例如:

        Sub test()
            MsgBox sht1.Range("A1").Value
            MsgBox sht2.Range("A1").Value
            MsgBox sht3.Range("A1").Value
        End Sub
        

        【讨论】:

        • 那么没有必要全局定义工作表。只有工作簿就足够了。自从有了工作簿后,我就可以链接到其中的工作表。
        • 那么在这种情况下,Macro Man 的答案就是你所需要的。我给了这个选项,这样你就不需要实例化所有的工作表,你可以直接使用它们。
        【解决方案9】:

        如果您创建一个模块,例如 ExcelMod,并且在该模块内您有一个公共函数或子例程 Initialize() 和另一个称为 Terminate(),您可以使用这些例程初始化和终止模块级变量。例如我以前用过这个:(注意模块变量是在模块顶部声明的第一件事。)

        Dim excelApp As Object, wb As Workbook, ws As Worksheet
        
        Sub Initialize()
            Set excelApp = CreateObject("Excel.Application")
            Set wb = Workbooks.Open("C:\SomeOtherBook.xlsx")
        End Sub
        
        Sub Terminate()
            Set excelApp = Nothing
            Set wb = Nothing
        End Sub
        

        变量是整个模块的一部分,只能通过这些子例程进行初始化和终止。您可以根据需要将变量传入和传出模块,并在所有这些模块子例程中使用它们,而无需再次设置。如果您需要在另一个模块中使用,则需要像往常一样将其传递给该模块。

        正如其他人所提到的,您可以使用 workbook_Open 事件来调用初始化子来创建对象并在需要时只设置一次。

        这就是你所追求的吗?

        【讨论】:

          【解决方案10】:

          如果我正确理解了您的问题,那么您正在创建的代码应该在应用程序级别而不是工作簿级别上运行。在这种情况下,为什么不创建一个加载项。

          加载项中的所有代码都可以访问应用程序级别的所有打开的工作簿。

          【讨论】:

            【解决方案11】:

            您可能想要创建一个插件,或使用一个类模块来处理属性,...

            但我不确定它是否会比常规模块中的简单声明以及在工作簿打开时调用该过程清洁也很好。

            (这个方法我用了好久没被打扰)

            因此您可以在(专用或非专用)常规模块中使用它

            'Set the path to your files
            Public Const DESTBOOK = "M:\My Documents\MSC Thesis\Italy\Merged\DURUM IT yields merged.xlsm"
            Public Const LOCBOOK = "M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx"
            
            'Declare all global and public variables
            Global Locations As Workbook
            Global MergeBook As Workbook
            Global TotalRowsMerged As String
            
            'Set all variable (Procedure call from Workbook_Open)
            Sub Set_All_Global_Variables()
                Set Locations = Set_Wbk(LOCBOOK)
                Set MergeBook = Set_Wbk(DESTBOOK)
                TotalRowsMerged = MergeBook.Worksheets("Sheet1").UsedRange.Rows.Count
                '...
            End Sub
            
            'Function to check if the workbook is already open or not
            Function Set_Wbk(ByVal Wbk_Path As String) As Workbook
                On Error Resume Next
                    Set Set_Wbk = Workbooks(Dir(Wbk_Path))
                On Error GoTo 0
                If Set_Wbk Is Nothing Then
                    Set Set_Wbk = Workbooks.Open(Wbk_Path)
                End If
            End Function
            

            调用设置ThisWorkbook模块中所有变量的过程

            Private Sub Workbook_Open()
                Set_All_Global_Variables
            End Sub
            

            【讨论】:

              猜你喜欢
              • 2015-02-19
              • 2016-01-24
              • 2011-04-16
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-08-21
              • 2011-11-25
              • 1970-01-01
              相关资源
              最近更新 更多