【问题标题】:Display a message box with a timeout value显示带有超时值的消息框
【发布时间】:2011-05-15 12:25:33
【问题描述】:

问题来自这样的代码。

Set scriptshell = CreateObject("wscript.shell")
    Const TIMEOUT_IN_SECS = 60
    Select Case scriptshell.popup("Yes or No? leaving this window for 1 min is the same as clicking Yes.", TIMEOUT_IN_SECS, "popup window", vbYesNo + vbQuestion)
        Case vbYes
            Call MethodFoo
        Case -1
            Call MethodFoo
    End Select

这是一种从 VBA(或 VB6)显示带有超时的消息框的简单方法。

在 Excel 2007 中(有时在 Internet Explorer 中也会出现)弹出窗口不会超时,而是等待用户输入。

这个问题很难调试,因为它只是偶尔发生,我不知道重现问题的步骤。我认为这是 Office 模式对话框和 Excel 无法识别超时已过期的问题。

http://social.technet.microsoft.com/Forums/en-US/ITCG/thread/251143a6-e4ea-4359-b821-34877ddf91fb/

我找到的解决方法是:

A.使用 Win32 API 调用

Declare Function MessageBoxTimeout Lib "user32.dll" Alias "MessageBoxTimeoutA" ( _
ByVal hwnd As Long, _
ByVal lpText As String, _
ByVal lpCaption As String, _
ByVal uType As Long, _
ByVal wLanguageID As Long, _
ByVal lngMilliseconds As Long) As Long

Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long

Public Sub MsgBoxDelay()
    Const cmsg As String = "Yes or No? leaving this window for 1 min is the same as clicking Yes."
    Const cTitle As String = "popup window"
    Dim retval As Long
    retval = MessageBoxTimeout(FindWindow(vbNullString, Title), cmsg, cTitle, 4, 0, 60000)

    If retval <> 7 Then
        Call MethodFoo
    End If

End Sub  

B.使用设计为看起来像消息框的 VBA 用户窗体的手动计时器。使用全局变量或类似变量来保存需要传递回调用代码的任何状态。确保使用提供的 vbModeless 参数调用用户窗体的 Show 方法。

C.在 MSHTA 进程中封装对 wscript.popup 方法的调用,这将允许代码在进程外运行并避免 Office 的模式性质。

CreateObject("WScript.Shell").Run "mshta.exe vbscript:close(CreateObject(""WScript.Shell"").Popup(""Test"",2,""Real%20Time%20Status%20Message""))"

A、B 或 C 或您自己的答案在 VBA 中显示具有超时值的消息框的最佳方式是什么?

【问题讨论】:

  • 不确定你的问题是什么......但我觉得 win32 API 解决方案没问题。
  • 对不起,如果我不够清楚。我认为问题的前两句话澄清了这一点。我会重新编辑它。

标签: excel vba winapi messagebox wsh


【解决方案1】:

这是一个很长的答案,但有很多理由要涵盖:这也是一个迟到的回复,但是自从对这个(和类似问题)的一些回复已经发布在堆栈上之后,情况发生了变化。这就像三相交流电上的真空吸尘器一样糟糕,因为它们在发布时是很好的答案,并且经过了很多思考。

简短的版本是:我注意到一年前在 VBA 中 Script WsShell Popup 解决方案停止为我工作,我为 VBA MsgBox 函数编写了一个有效的 API 计时器回调。

如果您急需答案,请直接跳至标题VBA 代码以调用带有超时的消息框 下的代码 - 我确实做到了,我确实有数千个自我实例-dismissing 'MsgPopup' 替代 VBA.MsgBox 来编辑,下面的代码适合一个独立的模块。

但是,这里的 VBA 编码人员(包括我自己)需要解释一下为什么完美的代码似乎不再有效。如果您了解其中的原因,您或许可以对隐藏在文本中的“取消”对话框使用部分解决方法。

