桌面窗口集合更改时调用异步委托?

Posted

技术标签:

【中文标题】桌面窗口集合更改时调用异步委托?【英文标题】:pinvoke async delegate when desktop windows collection changes? 【发布时间】:2014-10-29 15:42:05 【问题描述】:

我有一个 C# 程序来轮询对 EnumDesktopWindows 集合的更改。如果用户关闭或打开一个窗口,轮询例程会检测到这一点并将可用窗口的更新列表发送到另一个 .Net windows 窗体项目。但是,我不喜欢轮询方法。我希望对 EnumDesktopWindows 的任何更改都会触发一个事件,以便异步完成对更改的响应。

我能想到的最好的就是你在下面看到的。我尝试了 Scott C. 从控制台窗口执行的建议,但没有奏效。

目前,您在下面看到的内容在 Windows 窗体加载时捕获 CreateWnd=3(这是一个 Windows 窗体应用程序)。但是它不会全局捕获:它仅从当前运行的可执行文件中捕获窗口事件。如果有人有鹰眼并且可以发现如何在全球范围内捕获此代码,我将奖励答案。

尝试一下;首先创建一个Windows Forms应用项目,在Form1.cs中添加如下代码(需要在名为lstLog的Form中添加一个ListBox才能正确编译)

using System;
using System.Windows.Forms;

namespace Utilities

    public partial class Form1 : Form
    
        public Form1()
        
            InitializeComponent();
        

        private void Form1_Load(object sender, EventArgs e)
        
            var gwh = new GlobalWindowHook();
            gwh.WindowCreated += onWindowCreated;
        

        private void onWindowCreated()
        
            lstLog.Items.Add("window creation event detected.");
        
    

在同一个项目中创建一个名为 GlobalWindowHook.cs 的类文件并复制粘贴以下内容:

using System;
using System.Runtime.InteropServices;

