确定VBE是否处于打开状态。

5
我试图开发一个“自动运行”宏,以确定VBE是否已打开(不一定是焦点窗口,只要是打开的即可)。如果为真,则执行某些操作。
如果将此宏连接到CommandButton,则可以工作,但我无法在ThisWorkbook中的任何位置使其正常运行。
Sub CloseVBE()
    'use the MainWindow Property which represents
    ' the main window of the Visual Basic Editor - open the code window in VBE,
    ' but not the Project Explorer if it was closed previously:
    If Application.VBE.MainWindow.Visible = True Then
        MsgBox ""
        'close VBE window:
        Application.VBE.MainWindow.Visible = False
    End If

End Sub

我被要求使用下面的函数执行同样的操作,但我也无法使其正常工作:
Option Explicit

Private Declare Function FindWindow Lib "User32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function GetWindowText Lib "User32" Alias "GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetWindowTextLength Lib "User32" Alias "GetWindowTextLengthA" (ByVal hWnd As Long) As Long
Private Declare Function GetWindow Lib "User32" (ByVal hWnd As Long, ByVal wCmd As Long) As Long

Private Const GW_HWNDNEXT = 2

Function VBE_IsOpen() As Boolean

    Const appName       As String = "Visual Basic for Applications"

    Dim stringBuffer    As String
    Dim temphandle      As Long

    VBE_IsOpen = False

    temphandle = FindWindow(vbNullString, vbNullString)
    Do While temphandle <> 0
        stringBuffer = String(GetWindowTextLength(temphandle) + 1, Chr$(0))
        GetWindowText temphandle, stringBuffer, Len(stringBuffer)
        stringBuffer = Left$(stringBuffer, Len(stringBuffer) - 1)
        If InStr(1, stringBuffer, appName) > 0 Then
            VBE_IsOpen = True
            CloseVBE
        End If
        temphandle = GetWindow(temphandle, GW_HWNDNEXT)
    Loop

End Function

2018年1月23日,以下是对原问题的更新:

我找到了一段代码,正好符合我所需要的,但是在关闭工作簿时,宏报错并指出了错误的行:

Public Sub StopEventHook(lHook As Long)
    Dim LRet As Long
    Set lHook = 0'<<<------ When closing workbook, errors out on this line.
    If lHook = 0 Then Exit Sub
    LRet = UnhookWinEvent(lHook)    

    Exit Sub
End Sub

这里是整个代码,将其粘贴到常规模块中:

Option Explicit

Private Const EVENT_SYSTEM_FOREGROUND = &H3&
Private Const WINEVENT_OUTOFCONTEXT = 0

Private Declare Function SetWinEventHook Lib "user32.dll" (ByVal eventMin As Long, ByVal eventMax As Long, _
    ByVal hmodWinEventProc As Long, ByVal pfnWinEventProc As Long, ByVal idProcess As Long, _
    ByVal idThread As Long, ByVal dwFlags As Long) As Long
Private Declare Function GetCurrentProcessId Lib "kernel32" () As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, lpdwProcessId As Long) As Long

Private pRunningHandles As Collection

Public Function StartEventHook() As Long
  If pRunningHandles Is Nothing Then Set pRunningHandles = New Collection
  StartEventHook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, 0&, AddressOf WinEventFunc, 0, 0, WINEVENT_OUTOFCONTEXT)
  pRunningHandles.Add StartEventHook
End Function

Public Sub StopEventHook(lHook As Long)
  Dim LRet As Long
  On Error Resume Next
  Set lHook = 0  '<<<------ When closing workbook, errors out on this line.
    If lHook = 0 Then Exit Sub
    LRet = UnhookWinEvent(lHook)    

    Exit Sub
End Sub

Public Sub StartHook()
    StartEventHook
End Sub

Public Sub StopAllEventHooks()
  Dim vHook As Variant, lHook As Long
  For Each vHook In pRunningHandles
    lHook = vHook
    StopEventHook lHook
  Next vHook
End Sub

Public Function WinEventFunc(ByVal HookHandle As Long, ByVal LEvent As Long, _
                            ByVal hWnd As Long, ByVal idObject As Long, ByVal idChild As Long, _
                            ByVal idEventThread As Long, ByVal dwmsEventTime As Long) As Long
  'This function is a callback passed to the win32 api
  'We CANNOT throw an error or break. Bad things will happen.
  On Error Resume Next
  Dim thePID As Long

  If LEvent = EVENT_SYSTEM_FOREGROUND Then
    GetWindowThreadProcessId hWnd, thePID
    If thePID = GetCurrentProcessId Then
      Application.OnTime Now, "Event_GotFocus"
    Else
      Application.OnTime Now, "Event_LostFocus"
    End If
  End If

  On Error GoTo 0