我注意到一年前 Script WsShell Popup 解决方案在 VBA 中停止为我工作 - 'SecondsToWait' 超时被忽略,对话框就像熟悉的 VBA.MsgBox 一样悬空:

MsgPopup = objWShell.PopUp(Prompt, SecondsToWait, Title, Buttons)

我想我知道原因:您不能再从打开它的线程以外的任何地方向对话窗口发送 WM_CLOSE 或 WM_QUIT 消息。同样,User32 DestroyWindow() 函数不会关闭对话框窗口,除非它被打开对话框的线程调用。

Redmond 的某个人不喜欢在后台运行脚本并向所有那些停止工作的重要警告发送 WM_CLOSE 命令的想法(而且,如今,让它们永久消失需要本地管理员权限)。

我无法想象谁会写出这样的剧本,这是个糟糕的主意!

该决定会产生后果和附带损害:单线程 VBA 环境中的 WsScript.Popup() 对象使用 Timer 回调实现其“SecondsToWait”超时,并且该回调发送 WM_CLOSE 消息或类似消息。 .. 在大多数情况下会被忽略,因为它是回调线程,而不是对话框的所有者线程。

可能让它在带有“取消”按钮的弹出窗口上工作,一两分钟后就会明白为什么会这样。

我尝试编写一个定时器回调到 WM_CLOSE 弹出窗口,但在大多数情况下,这对我来说也失败了。

我尝试了一些特殊的 API 回调来干扰 VBA.MsgBox 和 WsShell.Popup 窗口,现在我可以告诉你它们不起作用。您无法处理不存在的内容:这些对话框窗口非常简单,其中大多数根本不包含任何功能,除了按钮单击中的响应 - 是、否、确定、取消、中止、重试、忽略和帮助。

“取消”是一个有趣的选项:当您指定 vbOKCancelvbRetryCancelvbYesNoCancel 时,您似乎可以从用于内置对话框的原始 Windows API 获得免费赠品 - “取消”功能会自动使用对话框菜单栏中的“关闭”按钮实现(其他按钮无法实现,但可以随意尝试使用包含“忽略”的对话框),这意味着......

如果 WsShell.Popup() 对话框有“取消”选项,它们有时会响应 SecondsToWait 超时。

objWShell.PopUp("Test Me", 10, "Dialog Test", vbQuestion + vbOkCancel)

如果您只想让 WsShell.Popup() 函数再次响应 SecondsToWait 参数,那么对于阅读本文的人来说,这可能是一个足够好的解决方法。

这也意味着您可以在回调中使用 SendMessage() API 调用将 WM_CLOSE 消息发送到“取消”对话框:

SendMessage(hwndDlgBox, WM_CLOSE, ByVal 0&, ByVal 0&)

严格来说,这应该只适用于WM_SYSCOMMAND, SC_CLOSE 消息 - 命令栏中的“关闭”框是一个带有特殊命令类别的“系统”菜单,但就像我说的,我们从Windows API。

我得到了它的工作,我开始思考:如果我只能处理那里的东西,也许我最好找出实际存在的东西...

答案很明显:对话框有自己的一组 WM_COMMAND 消息参数 -

' Dialog window message parameters, replicating Enum vbMsgBoxResult:
CONST dlgOK      As Long = 1
CONST dlgCANCEL  As Long = 2
CONST dlgABORT   As Long = 3
CONST dlgRETRY   As Long = 4
CONST dlgIGNORE  As Long = 5
CONST dlgYES     As Long = 6
CONST dlgNO      As Long = 7

而且,由于这些是“用户”消息,它们将用户响应返回给对话框的调用者(即调用线程),因此对话框很乐意接受它们并自行关闭。

您可以询问对话窗口以查看它是否实现了特定命令,如果是,您可以发送该命令:

If GetDlgItem(hWndMsgBox, vbRetry) <> 0 Then
    SendMessage hWndMsgBox, WM_COMMAND, vbRetry, 0&
    Exit For
