【问题标题】:Most Efficient Way for getting notified on window open在打开窗口时获得通知的最有效方式
【发布时间】:2014-03-21 16:25:06
【问题描述】:

我正在编写一个应用程序(.NET 4.0 中的 C# 和 WPF),如果它们不在白名单中,则需要打开窗口并关闭它们。

到目前为止,使用来自User32.dllEnumDesktopWindows Windows API,我可以在大约 10 毫秒内枚举我机器上所有打开的窗口。正如您现在可能已经猜到的那样,我需要在较短的时间内尽可能快地执行此操作,另一方面,选择较短的时间段会给系统带来很大的开销。

问题是,“有什么方法可以在打开窗口时获得通知(例如使用事件)?无论哪种方式,最有效的方法是什么?

【问题讨论】:

  • 看看@SetWinEventHook(不像CBT钩子不需要注入)

标签: c# wpf windows api winapi


【解决方案1】:

如果您是从其他应用程序访问窗口,这将不是一件容易的事,但您可以尝试在 windows 中使用Hooks

【讨论】:

    【解决方案2】:

    您可以使用 RegisterWindowMessageRegisterShellHookWindow API 函数连接到 Shell 以接收消息。

    您将需要以下互操作导入:

    public static class Interop
    {
        public enum ShellEvents : int
        {
            HSHELL_WINDOWCREATED = 1,
            HSHELL_WINDOWDESTROYED = 2,
            HSHELL_ACTIVATESHELLWINDOW = 3,
            HSHELL_WINDOWACTIVATED = 4,
            HSHELL_GETMINRECT = 5,
            HSHELL_REDRAW = 6,
            HSHELL_TASKMAN = 7,
            HSHELL_LANGUAGE = 8,
            HSHELL_ACCESSIBILITYSTATE = 11,
            HSHELL_APPCOMMAND = 12
        }
        [DllImport("user32.dll", EntryPoint = "RegisterWindowMessageA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern int RegisterWindowMessage(string lpString);
        [DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern int DeregisterShellHookWindow(IntPtr hWnd);
        [DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern int RegisterShellHookWindow(IntPtr hWnd);
        [DllImport("user32", EntryPoint = "GetWindowTextA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern int GetWindowText(IntPtr hwnd, System.Text.StringBuilder lpString, int cch);
        [DllImport("user32", EntryPoint = "GetWindowTextLengthA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern int GetWindowTextLength(IntPtr hwnd);
    }
    

    为了能够连接到 shell,您需要一个继承自 Form 并覆盖 WndProc 函数的类。你可以让这个表单有一个事件,当一个窗口改变它的状态时会引发这个事件。

    public class SystemProcessHookForm : Form
    {
        private readonly int msgNotify;
        public delegate void EventHandler(object sender, string data);
        public event EventHandler WindowEvent;
        protected virtual void OnWindowEvent(string data)
        {
            var handler = WindowEvent;
            if (handler != null)
            {
                handler(this, data);
            }
        }
    
        public SystemProcessHookForm()
        {
            // Hook on to the shell
            msgNotify = Interop.RegisterWindowMessage("SHELLHOOK");
            Interop.RegisterShellHookWindow(this.Handle);
        }
    
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == msgNotify)
            {
                // Receive shell messages
                switch ((Interop.ShellEvents)m.WParam.ToInt32())
                {
                    case Interop.ShellEvents.HSHELL_WINDOWCREATED:
                    case Interop.ShellEvents.HSHELL_WINDOWDESTROYED:
                    case Interop.ShellEvents.HSHELL_WINDOWACTIVATED:
                        string wName = GetWindowName(m.LParam);
                        var action = (Interop.ShellEvents)m.WParam.ToInt32();
                        OnWindowEvent(string.Format("{0} - {1}: {2}", action, m.LParam, wName));
                        break;
                }
            }
            base.WndProc(ref m);
        }
    
        private string GetWindowName(IntPtr hwnd)
        {
            StringBuilder sb = new StringBuilder();
            int longi = Interop.GetWindowTextLength(hwnd) + 1;
            sb.Capacity = longi;
            Interop.GetWindowText(hwnd, sb, sb.Capacity);
            return sb.ToString();
        }
    
        protected override void Dispose(bool disposing)
        {
            try { Interop.DeregisterShellHookWindow(this.Handle); }
            catch { }
            base.Dispose(disposing);
        }
    }
    

    然后,在应用程序的 Main 函数中,您可以拥有例如:

    static void Main(string[] args)
    {
        var f = new SystemProcessHookForm();
        f.WindowEvent += (sender, data) => Console.WriteLine(data); 
        while (true)
        {
            Application.DoEvents();
        }
    }
    

    输出样本:

    【讨论】:

    • 感谢您的回答,但在其他应用程序上打开对话框时它不会触发。我该怎么办?
    • 您使用 ANSI API 而不是 Unicode API 有什么特别的原因吗?
    【解决方案3】:

    使用System.Windows.Automation 命名空间。

    示例(采用from The Old New Thing)等待特定进程打开对话框,然后将其关闭:

    using System;
    using System.Windows.Automation;
    using System.Diagnostics;
    using System.Threading;
    
    class Program
    {
        [STAThread]
        public static void Main(string[] args)
        {     
            Automation.AddAutomationEventHandler(
                WindowPattern.WindowOpenedEvent,
                AutomationElement.RootElement,
                TreeScope.Children,
                (sender, e) =>
                {
                    var element = sender as AutomationElement;
    
                    Console.WriteLine("new window opened");
                });
    
            Console.ReadLine();
    
            Automation.RemoveAllEventHandlers();
        }
    }
    

    【讨论】:

    • 我检查了它,它非常简单。感谢您的评论,但是从安全的角度来看,它有一件令人讨厌的事情使它不适合。起初看起来很美妙,但事实证明,在窗口完成对屏幕所需的所有初始化之前,不会触发该事件!例如,如果您打开多个 eventvwr.msc 实例,每个实例都需要相当长的时间来填充起始页中的摘要,这会导致在窗口完成创建摘要之前未触发事件(大约 8-10 秒)
    • 澄清一下,如果您在填充上述摘要期间以老式方式枚举窗口,您可以在列表中找到事件查看器的所有实例,这意味着它们是由那个时间,但在加载完成之前不会触发事件。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-10
    • 2011-12-06
    • 1970-01-01
    • 2021-05-16
    • 2015-02-04
    • 1970-01-01
    相关资源
    最近更新 更多