13 августа 2017 г.

WPF/WinForms Хостинг (встраивание) окна чужого win32 приложения.

Ссылка на пример из статьи:
https://bitbucket.org/sergey_vaulin/wpf-application-host


Хочу поделиться с вами способом, как можно окно внешнего приложение "встроить" в свою программу. Ситуация из жизни: разрабатывая CRM систему для заказчика может появиться потребность, в одном из сценариев обслуживания, дать пользователю возможность произвести какие то манипуляции во внешней системе. Если эта система является Web сайтом, то задача решается добавлением WebBrowser на форму. Но представьте, что это какое то приложение, и что тогда делать?
Для этого можно провернуть трюк используя WinAPI, в результате которого окно внешнего приложения окажется "внутри" нашего (как будто экран виртуальной машиной), но у заказчика будет складываться иллюзия того, что мы его запустили внутри нашего приложения. И это похоже на магию.

В примере, ссылка на который имеется в начале статьи, я добавил TabControl, у которого на вкладках размещены три стандартных Windows приложения:
  • Notepad - блокнот.
  • Write - простой редактор документов.
  • Regedit - редактор реестра.
Если запустить пример и переключать закладки, то можно увидеть приведённый на скриншотах ниже, результат работы:

Блокнот:


Write редактор:


Редактор реестра:



Как это работает?


Нет, это не запуск приложения внутри нашего. Если отрыть Process Explorer, то можно увидеть, что мы запустили нужное приложение вне нашего процесса.


Трюк заключается в следующем:

  1. Запускаем нужное приложение (к примеру regedit.exe).
  2. Ожидаем, пока оно передёт в Idle состояние.
  3. Получаем у него Process.MainWindowHandle - это дескриптор главного окна внешнего приложения.
  4. На форме WPF (если используемся WinForm то можно без хоста) размещается WindowsFormsHost, дающий возможность использовать WinForm компоненты.
  5. В свойство Child у WindowsFormsHost размещается UserControl WinForms. Он будет выполнять функцию хоста окна внешнего приложения так как имеет Handle окна.
  6. Последняя итерация - это используя WinAPI функцию SetParent у внешнего окна выставить родителем UserControl от WinForms.
  7. Готово! Приложение магическим образом появилось внутри.
Далее (по вкусу) меняю стиль окна, избавляясь от рамок, в результате получается так:


Внимание! Использованный TabControl ведёт себя так, что при переключении вкладок с Template происходит не создание нового UserControl, а использование одного для каждой вкладки. Поэтому и происходят постоянные запуски новых приложений при каждом переключение. Так же замечал глюк, что TabControl самопроизвольно переключал вкладки, поэтому будьте внимательны.

Можно ли не использовать WindowsFormsHost?


Казалось бы, почему бы не сделать все то же самое, но без WindowsFormsHost? Добавить обычный WPF UserControl и попытаться на нём разместить приложение, но тут нас ждёт разочарование. Все UI элементы в WPF наследуются от класса Visual и не имеют Win32 Handle окна, а значит редерингом окна занимается не Windows, а внутренние механизмы WPF. В этом можно убедиться, запустив Spy++ (Утилита идущая с Visual Studio и являющаяся аналогом Snoop, но для Win32 приложений), и нацелив на окна приложений написанных на WPF и Win32. Мы увидим следующее:
  • Анализируя стандартный Win32 калькулятор, мы видим, что все элементы управления окна появились в дереве, в иерархическом виде.
     
  • Для примера WPF приложения я выбрал главную форму окна существующего Enterprise решения. Как видно, в списке нет Children окон, как было сказано выше, рендеренгом занимается WPF Engine и для системы видно только главное окно.


Ниже приведу изображение с хостинга приложения, на котором появилось Win32 окно, совместимое с окнами ОС Windows. Функцию хостинга осуществляет окно с именем класса WindowsForms.


Как использовать?


Для использования достаточно размещения на в XAML разметке <ApplicationHost/> UserControl и выставления у него нужных значений DependencyProperties.
  • ApplicationPath - путь до исполняемого файла приложения.
  • ApplicationRunning - признак того, что приложение запущено.
  • ErrorText - содержит текст текущей ошибки.
  • MainWindowShowTimeoutSeconds - время, которое надо ждать дополнительно до момента создания главного окна приложения. Бывает ситуация, что приложение перешло в состояние Idle, но главное окно ещё не создалось. Поэтому Handle будет NULL. Выставив это значение, мы дадим приложению шанс полностью открыться за указанное время.
В планах доделать передачу параметров исполняемому файлу.

Заключение.


В заключении хотелось бы сказать, что помимо размещения окна я устанавливаю HOOK функцию на Win32 события внешнего приложения через SetWinEventHook. Её задумка в том, что если вам потребуется хостить внутри не только главное окно, но и ВСЕ создаваемые приложением окна, то достаточно в методе HOOK отлавливать событие создания новых окон и менять их Parent, как для главного окна. Возможно в будущем я добавлю эту возможность через флаг. Помимо этого вы можете контролировать всю активность внешнего приложения, устанавливать значения полей, нажимать на кнопки и вести себя как "БОТ" с целью автоматизировать какие то операции. Для этого пользуйтесь SendMessage и PostMessage функциями.

Рекомендуемые ссылки:


Комментариев нет:

Отправить комментарий