End If

剩下的挑战是检测“超时”并拦截返回的消息框响应,并替换我们自己的值:如果我们遵循 WsShell.Popup() 函数建立的约定,则为 -1。所以我们的 'msgPopup' 包装器用于一个带有超时的消息框需要做三件事:

  1. 调用我们的 API Timer 以延迟关闭对话框;
  2. 打开消息框,传入常用参数;
  3. 要么:检测超时并替换“超时”响应...
    ...或者返回用户对对话框的响应,如果他们在 时间

在其他地方,我们需要为所有这些声明 API 调用,并且我们绝对必须有一个公开声明的“TimerProc”函数供 Timer API 调用。该函数必须存在,并且必须在没有错误或断点的情况下运行到“结束函数” - 任何中断,API Timer() 将调用操作系统的愤怒。

使用超时调用消息框的 VBA 代码:

Option Explicit
Option Private Module  

' Nigel Heffernan January 2016 

' Modified from code published by Microsoft on MSDN, and on StackOverflow: this code is in  ' the public domain.  
' This module implements a message box with a 'timeout'  
' It is similar to implementations of the WsShell.Popup() that use a VB.MessageBox interface
' with an additional 'SecondsToWait' or 'Timeout' parameter.  

Private m_strCaption As String 

Public Function MsgPopup(Optional Prompt As String, _
                         Optional Buttons As VbMsgBoxStyle = vbOKOnly, _
                         Optional Title As String, _
                         Optional SecondsToWait As Long = 0) As VbMsgBoxResult  

' Replicates the VBA MsgBox() function, with an added parameter to automatically dismiss the message box after n seconds
' If dismissed automatically, this will return -1: NOT 'cancel', nor the default button choice.  

Dim TimerStart As Single  

If Title = "" Then
    Title = ThisWorkbook.Name
End If  

If SecondsToWait > 0 Then
    ' TimedmessageBox launches a callback to close the MsgBox dialog
    TimedMessageBox Title, SecondsToWait
    TimerStart = VBA.Timer
End If   

MsgPopup = MsgBox(Prompt, Buttons, Title)    
If SecondsToWait   > 0 Then
    ' Catch the timeout, substitute -1 as the response
    If (VBA.Timer - TimerStart) >= SecondsToWait Then
        MsgPopup = -1
    End If
End If  

End Function   

Public Function MsgBoxResultText(ByVal MsgBoxResult As VbMsgBoxResult) As String  
' Returns a text value for the integers returned by VBA MsgBox() and WsShell.Popup() dialogs  
' Additional value: 'TIMEOUT', returned when the MsgBoxResult = -1  ' All other values return the string 'ERROR'    
On Error Resume Next    

If (MsgBoxResult >= vbOK) And (MsgBoxResult <= vbNo) Then
    MsgBoxResultText = Split("ERROR,OK,CANCEL,ABORT,RETRY,IGNORE,YES,NO,", ",")(MsgBoxResult)
ElseIf MsgBoxResult = dlgTIMEOUT Then
    MsgBoxResultText = "TIMEOUT"
Else
    MsgBoxResultText = "ERROR"
End If  

End Function
'
'
'
'
'
'
'
'
'
'
Private Property Get MessageBox_Caption() As String
    MessageBox_Caption = m_strCaption
End Property  

Private Property Let MessageBox_Caption(NewCaption As String)
    m_strCaption = NewCaption 
End Property    

Private Sub TimedMessageBox(Caption As String, Seconds As Long)
On Error Resume Next

    ' REQUIRED for Function msgPopup
   ' Public Sub  TimerProcMessageBox  MUST EXIST  
    MessageBox_Caption = Caption  
    SetTimer 0&, 0&, Seconds * 1000, AddressOf TimerProcMessageBox  
    Debug.Print "start Timer " & Now  

