如果打开两个具有相同宏的类似xls文件,则全局变量为空

时间:2017-08-02 14:52:36

标签: excel vba excel-vba

在Excel中有一个已经写好的宏,并且有关于此的报告错误,我必须解决这个问题。初步调查如下...... 有ABC.xls个文件,其中包含macro

现在,该宏有一个名为sub的{​​{1}},当我按下changeTheCode时,它会被调用。

此子将打开Ctrl + M,用户可以在其中选择CSV文件。我存储在全局变量中的CSV文件的路径声明在所有函数之外...

Open File Dialog

当用户关闭excel时,此全局变量将用于将更改保存到CSV文件中。

Public txtFileNameAndPath As String

我使用此Private Sub Workbook_BeforeClose(Cancel As Boolean) Call saveUnicodeCSV Call deleteXLS End Sub 文件打开ABC.xls文件。

我使用此ABC123.CSV(ABC.xls的副本)文件打开DEF.xls文件。但是,当我使用DEF123.CSV打开DEF123.CSV时,Ctrl + M的子changeTheCode被调用,ABC.xls的全局变量txtFileNameAndPath为空的,当我关闭Excel时,由于这个原因,事情没有得到保存。

enter image description here

设置全局变量的代码。

DEF.xls

如何处理这个问题的输入将对我有所帮助。

注意:包含宏的Excel将提供给客户。因此,我不能要求客户进行一些注册表调整,以便在单独的实例中打开Excel。

感谢。

8 个答案:

答案 0 :(得分:5)

我认为问题是当您将相同的宏名称绑定到快捷键时,打开的第一本书将被分配给该快捷键。

解决方法是使另一个宏根据工作簿名称调用正确的宏。这可能需要您将某些工作簿和工作表替换为ActiveWorkbook或ActiveWorksheet。但请先试试。

这只是我尝试的示例代码,但请根据您的需要进行编辑。我通过制作两个文件a.xlsm和b.xlsm来尝试它。区别是a.xlsm有msgbox" a"在第一行和b.xlsm有msbox" b"在第一行。首先打开a.xlsm然后再打开b.xls进行检查。然后分配相同的快捷键。您将看到,当您在b.xlsm中运行Ctrl + M时,将运行的宏将位于带有msgbox" A"的a.xlsm中。但是它会在b.xlsm中调用正确的宏。

简答

此代码使用Application.Run来运行特定工作簿中的宏。创建一个帮助宏,它将检查文件名,然后附加宏名称。

因此,当您按ctrl + m时,无论运行哪个工作簿run_code,它都将返回到activeworkbook并从该工作簿运行宏(在本例中为plaster)。此外,它将使用activeworkbook值填充公共变量。

https://www.rondebruin.nl/win/s9/win001.htm application.run的一些示例

到目前为止,最简单的解决方案只需添加来电宏

来电宏:

Sub call_changeTheCode() ' add to all workbooks, that have changeTheCode macro then assign to Ctrl + m

Application.Run ActiveWorkbook.Name & "!changeTheCode"

End Sub




概念证明,而不是您的实际代码,使用上面的来电宏:

Public varvar As String
Sub run_code() 'assignt to shortcut key CTRL+M both macros in a.xlsm and b.xlsm
MsgBox "a" ' to test create another workbook and change this to b
file_path = ActiveWorkbook.Path 'just to check path
file_name = ActiveWorkbook.Name 'gets the file name
MsgBox file_path 'msgbox the file_path
MsgBox file_name 'msgbox the file_name
MsgBox file_name & "!plaster" 'msgbox the file name plus macro name, in your instance it would be "ABC.xlsm'!macro_name" please note the format
Application.Run file_name & "!plaster"

End Sub

Sub plaster() 'this is the test macro that will show correct macro in workbook is called
varvar = ActiveWorkbook.Name
MsgBox "hi this is workbook " & varvar

End Sub

答案 1 :(得分:3)