End Function

Public Sub Event_GotFocus()
    Sheet1.[A1] = "Got Focus"
End Sub

Public Sub Event_LostFocus()
    Sheet1.[A1] = "Nope"
End Sub

将以下内容复制粘贴到 ThisWorkbook 中:
Option Explicit

Private Sub Workbook_BeforeClose(Cancel As Boolean)
    StopAllEventHooks
End Sub

Private Sub Workbook_Open()
    StartHook
End Sub

解释一下"errors out"的意思,包括完整的错误信息。并且格式化你的代码 - 高亮显示代码行,并按下ctrl+k或点击编辑工具栏上的"{}"按钮。 - Blorgbeard
1
只是一点小提示,如果你在WinProc中不调用CallNextHookEx(),可能会破坏钩子链。 - Ross Bush
错误信息为:对象必需。请理解我对使用API的知识几乎为零。这些函数是在网络上找到的。 - Jerry
我所说的“完全按照我的需求执行”的意思是...如果工作表失去焦点,它会提供通知。我需要API代码检测VBE是否具有焦点或已打开,并提供相同的通知。从那里开始...我可以用调用宏来执行后续所需的操作代替通知。 - Jerry
1
有趣的问题。出于好奇,你为什么想要这样做? - John Coleman
2个回答

2

好消息:只需要进行两个小改动就可以在我的系统上正常运行(Excel 2013 x86 on Win 8.1 x64):

  • Comment out the offending line (!)
  • Add the following declaration for UnhookWinEventat the top of the module:

    Private Declare Function UnhookWinEvent Lib "user32.dll" (ByVal hHook As Long)
    
Set x=y对象变量x设置为引用对象实例y。因此,它不能用于LongString或其他非对象类型。这就是当该行运行时出现Object Required错误的原因。有关Set的详细信息,请参见此问题的答案
另外,我不确定您从哪里获取了代码,但如果它起作用,错误行将使StopEventHook函数成为无操作函数。
Public Sub StopEventHook(lHook As Long)
    Dim LRet As Long
    On Error Resume Next
    Set lHook = 0  '<<<- The error line --- throws away the input parameter!
    If lHook = 0 Then Exit Sub    ' ... then this always causes the Sub to exit.
    LRet = UnhookWinEvent(lHook)    

    Exit Sub ' note: don't need this; you can remove it if you want.
End Sub

如果lHook被设置为0,下一行代码会导致Sub退出,因此钩子将永远不会被卸载。
可能的崩溃问题
有时关闭工作簿时Excel会崩溃,但并非总是如此。我认为这不是问题,因为我习惯了挂钩导致Office崩溃:)。然而,@RossBush在他的评论中指出,“如果您没有在WinProc中调用CallNextHookEx(),则可能会杀死钩子链”,这可能是问题的一部分。如果您遇到该问题并且无法解决,请提出一个单独的问题。肯定有许多人遇到了同样的问题!

谢谢您的回复。我正在运行Win 10 / Excel 2007。代码“尝试工作”,但这里存在犹豫和崩溃。我已决定采用不同的方法,结合T.M.列出的类似宏的方式。 - Jerry

2
为什么不使用带有“Workbook_Open”事件的“ThisWorkBook模块”?
在ThisWorkBook代码模块中的代码。
 Private Sub Workbook_Open()         ' or...  Sub Workbook_Activate()
   ' checkIsVBEOpen
   If Application.VBE.MainWindow.Visible = True Then
      MsgBox "VBE window is open", vbInformation
      ' do something
      ' ...
      ' close VBE window
        Application.VBE.MainWindow.Visible = False
    Else
      MsgBox "VBE window is NOT open"   ' do nothing else
   End If
End Sub

T.M. 感谢您的回复。我采纳了您的建议,并在例程模块中添加了定时函数。定时器会按要求检查并触发操作。再次感谢所有回复的人。 - Jerry
我需要将这个线程标记为“已解决”或类似的东西吗?如果是的话...怎么做? - Jerry
1
关于您的问题:Stack Overflow(以下简称SO)邀请您将一个答案标记为被采纳,并对有帮助或经过充分研究的答案进行投票,这可能对其他用户也有帮助(参见https://stackoverflow.com/help/someone-answers)。 - T.M.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接