End Sub  

#If VBA7 And Win64 Then     ' 64 bit Excel under 64-bit windows   
                            ' Use LongLong and LongPtr    

    Public Sub TimerProcMessageBox(ByVal hwnd As LongPtr, _
                                   ByVal wMsg As Long, _
                                   ByVal idEvent As LongPtr, _
                                   ByVal dwTime As LongLong)
    On Error Resume Next  

    ' REQUIRED for Function msgPopup
    ' https://msdn.microsoft.com/en-US/library/windows/desktop/ms644907(v=vs.85).aspx  
    ' Closes a dialog box (Shell.Popup or VBA.MsgBox) having a caption stored in MessageBox_Caption
    ' This TimerProc sends *any* message that can close the dialog: the objective is solely to close
    ' the dialog and resume the VBA thread. Your caller must detect the expired TimerProc interval
    ' and insert a custom return value (or default) that signals the 'Timeout' for responses.  
    ' The MsgPopup implementation in this project returns -1 for this 'Timeout' 

    Dim hWndMsgBox As LongPtr   ' Handle to VBA MsgBox 

    KillTimer hWndMsgBox, idEvent  
    hWndMsgBox = 0
    hWndMsgBox = FindWindow("#32770", MessageBox_Caption)  

    If hWndMsgBox   <  > 0 Then  
        ' Enumerate WM_COMMAND values
        For iDlgCommand = vbOK To vbNo
            If GetDlgItem(hWndMsgBox, iDlgCommand)   <> 0 Then
                SendMessage hWndMsgBox, WM_COMMAND, iDlgCommand, 0&
                Exit For
            End If
        Next iDlgCommand  
    End If 

    End Sub  

#ElseIf VBA7 Then    ' 64 bit Excel in all environments  
                     ' Use LongPtr only   

    Public Sub TimerProcMessageBox(ByVal hwnd As LongPtr, _
                                   ByVal wMsg As Long, _
                                   ByVal idEvent As LongPtr, _
                                   ByVal dwTime As Long)
    On Error Resume Next     

    ' REQUIRED for Function msgPopup
    ' https://msdn.microsoft.com/en-US/library/windows/desktop/ms644907(v=vs.85).aspx  
    ' Closes a dialog box (Shell.Popup or VBA.MsgBox) having a caption stored in MessageBox_Caption
    ' This TimerProc sends *any* message that can close the dialog: the objective is solely to close
    ' the dialog and resume the VBA thread. Your caller must detect the expired TimerProc interval
    ' and insert a custom return value (or default) that signals the 'Timeout' for responses.      
    ' The MsgPopup implementation in this project returns -1 for this 'Timeout' 

    Dim hWndMsgBox  As LongPtr          ' Handle to VBA MsgBox

    Dim iDlgCommand As VbMsgBoxResult   ' Dialog command values: OK, CANCEL, YES, NO, etc  
    KillTimer hwnd, idEvent  
    hWndMsgBox = 0
    hWndMsgBox = FindWindow("#32770", MessageBox_Caption)  

    If hWndMsgBox   <  > 0 Then  
        ' Enumerate WM_COMMAND values 
        For iDlgCommand = vbOK To vbNo
            If GetDlgItem(hWndMsgBox, iDlgCommand)   <> 0 Then
                SendMessage hWndMsgBox, WM_COMMAND, iDlgCommand, 0&
                Exit For
            End If
        Next iDlgCommand  
    End If  

    End Sub  