namespace Utilities

    internal class GlobalWindowHook
    
        private delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        public enum HookType
        
            WH_JOURNALRECORD = 0,
            WH_JOURNALPLAYBACK = 1,
            WH_KEYBOARD = 2,
            WH_GETMESSAGE = 3,
            WH_CALLWNDPROC = 4,
            WH_CBT = 5,
            WH_SYSMSGFILTER = 6,
            WH_MOUSE = 7,
            WH_HARDWARE = 8,
            WH_DEBUG = 9,
            WH_SHELL = 10,
            WH_FOREGROUNDIDLE = 11,
            WH_CALLWNDPROCRET = 12,
            WH_KEYBOARD_LL = 13,
            WH_MOUSE_LL = 14
        

        public enum HCBT
        
            MoveSize = 0,
            MinMax = 1,
            QueueSync = 2,
            CreateWnd = 3,
            DestroyWnd = 4,
            Activate = 5,
            ClickSkipped = 6,
            KeySkipped = 7,
            SysCommand = 8,
            SetFocus = 9
        

        private IntPtr hhook = IntPtr.Zero;

        public GlobalWindowHook()
        
            hook();
        


        ~GlobalWindowHook()
        
            unhook();
        


        public void hook()
        
            IntPtr hInstance = LoadLibrary("User32");

            hhook = SetWindowsHookEx(HookType.WH_CBT, hookProc, hInstance, 0);
        

        public void unhook()
        
            UnhookWindowsHookEx(hhook);
        

        public IntPtr hookProc(int code, IntPtr wParam, IntPtr lParam)
        
            if (code != (int) HCBT.CreateWnd && code != (int) HCBT.DestroyWnd)
                return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);

            //Do whatever with the created or destroyed window.

            return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
        


        [DllImport("user32.dll")]
        private static extern IntPtr SetWindowsHookEx(HookType code, HookProc func, IntPtr hInstance, int threadId);

        [DllImport("user32.dll")]
        private static extern bool UnhookWindowsHookEx(IntPtr hInstance);

        [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern IntPtr LoadLibrary(string fileName);
    

执行上述步骤后,执行 windows 窗体项目。您应该会看到它检测到正在创建一个窗口,即您刚刚执行的那个。

【问题讨论】:

我认为你可以使用 SetWindowsHookEx()WH_CBT 钩子类型来做到这一点。 msdn.microsoft.com/en-us/library/windows/desktop/… 我刚刚修改了我的问题,并用我迄今为止的最佳尝试对其进行了更新(谢谢 Scott)。我仍在寻求答案。 @Hans - 所以不要使用 SetWindowsHookEx?我会试一试。我是这方面的新手。 【参考方案1】:

如果您想推送通知而不是拉取通知,这并不太难。您需要做的是 P/Invoke SetWindowsHookEx 并注册 WH_CBT 事件。收到这些消息后,您可以使用HCBT_CREATEWND or HCBT_DESTROYWND 代码收听消息。它会在新窗口创建或销毁时通知您。

使用pinvoke.net定义的类型

class HookingClass

    private delegate IntPtr CBTProc(HCBT nCode, IntPtr wParam, IntPtr lParam);

    private readonly CBTProc _callbackDelegate;

    public HookingClass()
    
         _callbackDelegate = CallbackFunction;
    

    private IntPtr _hook;

    private void CreateHook()
    
        using (Process process = Process.GetCurrentProcess())
        using (ProcessModule module = process.MainModule)
        
            IntPtr hModule = GetModuleHandle(module.ModuleName);

            _hook = SetWindowsHookEx(HookType.WH_CBT, _callbackDelegate, hModule, 0);
            if(_hook == IntPtr.Zero)
                throw new Win32Exception(); //The default constructor automatically calls Marshal.GetLastError()
        

    

    private void Unhook()
    
        var success = UnhookWindowsHookEx(_hook);
        if(!success)
            throw new Win32Exception();
    

     private IntPtr CallbackFunction(HCBT code, IntPtr wParam, IntPtr lParam)
     
         if (code != HCBT.CreateWnd && code != HCBT.DestroyWnd) 
                  
             return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
         

         //Do whatever with the created or destroyed window.

         return CallNextHookEx(IntPtr.Zero, (int)code, wParam, lParam);
     

【讨论】:

@sapbucket 你的问题是hModule 需要指向包含_callbackDelegate 函数的程序集。 user32.dll 不包含您的功能,这就是它不起作用的原因。尝试从测试控制台应用程序执行此操作,然后再尝试从单元测试中运行它。此外,您的单元测试程序将在事件触发之前退出并关闭。 Hans Passat 解释说 hModule 没有在低级库中使用,传入任何有效的东西都可以解决问题。这就是我坚持使用 LoadLibrary 的原因。汉斯错了吗?不管怎样,我都没有成功。 这仅适用于低级挂钩。 CBT 钩子没有什么低级的,模块句柄非常很重要。 @Hans - 我发现一个线程表明我正在尝试做的事情不受 .Net 支持;我相信你的意见 - 线程正确吗? ***.com/questions/1811383/setwindowshookex-in-c-sharp 没错,是的。您不能将 C# 代码注入另一个进程。 Afaik,Scott 的代码只会告诉您有关您自己的应用程序中的窗口被创建和关闭的信息。通常不是程序员正在寻找的东西,尤其是在控制台模式应用程序中:) 这就是我发布替代方案的原因。需要消息循环,因此避免使用控制台模式应用程序。

以上是关于桌面窗口集合更改时调用异步委托?的主要内容,如果未能解决你的问题,请参考以下文章

当边界 UICollectionView 更改时调整单元格大小

AsyncCallback

嵌套集合视图单元格未在方向更改时调整大小

JS Async Callback

Groovy集合遍历 ( 集合中有集合元素时调用 flatten 函数拉平集合元素 | 代码示例 )

Groovy集合遍历 ( 集合中有集合元素时调用 flatten 函数拉平集合元素 | 代码示例 )