Подписаться на события Vista в .NET (например, открытое окно) - PullRequest
0 голосов
/ 24 апреля 2009

Я пытаюсь создать свой собственный маленький набор инструментов для Vista.

Одной из функций является «инструмент размещения окон», который помещает окна в сохраненную позицию. Еще один инструмент, который я могу себе представить, это расширения для Firefox или Thunderbird ...

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

  • Проводник открыл новое окно
  • Пользователь запустил Firefox
  • Мышь перемещена

Для случая мыши есть несколько примеров для C #. Я также знаю о наблюдателе каталога, милый маленький помощник.

Хочу, чтобы мне сейчас понадобилось «событие, открытое в новом окне»

Любая идея, как контролировать это, не повторяя текущий список окон каждые 5 секунд (я уже знаю, как получить Windows, используя DLLImports, и получить процессы, используя управляемый код. Но у меня нет события, когда процесс проводника открывает новый окна)

Спасибо за вашу помощь, Chris

Ответы [ 4 ]

1 голос
/ 13 мая 2011

У меня точно такая же проблема, как и эта, и я думаю, что у меня есть работоспособное решение. Изначально я искал вариант, подобный тому, что упоминал «tylerl». Однако в моем случае вместо использования SetWindowsHookEx я попытался использовать аналогичную функцию RegisterShellHookWindows.

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

Поскольку мне не хотелось взламывать другие процессы или писать собственный код, который потребуется для SetWindowHookEx, я попытался покопаться в API автоматизации .NET, представленных в .NET 4.0, и я думаю, что это ответ на ваш вопрос. проблема (по крайней мере, в том, что касается определения, когда окна открыты / закрыты).

Вот фрагмент кода для использования этого API для обнаружения открытия / закрытия окон:

    using System.Windows.Automation;

    private void StartMonitoringForWindowEvents()
    {
        Task.Factory.StartNew(() =>
        {
            AutomationEventHandler windowOpenedHandler = new AutomationEventHandler(OnWindowOpened);

            System.Windows.Automation.Automation.AddAutomationEventHandler(
                WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, 
                TreeScope.Descendants, windowOpenedHandler);
        });
    }

    private void OnWindowOpened(object source, AutomationEventArgs eventArgs)
    {
        try
        {
            AutomationElement sourceElement = (AutomationElement)source;

            string message = string.Format(
                "Automation.WindowOpened PID: {0}, Handle: {1}, Name:{2}",
                sourceElement.Current.ProcessId,
                sourceElement.Current.NativeWindowHandle,
                sourceElement.Current.Name);

            Debug.WriteLine(message);

            // for each created window, register to watch for it being closed
            RegisterForClosedWindowEvent(sourceElement);
        }
        catch
        {
        }
    }

    private void RegisterForClosedWindowEvent(AutomationElement element)
    {
        try
        {
            string elementName = element.Current.Name;
            int processId = element.Current.ProcessId;
            int nativeHandle = element.Current.NativeWindowHandle;

            AutomationEventHandler windowClosedHandler = new AutomationEventHandler(
                (ignoreSource, ignoreArgs) => OnWindowClosed(nativeHandle, processId, elementName));

            System.Windows.Automation.Automation.AddAutomationEventHandler(
                WindowPattern.WindowClosedEvent, element,
                TreeScope.Element, windowClosedHandler);
        }
        catch
        {
        }
    }

    private void OnWindowClosed(int nativeHandle, int processId, string elementName)
    {
        string message = string.Format(
            "Automation.WindowClosed PID: {0}, Handle: {1}, Name:{2}",
            processId,
            nativeHandle,
            elementName);

        Debug.WriteLine(message);
    }

Вам нужно будет добавить ссылку на сборки UIAutomationClient и UIAutomationClientTypes.

Вот ссылка на документацию MSDN (вы, вероятно, особенно захотите взглянуть на информацию о событиях): http://msdn.microsoft.com/en-us/library/ms747327.aspx

Важные замечания по реализации:

1.) Обратите внимание, что в примере я использовал фабрику задач, чтобы зарегистрироваться для получения событий автоматизации. Особенно важно избегать использования потока пользовательского интерфейса при регистрации событий автоматизации или общении с API автоматизации. Это может (и обычно быстро) приводит к тупику. Поэтому я использую фабрику задач, чтобы обеспечить регистрацию через пул потоков.

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

2.) Вы также заметите, что я собираю любую необходимую информацию об элементе, который может быть закрыт, во время регистрации (используя закрытие). Это связано с тем, что после закрытия элемента у нас больше не будет доступа к этой информации - поскольку элемент был уничтожен.

Phil

1 голос
/ 24 апреля 2009

То, о чем вы говорите, непросто.

Вам нужно зарегистрировать hook , и вам нужно будет создать процедуру обратного вызова, которая будет вызываться в контексте выполнения другого процесса - это не будет .NET код (вероятно, C), и должен быть в DLL. Эта процедура обратного вызова будет вызываться каждый раз, когда происходит определенный класс событий. Он изучит полученные события и отфильтрует те, которые вас интересуют, а затем отправит вашему приложению нужные вам уведомления (возможно, через PostMessage ). Затем вы подключитесь к главному циклу сообщений вашего приложения, чтобы перехватить эти сообщения, и оттуда вы можете запустить событие .NET или что угодно.

Написание обратных вызовов ловушек - сложная штука, потому что код запускается в другом процессе, а не в вашем, а вопросы управления памятью и параллелизма требуют некоторого внимания. По той же причине это не будет сделано в C #. В идеале, однако, этот код обратного вызова будет очень маленьким и очень быстрым, поскольку его будут вызывать так часто.

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

Также обратите внимание, что далеко идущие последствия системных хуков также означают, что простые ошибки программирования могут разрушить всю вашу систему, что вы, вероятно, обнаружите для себя в какой-то момент при отладке; так что сохраните все, прежде чем нажать «запустить».

Удачи!

EDIT

Сделал еще немного поиска, чтобы узнать, есть ли способ написать процедуру ловушки в C #, и придумал это:

Как установить хук Windows в Visual C # .NET

Это почти то, что вы ищете, но не совсем. Хук-процедуры могут быть либо глобальными (что означает, что они выполняются в каждом приложении), либо потоковыми (только внутри вашего приложения). В документе говорится, что:

Глобальные хуки не поддерживаются в .NET Framework

За исключением WH_KEYBOARD_LL крючок низкого уровня и WH_MOUSE_LL крючок низкого уровня, вы не можете реализовать глобальные хуки в Microsoft .NET Framework. Чтобы установить глобальный хук, хук должен иметь нативный DLL экспорт, чтобы внедрить себя в другой процесс, который требует действительного, последовательная функция для вызова. это поведение требует экспорта DLL. .NET Framework не поддерживает DLL экспорт. Управляемый код не имеет понятия последовательного значения для функции указатель, потому что эти функции указатели являются прокси, которые построены динамически.

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

0 голосов
/ 24 апреля 2009

Чтобы расширить ответ Джоэля Люси, вам нужно использовать Win32 API. Тем не менее, есть хорошая библиотека, Управляемый Windows API , которая предоставляет объектно-ориентированную оболочку поверх обычных API. Это может иметь то, что вам нужно.

0 голосов
/ 24 апреля 2009

Ответ не является специфичным для C # (или .Net). Вам нужно будет вызвать SetWindowsHookEx (WH_CBT, ...). Это позволит узнать, когда окно создано, разрушено, перемещено, измерено и т. Д. Вам также необходимо получить соответствующую информацию из окна, чтобы определить, нужно ли с ней что-то делать. Возможно, GetClassInfo, GetWindowLong и GetWindowText.

Проблема с SetWindowsHookEx заключается в том, что для получения событий из каждого окна вам нужно иметь отдельную DLL-библиотеку win32 с экспортируемой функцией. Хотя вы можете добиться успеха с процедурой, описанной здесь .

...