#Else    ' 32 bit Excel   

    Public Sub TimerProcMessageBox(ByVal hwnd As Long, _
                                   ByVal wMsg As Long, _
                                   ByVal idEvent As Long, _
                                   ByVal dwTime As Long)
    On Error Resume Next  

    ' REQUIRED for Function msgPopup  
    ' The MsgPopup implementation in this project returns -1 for this 'Timeout'  

    Dim hWndMsgBox As Long    ' Handle to VBA MsgBox  

    KillTimer hwnd, idEvent  
    hWndMsgBox = 0
    hWndMsgBox = FindWindow("#32770", MessageBox_Caption)  

    If hWndMsgBox   <  > 0 Then  
        ' Enumerate WM_COMMAND values 
        For iDlgCommand = vbOK To vbNo
            If GetDlgItem(hWndMsgBox, iDlgCommand)   <> 0 Then
                SendMessage hWndMsgBox, WM_COMMAND, iDlgCommand, 0&
                Exit For
            End If
        Next iDlgCommand  
    End If  

    End Sub  

#End If

这里是 API 声明 - 请注意 VBA7、64 位 Windows 和普通 32 位的条件声明:

' Explanation of compiler constants for 64-Bit VBA and API declarations :
' https://msdn.microsoft.com/en-us/library/office/ee691831(v=office.14).aspx

#If VBA7 And Win64 Then     ' 64 bit Excel under 64-bit windows ' Use LongLong and LongPtr
    Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
                                    (ByVal lpClassName As String, _
                                     ByVal lpWindowName As String) As LongPtr
    Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" _
                                    (ByVal hwnd As LongPtr, _
                                     ByVal wMsg As Long, _
                                     ByVal wParam As Long, _
                                     ByRef lParam As Any _
                                     ) As LongPtr
    Private Declare PtrSafe Function SetTimer Lib "user32" _
                                    (ByVal hwnd As LongPtr, _
                                     ByVal nIDEvent As LongPtr, _
                                     ByVal uElapse As Long, _
                                     ByVal lpTimerFunc As LongPtr _
                                     ) As Long
     Public Declare PtrSafe Function KillTimer Lib "user32" _
                                    (ByVal hwnd As LongPtr, _
                                     ByVal nIDEvent As LongPtr _
                                     ) As Long
    Private Declare PtrSafe Function GetDlgItem Lib "user32" _
                                    (ByVal hWndDlg As LongPtr, _
                                     ByVal nIDDlgItem As Long _
                                     ) As LongPtr

#ElseIf VBA7 Then           ' VBA7 in all environments, including 32-Bit Office  ' Use LongPtr for ptrSafe declarations, LongLong is not available

    Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
                                    (ByVal lpClassName As String, _
                                     ByVal lpWindowName As String) As LongPtr
    Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" _
                                    (ByVal hwnd As LongPtr, _
                                     ByVal wMsg As Long, _
                                     ByVal wParam As Long, _
                                     ByRef lParam As Any _
                                     ) As LongPtr
    Private Declare PtrSafe Function SetTimer Lib "user32" _
                                    (ByVal hwnd As LongPtr, _
                                     ByVal nIDEvent As Long, _
                                     ByVal uElapse As Long, _
                                     ByVal lpTimerFunc As LongPtr) As LongPtr
    Private Declare PtrSafe Function KillTimer Lib "user32" _
                                    (ByVal hwnd As LongPtr, _
                                     ByVal nIDEvent As Long) As Long
    Private Declare PtrSafe Function GetDlgItem Lib "user32" _
                                    (ByVal hWndDlg As LongPtr, _
                                     ByVal nIDDlgItem As Long _
                                     ) As LongPtr
#Else
    Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
                            (ByVal lpClassName As String, _
                             ByVal lpWindowName As String) As Long
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
                            (ByVal hwnd As Long, _
                             ByVal wMsg As Long, _
                             ByVal wParam As Long, _
                             ByRef lParam As Any _
                             ) As Long
    Private Declare Function SetTimer Lib "user32" _
                            (ByVal hwnd As Long, _
                             ByVal nIDEvent As Long, _
                             ByVal uElapse As Long, _
                             ByVal lpTimerFunc As Long) As Long
    Public Declare Function KillTimer Lib "user32" _
                            (ByVal hwnd As Long, _
                             ByVal nIDEvent As Long) As Long
    Private Declare Function GetDlgItem Lib "user32" _ 
                             (ByVal hWndDlg, ByVal nIDDlgItem As Long) As Long
