在 C# 中监听另一个窗口调整大小事件

发布于 2024-09-02 12:42:40 字数 343 浏览 3 评论 0原文

我正在实现一个小型应用程序(观察者),它需要将自身“附加”到另一个窗口(观察者)的底部。后者不是应用程序内的窗口。

此时,我通过获取窗口的 hWnd 并在线程中定期查询观察到的窗口的位置,相应地移动观察者窗口来解决。

然而,这是一个非常不优雅的解决方案。我想做的是监听被观察窗口的调整大小事件,以便观察者仅在必要时做出反应。

我认为我应该使用钩子,并且我发现了很多方法来做到这一点,但是我对 C WinAPI 的缺乏知识阻碍了我理解我需要创建哪个钩子以及如何创建(pinvoke/参数/等)。

我很确定这是非常微不足道的,并且一些熟悉 C/C++ 和 WinAPI 的人会手头有答案;)

谢谢

I am implementing a small application (observer) that needs to "attach" itself to the bottom of another window (observed). The latter is not a window inside the application.

At this moment I solved by getting the hWnd of the window and querying periodically in a thread the location of the observed window, moving the observer window accordingly.

However this is a very inelegant solution. What I would like to do is to listen to the resize event of the observed window so that the observer will react only when necessary.

I assume I should use a hook, and I found plenty of ways of doing it, but my lack of knowledge of the C WinAPI is blocking me in understanding which hook I need to create and how (pinvoke/parameters/etc).

I'm pretty sure this is quite trivial, and some of you familiar with C/C++ and WinAPI will have the answer ready at hand ;)

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

北陌 2024-09-09 12:42:40

扩展 Chris Taylor 的答案:您可以使用 ManagedWinApi,而不是自己进行本机互操作,其中包含 钩子类。

编辑:使用 ManagedWinApi。代码中的某个位置:

Hook MyHook = new Hook(HookType.WH_CALLWNDPROC, false, false);
MyHook.Callback += MyHookCallback;
MyHook StartHook();

对于回调,请参考 CallWndProcCWPSTRUCT

private static int MyHookCallback(int code, IntPtr wParam, IntPtr lParam, ref bool callNext)
{
    if (code >= 0)
    {
        // You will need to define the struct
        var message = (CWPSTRUCT)Marshal.PtrToStructure(lParam, typeof(CWPSTRUCT));
        // Do something with the data
    }
    return 0; // Return value is ignored unless you set callNext to false
}

Expanding on Chris Taylor's answer: Instead of doing the native interop yourself, you can use ManagedWinApi, which contains a Hook class.

EDIT: To use ManagedWinApi. Somewhere in your code:

Hook MyHook = new Hook(HookType.WH_CALLWNDPROC, false, false);
MyHook.Callback += MyHookCallback;
MyHook StartHook();

For the callback, reference CallWndProc and CWPSTRUCT:

private static int MyHookCallback(int code, IntPtr wParam, IntPtr lParam, ref bool callNext)
{
    if (code >= 0)
    {
        // You will need to define the struct
        var message = (CWPSTRUCT)Marshal.PtrToStructure(lParam, typeof(CWPSTRUCT));
        // Do something with the data
    }
    return 0; // Return value is ignored unless you set callNext to false
}
岁月无声 2024-09-09 12:42:40

我在使用的一些代码中遇到了同样的问题,并发现我无法将托管 .DLL 注入到进程中。

由于不想编写使用 SetWindowsHook 的 C++ 非托管 DLL,我结合使用了 SetWinEventHook,它在窗口开始和结束移动事件时发出信号,并使用 Timer 进行轮询窗口移动时使其看起来跟随窗口移动。

这是一个可以做到这一点的类的(非常简化的)版本(只需用移动窗口或引发事件的代码替换 TODO)。

public class DockingHelper
{
    private readonly uint m_processId, m_threadId;

    private readonly IntPtr m_target;

    // Needed to prevent the GC from sweeping up our callback
    private readonly WinEventDelegate m_winEventDelegate;
    private IntPtr m_hook;

    private Timer m_timer;

    public DockingHelper(string windowName, string className)
    {
        if (windowName == null && className == null) throw new ArgumentException("Either windowName or className must have a value");

        m_target = FindWindow(className, windowName);
        ThrowOnWin32Error("Failed to get target window");

        m_threadId = GetWindowThreadProcessId(m_target, out m_processId);
        ThrowOnWin32Error("Failed to get process id");

        m_winEventDelegate = WhenWindowMoveStartsOrEnds;
    }

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool UnhookWinEvent(IntPtr hWinEventHook);

    [DllImport("user32.dll")]
    private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    private void ThrowOnWin32Error(string message)
    {
        int err = Marshal.GetLastWin32Error();
        if (err != 0)
            throw new Win32Exception(err, message);
    }

    private RECT GetWindowLocation()
    {
        RECT loc;
        GetWindowRect(m_target, out loc);
        if (Marshal.GetLastWin32Error() != 0)
        {
            // Do something useful with this to handle if the target window closes, etc.
        }
        return loc;
    }

    public void Subscribe()
    {
        // 10 = window move start, 11 = window move end, 0 = fire out of context
        m_hook = SetWinEventHook(10, 11, m_target, m_winEventDelegate, m_processId, m_threadId, 0);
    }

    private void PollWindowLocation(object state)
    {
        var location = GetWindowLocation();
        // TODO: Reposition your window with the values from location (or fire an event with it attached)
    }

