窗口大小调整事件

4

问题

每当主Excel窗口被调整大小时,调用一个过程。

第一次尝试:

Sub Workbook_WindowResize(ByVal Wn As Window)
    Debug.Print Wn.Width & "x" & Wn.Height
End Sub

结果:

每当“内部”工作簿窗口大小调整时,子例程都会被调用,但当应用程序窗口大小调整时不会调用。即在包含应用程序实例的多文档界面子项上进行调整。

第二次尝试

Dim WithEvents App As Application
Private Sub App_WindowResize(ByVal Wb As Workbook, ByVal Wn As Window)
    Debug.Print Wn.Width & "x" & Wn.Height
End Sub

结果:

奇怪的是,之前发生的同样的事情又发生了,这让我感到惊讶。事件只在工作簿窗口调整大小时发生,而不是应用程序窗口。

因此,我开始研究使用Windows API。

有许多使用Windows API设置系统范围键盘和鼠标钩子的示例。这是相同的原理:

Public Enum enHookTypes
    WH_CALLWNDPROC = 4
    WH_CALLWNDPROCRET = 12
    WH_CBT = 5
    WH_DEBUG = 9
    WH_FOREGROUNDIDLE = 11
    WH_GETMESSAGE = 3
    WH_HARDWARE = 8
    WH_JOURNALPLAYBACK = 1
    WH_JOURNALRECORD = 0
    WH_MOUSE = 7
    WH_MSGFILTER = (-1)
    WH_SHELL = 10
    WH_SYSMSGFILTER = 6
    WH_KEYBOARD_LL = 13
    WH_MOUSE_LL = 14
    WH_KEYBOARD = 2
 End Enum

Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As LongPtr, ByVal lpfn As Long, ByVal hMod As Long, ByVal dwThreadId As Long) As LongPtr
Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long

Private Declare Function GetLastError Lib "kernel32" () As Long

'Ensure that your hook procedure does not interfere with the normal operation of other hook procedures
Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As Long

Public hndl As Long

Sub HookWindow()
    hndl = SetWindowsHookEx(WH_CALLWNDPROC, AddressOf measureWindow, Application.Hinstance, 0&)
    Debug.Print hndl & "~~" & GetLastError()
End Sub

Sub unhookWindow()
    ret = UnhookWindowsHookEx(hndl)
    Debug.Print ret
End Sub

Public Sub measureWindow(code As Long, wParam As Long, lParam As Long)
    If code > 0 Then
        Debug.Print ThisWorkbook.Windows(1).Width & "x" & ThisWorkbook.Windows(1).Height
    Else
        ret = CallNextHookEx(measureWindow, code, wParam, lParam)
    End If
End Sub

结果:

如果我替换掉以下内容中的 WH_CALLWNDPROC:

hndl = SetWindowsHookEx(WH_CALLWNDPROC, AddressOf measureWindow, Application.Hinstance, 0&)

使用 WH_KEYBOARD_LL 后,每次按键时都会调用子程序。同样地,如果我将其替换为 WH_MOUSE_LL,则每当鼠标移动或按下鼠标按钮时都会调用子程序。

问题在于,当我尝试将子程序钩入 WH_CALLWNDPROC 时,什么也没有发生?

为什么?

我仍然不确定,但对于 enHookTypes 中的所有 ENUM(除了 WH_MOUSE_LLWH_KEYBOARD_LL),情况也是如此。浏览 WinAPI 文档时,我读到您可以使用来自 kernel32.dll 的 GetLastError 来获得一些有关操作失败原因的指示。

到目前为止,我获得的错误号码(以十进制表示)为 错误5(用于 JOURNAL 钩子),其余的为 错误1428

最终这也失败了。


2
错误代码 1428 的翻译是 "不能在没有模块句柄的情况下设置非本地钩子。" 这是可以预料的。您正在安装全局钩子,但指定了任意模块的模块句柄(在本例中为应用程序的主模块)。这应该是实现钩子过程的模块。要么将您的钩子过程放入 DLL 中,并传递该模块句柄,要么安装本地(线程)钩子。不清楚您为什么一开始就尝试安装全局钩子。 - IInspectable
1
如果你只需要捕获窗口消息,SetWindowLongPtr 可能是你要找的。 - Comintern
1
你可以使用SetWindowLongPtr函数来设置自己的WndProc回调函数(参见GWLP_WNDPROC)。这样可以让你响应特定窗口的窗口消息。你基本上是将自己注入到Excel窗口的消息队列中。 - Comintern
1
@IInspectable - VBA不是这个千年的产品。创建SUBCLASSPROC并非易事(委婉地说)。 - Comintern
1
当 Rich Edit 控件被调整大小时,会发送 EM_REQUESTRESIZE 消息。普通窗口在调整大小时会接收到 WM_SIZEWM_SIZINGWM_WINDOWPOSCHANGED 等一些其他消息。它们在不同的时间和不同的目的下使用。你需要查找它们以确定需要处理哪一个。如果不确定,请使用类似 Spy++ 的工具观察窗口消息在调整窗口大小时生成的情况。 - IInspectable
显示剩余12条评论
2个回答

1

Application.Windows 是所有在 Application 中打开的 Workbook 窗口对象的集合。当非最大化窗口改变大小时,会触发 WindowResize 事件。Workbook_WindowResize(ByVal Wn As Window) 事件在 workbook 对象本身中公开。Application_WindowResize(ByVal Wb as Workbook, ByVal Wn As Window) 事件与 Application 中的任何/所有工作簿有关,当非最大化工作簿的窗口大小改变时会触发。因此,事件传递的引用不同。在第一种情况下,是工作簿对象内触发事件的工作簿的窗口,没有疑问这是哪个窗口(它是“我”工作簿的窗口)。当在应用程序级别触发事件时,它既是 Workbook,也是那个工作簿的窗口,因为与事件相关的工作簿需要被识别 :) Excel 没有针对 App 窗口本身的 “Resize” 事件,您需要使用 APIs。

在较新版本的 Excel(过去的2010年之后),每个 Excel 应用程序窗口只有一个工作簿,工作簿的窗口始终以旧意义上的最大化状态显示,并且 Workbook 和 Application 事件都指向同一个工作簿,可以按您所需正常工作。


-1
创建一个定时器事件,每隔几秒检查和比较宽度的解决方案...
Sub my_ONTIME()
    application.OnTime Now + TimeValue("00:00:2"), "my_TIMEREVENT"
End Sub
Sub my_TIMEREVENT()
    If application.Width <> EWIDTHR Then ESCREENRESIZE
    my_ONTIME
End Sub
Sub ESCREENRESIZE()
    Dim EWIDTH As Single
    Dim ESIDE As Single
    Dim EMID As Single
    EWIDTH = application.Width
    EWIDTHR = EWIDTH
    If EWIDTH < 500 Then
        EWIDTH = 500
        application.Width = 500
    End If
    EMID = 80 * 5.41
    ESIDE = ((EWIDTH - EMID) / 2) / 5.41
    Sheet1.Columns("A:A").ColumnWidth = ESIDE
    Sheet1.Columns("C:C").ColumnWidth = ESIDE
End Sub

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