#End If

Private Enum WINDOW_MESSAGE
    WM_ACTIVATE = 6
    WM_SETFOCUS = 7
    WM_KILLFOCUS = 8
    WM_PAINT = &HF
    WM_CLOSE = &H10
    WM_QUIT = &H12
    WM_COMMAND = &H111
    WM_SYSCOMMAND = &H112
End Enum

' Dialog Box Command IDs - replicates vbMsgBoxResult, with the addition of 'dlgTIMEOUT'
Public Enum DIALOGBOX_COMMAND
    dlgTIMEOUT = -1
    dlgOK = 1
    dlgCANCEL = 2
    dlgABORT = 3
    dlgRETRY = 4
    dlgIGNORE = 5
    dlgYES = 6
    dlgNO = 7
End Enum

最后一点:我欢迎经验丰富的 MFC C++ 开发人员提出改进建议,因为您将更好地掌握“对话”窗口背后的基本 Windows 消息传递概念 - 我使用一种过于简单的语言很可能我的理解过于简单化了,导致我的解释完全错误。

【讨论】:

  • 发现这个答案非常有用。我已经在我的项目中实现了这一点。我还“注意到一年前 Script WsShell Popup 解决方案在 VBA 中停止为我工作”,我现在才刚刚通过并更新我的代码。讨厌事情停止工作,但很高兴我决定寻找解决方案。
  • 不错的帖子,但我发现微软又得到了我们。最近,他们开始将任何带有 DECLARES 的 VBA 代码标记为安全风险。 IT 有办法处理异常,但那很混乱。我将尝试对这些使用非模态对话框,然后进行轮询。
  • @Tuntable 你在 DECLARE 中有 ptrSafe 吗?
  • @Tuntable - 另外,您能告诉我们更多关于您的操作环境和您的 MS-Office 版本的信息吗?这听起来像是一个被误导的天才,有一个系统管理员徽章在用户配置文件上设置策略。
  • 与 PtrSafe 无关。但与被误导的系统管理员有很大关系。而且,微软最近发生了一个变化,我认为他们禁用或至少可以很容易地使用 DECLARE 语句禁用任何 VBA。也就是说,我想我可以通过非模态对话框和一些hacky来完成它。
【解决方案2】:

选择答案 A。Win32 解决方案。这符合要求,并且从目前的测试来看是稳健的。

Declare Function MessageBoxTimeout Lib "user32.dll" Alias "MessageBoxTimeoutA" ( _ 
ByVal hwnd As Long, _ 
ByVal lpText As String, _ 
ByVal lpCaption As String, _ 
ByVal uType As Long, _ 
ByVal wLanguageID As Long, _ 
ByVal lngMilliseconds As Long) As Long 

Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" ( _ 
ByVal lpClassName As String, _ 
ByVal lpWindowName As String) As Long 

Public Sub MsgBoxDelay() 
    Const cmsg As String = "Yes or No? leaving this window for 1 min is the same as clicking Yes." 
    Const cTitle As String = "popup window" 
    Dim retval As Long 
    retval = MessageBoxTimeout(FindWindow(vbNullString, Title), cmsg, cTitle, 4, 0, 60000) 

    If retval <> 7 Then 
        Call MethodFoo 
    End If 

End Sub

【讨论】:

  • 您可以使用通用 APC SetTimer() 调用并在回调中弹出一个 msgbox。
【解决方案3】:

简单

Call CreateObject("WScript.Shell").Popup("Timed message box", 1, "Title", vbOKOnly)

【讨论】:

  • umm ...问题是...弹出窗口会意外地不会超时 此外,如果您检查问题中的代码,您会注意到我的代码与您的代码几乎完全相同如上所述。