如果我理解正确,则用户将DEF.xls作为顶级工作簿,因此等于ActiveWorkbook。然后我的想法是为这个工作簿添加一个非常独特的名称,例如" zZzVBAdatazZz"并使其非常隐藏(设置Sheets("zZzVBAdatazZz").Visible = xlVeryHidden),因此用户无法通过菜单命令取消隐藏它。运行宏时,您可以在此工作表中存储DEF123.CSV的路径,让我们在单元格A1ActiveWorkbook.Sheets("zZzVBAdatazZz").Range(A1) = .SelectedItems(1))中说明。在Private Sub Workbook_BeforeClose(Cancel As Boolean)检查Sheets("zZzVBAdatazZz").Range(A1)中是否有值。如果是这样,请将其存储在变量中(现在可以是本地的),删除A1中的值并将路径传递给saveUnicodeCSV

答案 2 :(得分:2)

有趣的问题,我认为问题的核心是工作簿的全局对象在整个应用程序空间中并不是真正的全局对象。此后,我将全球写为“全球”,以突出这种误解。

我相信你的宏总是从一个地方运行,所以如何为每个工作簿设置一个“全局”变量的范围,但仍然可以从另一个工作簿的代码库中获取它。

我的解决方案是使用ThisWorkbook模块作为容纳“全局”变量的位置。所以在ThisWorkbook模块中放置以下代码

Option Explicit

Public NJMRGlobalVar As Variant

然后在宏代码的模块中,您将需要一个函数来测试给定的工作簿是否支持/导出这个新的“全局”变量。所以这样的事情(与单元测试一起给出)

Private Function WorkbookHasNJMRGlobalVar(ByVal wb As Excel.Workbook) As Boolean
    If wb Is Nothing Then
        WorkbookHasNJMRGlobalVar = False
    Else
        Dim vTest As Variant
        vTest = CVErr(xlErrName)
        'Requires VBA IDE->Tools->Options->General->Error Trapping->Break on Unhandled Errors
        On Error Resume Next
        vTest = CallByName(wb, "NJMRGlobalVar", VbGet)

        Dim lSaveError As Long
        lSaveError = Err.Number
        On Error GoTo 0
        WorkbookHasNJMRGlobalVar = (lSaveError = 0)
    End If

End Function

Private Sub TestWorkbookHasNJMRGlobalVar()

    Debug.Assert WorkbookHasNJMRGlobalVar(ThisWorkbook) = True

    Dim wbUnsuitable As Excel.Workbook
    Set wbUnsuitable = Workbooks.Item("VBA Fileshare.xlsm") '*<---- different for you!

    Debug.Assert WorkbookHasNJMRGlobalVar(wbUnsuitable) = False

End Sub

最后一步是重写您的代码,而不是使用ThisWorkbook,而是重写ActiveWorkbook或使用Application.Workbooks.Item(“foo.xlsm”)获取目标工作簿。所以这里有一些代码(适用于ActiveWorkbook)。

Sub CodePageChange()
    Dim SheetName As Worksheet
    Dim fd As Office.FileDialog
    Dim sheetName1 As String
    Dim tabSheetName As String

    If Not WorkbookHasNJMRGlobalVar(ActiveWorkbook) Then
        MsgBox "Currently Active Workbook not a suitable candidate for that macro."
    Else

        Dim wbGlobalVarEnabled As Excel.Workbook
        Set wbGlobalVarEnabled = ActiveWorkbook

        Set fd = Application.FileDialog(msoFileDialogFilePicker)

        With fd
            '....
            '....
            '....
            If .Show = True Then
                wbGlobalVarEnabled.NJMRGlobalVar = .SelectedItems(1)
            Else
                MsgBox "Please start over.  You must select a csv file."
                Exit Sub
            End If
        End With
    End If
End Sub

