将工作簿声明为全局变量

Declare a Workbook as a Global variable

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

首先我有:

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

这给了我:

"Compile error: Invalid outside procedure"

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

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

这给了我:

"Compile error: Expected: type name"


编辑:

使用:

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

但是 returns

"Object Required"

在我的模块中。

编辑 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

这是迄今为止我能想到的最好的。结果是现在只有一个地方可以更改文件名,但是我仍然需要在每个子例程中复制 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

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


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

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

中关于变量作用域的文章

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

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

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

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

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

如果您创建一个模块,例如 ExcelMod,并且在该模块中您有一个 public 函数或子例程 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 事件调用初始化子来创建对象并在需要时仅设置一次。

这就是你想要的吗?

我认为工作簿全局变量最通用的方法是创建一个带有 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

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

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

要使用,只需在您需要的地方和时间调用 属性,不需要额外的变量(或它们的集合)。

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

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

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

您也可以使用 class 模块来完成此操作,并在模块中使用 class 初始化程序时为您完成工作:

Class 名为 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 来引用工作簿代码是 运行ning 和 ActiveWorkbook 来引用具有焦点的工作簿。这样您就可以 运行 来自一个工作簿 (ThisWorkbook) 的代码,使用位置工作簿 (Locations.Workbook) 作为参考并迭代其他工作簿 (ActiveWorkbook) 以添加另一个级别的自动化。

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

不过我必须补充一点,在这种情况下,我认为如果你能稍微大一点地描述你想要实现的目标,我们可能会为你提供一个比你遇到的问题更好的解决方案编码。

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

Class模块:

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

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

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

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

此外,在可能启动您的代码的任何 "gateway" 子函数或函数(例如事件处理程序、UDF 等)中,放置 Initialize(或者:If Not Initialized Then Initialize)作为第一行。通常大多数潜艇不会直接启动,并且可以依赖调用者正确设置的 Locations 。如果您需要测试某些变量未设置时无法正确 运行 的内容,那么您可以直接在立即 Window.

中键入 initialize

每当我 运行 进入这个时,我将 wb 声明为 public 常量字符串:

public wb as string = "c:\location"

然后,整个项目中的代码,可以参考

workbooks(wb).anything

您可能想要创建一个加载项,或使用 Class 模块来处理属性,...

但我不确定 常规模块中的简单声明和在工作簿打开时调用该过程 更清洁 =27=] 也可以做到这一点。

(我已经使用这个方法很多次了,一直没有被打扰)

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

'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