【解决方案4】:

从这篇文章中的示例开始,我的最终代码如下:

' Coded by Clint Smith
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' tMsgBox Function (Timered Message Box)
' By Clint Smith, clintasm@gmail.com
' Created 04-Sep-2014
' Updated for 64-bit 03-Mar-2020
' This provides an publicly accessible procedure named
' tMsgBox that when invoked instantiates a timered
' message box.  Many constants predefined for easy use.
' There is also a global result variable tMsgBoxResult.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''


Public Const mbBTN_Ok = vbOKOnly                       'Default
Public Const mbBTN_OkCancel = vbOKCancel
Public Const mbBTN_AbortRetryIgnore = vbAbortRetryIgnore
Public Const mbBTN_YesNoCancel = vbYesNoCancel
Public Const mbBTN_YesNo = vbYesNo
Public Const mbBTN_RetryCancel = vbRetryCancel
Public Const mbBTN_CanceTryagainContinue = &H6
Public Const mbICON_Stop = vbCritical
Public Const mbICON_Question = vbQuestion
Public Const mbICON_Exclaim = vbExclamation
Public Const mbICON_Info = vbInformation
Public Const mbBTN_2ndDefault = vbDefaultButton2
Public Const mbBTN_3rdDefault = vbDefaultButton3
Public Const mbBTN_4rdDefault = vbDefaultButton4
Public Const mbBOX_Modal = vbSystemModal
Public Const mbBTN_AddHelp = vbMsgBoxHelpButton
Public Const mbTXT_RightJustified = vbMsgBoxRight
Public Const mbWIN_Top = &H40000                        'Default

Public Const mbcTimeOut = 32000
Public Const mbcOk = vbOK
Public Const mbcCancel = vbCancel
Public Const mbcAbort = vbAbort
Public Const mbcRetry = vbRetry
Public Const mbcIgnore = vbIgnore
Public Const mbcYes = vbYes
Public Const mbcNo = vbNo
Public Const mbcTryagain = 10
Public Const mbcContinue = 11

Public Const wAccessWin = "OMain"
Public Const wExcelWin = "XLMAIN"
Public Const wWordWin = "OpusApp"

Public tMsgBoxResult As Long

#If VBA7 Then

  Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
    (ByVal lpClassName As String, _
    ByVal lpWindowName As String) As Long

  Public Declare PtrSafe Function tMsgBoxA Lib "user32.dll" Alias "MessageBoxTimeoutA" ( _
    ByVal hwnd As Long, _
    ByVal lpText As String, _
    ByVal lpCaption As String, _
    ByVal uType As Long, _
    ByVal wLanguageID As Long, _
    ByVal lngMilliseconds As Long) As Long

#Else

  Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
    (ByVal lpClassName As String, _
    ByVal lpWindowName As String) As Long

  Public Declare Function tMsgBoxA Lib "user32.dll" Alias "MessageBoxTimeoutA" ( _
    ByVal hwnd As Long, _
    ByVal lpText As String, _
    ByVal lpCaption As String, _
    ByVal uType As Long, _
    ByVal wLanguageID As Long, _
    ByVal lngMilliseconds As Long) As Long

#End If

Public Sub tMsgBox( _
    Optional sMessage As String = "Default: (10 sec timeout)" & vbLf & "Coded by Clint Smith", _
    Optional sTitle As String = "Message Box with Timer", _
    Optional iTimer As Integer = 10, _
    Optional hNtype As Long = mbBTN_Ok + mbWIN_Top, _
    Optional hLangID As Long = &H0, _
    Optional wParentType As String = vbNullString, _
    Optional wParentName As String = vbNullString)

    tMsgBoxResult = tMsgBoxA(FindWindow(wParentType, wParentName), sMessage, sTitle, hNtype, hLangID, 1000 * iTimer)
End Sub