所以现在每个工作簿都有一个“全局”变量,可以通过获取对Excel.Workbook的引用从一个代码中自由访问。这是有效的,因为Excel.Workbook不禁止接口中的可扩展性,这意味着可以添加额外的方法和属性(但并非所有工作簿都支持它们,因此您可以检测其功能,例如上面的WorkbookHasNJMRGlobalVar

请发布反馈,如果还不够,我会修改,我有99/100的VBA问题我想获得我的青铜VBA徽章:)

编辑:阅读你的个人资料,你有C / C ++技能,所以我可以进一步解释这是如何工作的。在Excel的类型库中,“不可扩展”的IDL关键字装饰了大多数接口,禁止使用额外的方法和属性,但interface _Workbook中缺少这些,所以你可以这样做。以下是OleView.exe的截图

enter image description here

答案 3 :(得分:2)

唯一的快捷方式只能分配给工作簿中的单个过程。 再次设置相同的快捷方式会覆盖先前的分配。

要在不同工作簿中处理相同的快捷方式,请在激活工作簿时指定快捷方式:

' ThisWorkbook '

Private Sub Workbook_Activate()
  Application.OnKey "^m", "CodePageChange"
End Sub

' Module '

Public Sub CodePageChange()
  MsgBox ThisWorkbook.Name
End Sub

或者处理接收它的工作簿中的回调,然后调用目标工作簿上的过程:

' ThisWorkbook '

Private Sub Workbook_Open()
  Application.OnKey "^m", "OnHotkeyCtrlM"
End Sub

' Module '

Public Sub OnHotkeyCtrlM()
  Application.Run "'" & ActiveWorkbook.Name & "'!CodePageChange"
End Sub

Public Sub CodePageChange()
   MsgBox ThisWorkbook.Name
End Sub

答案 4 :(得分:1)

将自定义属性用作全局变量

Private Sub Workbook_BeforeClose(Cancel As Boolean)

    Debug.Print ActiveWorkbook.CustomDocumentProperties("xyz")

End Sub


Sub changeTheCode()

    On Error Resume Next
    ActiveWorkbook.CustomDocumentProperties("xyz").Delete
    ActiveWorkbook.CustomDocumentProperties.Add Name:="xyz", LinkToContent:=False, Type:=msoPropertyTypeString, Value:=ActiveWorkbook.Name
    Debug.Print ActiveWorkbook.CustomDocumentProperties("xyz")

End Sub

答案 5 :(得分:1)

我同意S Meaden的说法,如果没有指定存储“全局”变量的工作簿,则不能在多个工作簿中使用全局变量。

另一种方法: 在带有宏的Excel文件中,完成“全局”变量的设置: 用

替换设置
Shell ("Cmd.Exe /C SetX txtFileNameAndPath thePath")

将thePath替换为实际路径或值。 运行此宏后,您可以运行Excel文件并通过

检索真正的全局值
txtFileNameAndPath = Environ("txtFileNameAndPath")

答案 6 :(得分:1)

将全局变量放入ThisWorkbook类模块。

Public txtFileNameAndPath As String

Private Sub Workbook_BeforeClose(Cancel As Boolean)
    Debug.Print Me.Name & ":" & txtFileNameAndPath
End Sub 

然后在过程CodePageChange中遍历所有工作簿并使用ActiveWorkbook.Name检查应使用哪个全局变量。 HTH

If .Show = True Then
  Dim w As Workbook
  On Error Resume Next
  For Each w In Workbooks
    If ActiveWorkbook.Name = w.Name Then
        w.txtFileNameAndPath = .SelectedItems(1)
        Exit For
    End If
  Next w
  On Error GoTo 0
Else
    MsgBox "Please start over.  You must select a csv file."
    Exit Sub
End If

答案 7 :(得分:0)

用户友好的解决方法:抛弃键盘快捷方式并将CommandButton添加到工作表或将自定义功能区选项卡添加到工作簿以调用该过程。他们保证可以从他们自己的工作簿中调用该过程,并且对用户来说更容易。