    public void Unsubscribe()
    {
        UnhookWinEvent(m_hook);
    }

    private void WhenWindowMoveStartsOrEnds(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
    {
        if (hwnd != m_target) // We only want events from our target window, not other windows owned by the thread.
            return;

        if (eventType == 10) // Starts
        {
            m_timer = new Timer(PollWindowLocation, null, 10, Timeout.Infinite);
            // This is always the original position of the window, so we don't need to do anything, yet.
        }
        else if (eventType == 11)
        {
            m_timer.Dispose();
            m_timer = null;
            var location = GetWindowLocation();
            // TODO: Reposition your window with the values from location (or fire an event with it attached)
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int Left, Top, Right, Bottom;
    }

    private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
}

I ran into the same thing in some code I was working with and discovered I couldn't inject a managed .DLL into the process.

Not wanting to write a C++ unmanaged DLL that used SetWindowsHook, I went with a combination of SetWinEventHook, which signals when a window starts and ends a move event and a Timer to poll the window while it's moving to give it the appearance of following the window while it moves.

Here's a (very simplified) version of a class that can do that (just replace the TODO's with code to move the window or raise an event).

public class DockingHelper
{
    private readonly uint m_processId, m_threadId;

    private readonly IntPtr m_target;

    // Needed to prevent the GC from sweeping up our callback
    private readonly WinEventDelegate m_winEventDelegate;
    private IntPtr m_hook;

    private Timer m_timer;

    public DockingHelper(string windowName, string className)
    {
        if (windowName == null && className == null) throw new ArgumentException("Either windowName or className must have a value");

        m_target = FindWindow(className, windowName);
        ThrowOnWin32Error("Failed to get target window");

        m_threadId = GetWindowThreadProcessId(m_target, out m_processId);
        ThrowOnWin32Error("Failed to get process id");

        m_winEventDelegate = WhenWindowMoveStartsOrEnds;
    }

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool UnhookWinEvent(IntPtr hWinEventHook);

    [DllImport("user32.dll")]
    private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    private void ThrowOnWin32Error(string message)
    {
        int err = Marshal.GetLastWin32Error();
        if (err != 0)
            throw new Win32Exception(err, message);
    }

    private RECT GetWindowLocation()
    {
        RECT loc;
        GetWindowRect(m_target, out loc);
        if (Marshal.GetLastWin32Error() != 0)
        {
            // Do something useful with this to handle if the target window closes, etc.
        }
        return loc;
    }

    public void Subscribe()
    {
        // 10 = window move start, 11 = window move end, 0 = fire out of context
        m_hook = SetWinEventHook(10, 11, m_target, m_winEventDelegate, m_processId, m_threadId, 0);
    }

    private void PollWindowLocation(object state)
    {
        var location = GetWindowLocation();
        // TODO: Reposition your window with the values from location (or fire an event with it attached)
    }

    public void Unsubscribe()
    {
        UnhookWinEvent(m_hook);
    }

    private void WhenWindowMoveStartsOrEnds(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
    {
        if (hwnd != m_target) // We only want events from our target window, not other windows owned by the thread.
            return;

        if (eventType == 10) // Starts
        {
            m_timer = new Timer(PollWindowLocation, null, 10, Timeout.Infinite);
            // This is always the original position of the window, so we don't need to do anything, yet.
        }
        else if (eventType == 11)
        {
            m_timer.Dispose();
            m_timer = null;
            var location = GetWindowLocation();
            // TODO: Reposition your window with the values from location (or fire an event with it attached)
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int Left, Top, Right, Bottom;
    }

    private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
}
木緿 2024-09-09 12:42:40

WH_CALLWNDPROC hook 可能就足够了,这将允许您监视发往感兴趣窗口的所有消息。

您是否询问如何使用 C# 创建全局钩子,或者您是否乐意在 C++ 中创建钩子,然后与 .NET 中的钩子进行互操作?第二个选择是我要走的路线。

基本上我的头脑中,我要做的是以下

1- 在 C 中创建全局钩子,并将函数导出到 InstallHookUninstallHook,可以从使用 Interop 的 C# 应用程序。 InstallHook 获取 C# 应用程序中窗口的 hwnd。

2- 当您感兴趣的消息(例如您的案例中的 WM_SIZE)时,让已安装的钩子函数将自定义消息发布到调用 InstallHook 中提供的 C# 窗口。

3- 在 C# 应用程序中,从挂钩接收发布消息的窗口将覆盖 WndProc 来处理自定义消息。

这是一种方法的概述。

A WH_CALLWNDPROC hook would probably suffice, this will allow you to monitor all messages destined for the window of interest.

Are you asking how to create a global hook using C# or are you happy to create the hook in C++ and then interop with that from .NET? The second option is the route I would go.

Basically off the top of my head, what I would do is the following

1- Create global hook in C, and export functions to InstallHook and UninstallHook, which can be called from your C# app using Interop. InstallHook take an hwnd of the window in your C# application.

2- Have the installed hook function post a custom message to the C# window provided in the call to InstallHook when ever there is a message you are interested in like WM_SIZE in your case.

3- In the C# application your window that receives the posted messages from the hook will override WndProc to handle the custom message.

That is an outline of one approach.

关于从前 2024-09-09 12:42:40

我建议您使用 WinEvents:

public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

    [DllImport("user32.dll")]
    public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

另请参阅:事件挂钩

I suggest you use WinEvents:

public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

    [DllImport("user32.dll")]
    public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

See also:event hooks

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文