【讨论】:

  • +1 的努力,因为它有效。但是我发现1秒的时间分辨率太大了,应该是1/10,更一般的参数应该和常规的MsgBox一样,以便快速替换。
  • @PatrickHonorez 谢谢!我稍后会为此更新!感谢反馈,是的,它确实有效,我在 Word、Excel 和 Access 中使用过它。也可以通过编辑最后一行来更改分辨率。 :D
【解决方案5】:
Private Declare Function MsgBoxTimeout _
     Lib "user32" _
     Alias "MessageBoxTimeoutA" ( _
         ByVal hwnd As Long, _
         ByVal MsgText As String, _
         ByVal Title As String, _
         ByVal MsgBoxType As VbMsgBoxStyle, _
         ByVal wlange As Long, _
         ByVal Timeout As Long) _
    As Long
    Dim btnOK As Boolean
    Dim btnCancel As Boolean
    Dim MsgTimeOut As Boolean

Option Explicit

Sub Main

    AutoMsgbox("Message Text", "Title", vbOkCancel , 5) '5 sec TimeOut

    MsgBox("Pressed OK: " & btnOK & vbNewLine & "Pressed Cancel: " & btnCancel & vbNewLine &"MsgBox Timeout: " & MsgTimeOut)

End Sub

Function AutoMsgbox(MsgText , Title , MsgBoxType , Timeout)

    Dim ReturnValue
    Dim TimeStamp As Date
    TimeStamp = DateAdd("s",Timeout,Now)
    Dim MsgText1 As String

    Dim TimeOutCounter As Integer

    For TimeOutCounter = 0 To Timeout

        MsgText1 = MsgText & vbNewLine & vbNewLine & " Auto Selction in " & Timeout - TimeOutCounter & " [s]"

        ReturnValue =  MsgBoxTimeout(0 , MsgText1 , Title, MsgBoxType, 0 ,1000)

        Select Case ReturnValue
            Case 1
                btnOK       = True
                btnCancel   = False
                btnAbort    = False
                btnRetry    = False
                btnIgnore   = False
                btnYes      = False
                btnNo       = False
                MsgTimeOut  = False
                Exit Function
            Case 2
                btnOK       = False
                btnCancel   = True
                btnAbort    = False
                btnRetry    = False
                btnIgnore   = False
                btnYes      = False
                btnNo       = False
                MsgTimeOut  = False
                Exit Function
            Case 3
                btnOK       = False
                btnCancel   = False
                btnAbort    = True
                btnRetry    = False
                btnIgnore   = False
                btnYes      = False
                btnNo       = False
                MsgTimeOut  = False
                Exit Function
            Case 4
                btnOK       = False
                btnCancel   = False
                btnAbort    = False
                btnRetry    = True
                btnIgnore   = False
                btnYes      = False
                btnNo       = False
                MsgTimeOut  = False
                Exit Function
            Case 5
                btnOK       = False
                btnCancel   = False
                btnAbort    = False
                btnRetry    = False
                btnIgnore   = True
                btnYes      = False
                btnNo       = False
                MsgTimeOut  = False
                Exit Function
            Case 6
                btnOK       = False
                btnCancel   = False
                btnAbort    = False
                btnRetry    = False
                btnIgnore   = False
                btnYes      = True
                btnNo       = False
                MsgTimeOut  = False
                Exit Function
            Case 7
                btnOK       = False
                btnCancel   = False
                btnAbort    = False
                btnRetry    = False
                btnIgnore   = False
                btnYes      = False
                btnNo       = True
                MsgTimeOut  = False
                Exit Function
            Case 32000
                btnOK       = False
                btnCancel   = False
                btnAbort    = False
                btnRetry    = False
                btnIgnore   = False
                btnYes      = False
                btnNo       = False
                MsgTimeOut  = True

    Next TimeOutCounter

End Function

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-05
    • 1970-01-01
    • 2011-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-02
    相关资源
    最近更新 更多