Самый эффективный способ получения уведомлений об открытии окна

Я пишу приложение (C# и WPF в.NET 4.0), которое должно открывать окна и закрывать их, если их нет в белом списке.

До сих пор, используя EnumDesktopWindows Windows API от User32.dllЯ могу перечислить все открытые окна примерно за 10 мс на моей машине. Как вы, наверное, уже догадались, мне нужно сделать это за небольшие промежутки времени, чтобы быть как можно быстрее, а с другой стороны, выбор небольших промежутков времени приведет к большим накладным расходам в системе.

Вопрос заключается в следующем: "Есть ли способ получить уведомление при открытии окна (например, с помощью события)? В любом случае, какой самый эффективный способ сделать это?

3 ответа

Вы можете подключиться к Shell для получения сообщений с помощью функций API RegisterWindowMessage и RegisterShellHookWindow.

Вам понадобится следующий импорт Interop:

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);
}

Чтобы иметь возможность подключиться к оболочке, вам понадобится класс, который наследуется от 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);
    }
}

И тогда, в вашей основной функции вашего приложения, вы можете иметь, например:

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

Выходной образец:

Использовать System.Windows.Automation Пространство имен.

Пример (взят из 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();
    }
}

Если вы получаете доступ к окнам из другого приложения, это не будет легкой задачей, но вы можете попробовать использовать хуки в окнах.

Другие вопросы по тегам