среда, 24 сентября 2008 г.

Much of muchness. Part 3. WPF-interop and "airspace" notion - hosting gadgets in complex shape windows.

Эта часть будет короткой, и по большей части к разработке самих плагинов не относится. Она, скорее, о WPF-interop, и о том какие проблемы возникают при хостинге gadget'ов (UI плагина) в WPF.

Как я уже говорил в предыдущей части, проблема возникает вследствие того, что представление плагина рендерится не WPF движком (построеном на DirectX), а средствами Win32. Вообще в WPF можно хостить контент отрисованный другими технологиями, но при этом возникает целый ряд проблем, которые в Microsoft очень метко обозвали "airspace". Суть этой проблемы заключается в том, что при хостинге UI отрисованного различными технологиями, эти части UI находятся в изолированных пространствах и подчиняются правилам тех технологий, которые занимаются рендерингом. В нашем случае проблема возникает в случае использования Layered Windows. При попытке захостить gadget в окне с установленным флагом AllowsTranspency и Transparent Background, эта проблема встает в полный рост - WPF владеет всеми пикселами окна и любой другой не-WPF контент будет просто невидим.

Вы спросите зачем нужны layered windows? В WPF это наиболее простой способ создания окон сложной формы - окон без стандартного заголовка (chrome). Но выход есть, и если вы хотите хостить UI плагина в окошках со сложной формой, вам необходимо использовать альтернативный способ создания окна без стандартного заголовка, а именно WinAPI Regions.

Далее привожу код для окна с нестандартным chrome.

Здесь в OnSourceInitialized мы захватываем обект HwndSource, который является (согласно его названию) поставщиком HWND для окна WPF и расширяем стиль окна.

Поскольку наше окно будет лишено стандартного заголовка, то надо как-то его таскать. Об этом заботится обработчик OnMouseLeftButtonDown.

WPF and Win32 Interoperation Overview

  • Part 1. Overview.
  • Part 2. MAF addin UI - gadget. Object model.
  • Part 3. WPF-interop and "airspace" notion - hosting gadgets in complex shape windows.
  • Part 4. Publishing MAF addin settings.
  • Part 5. Handling unhandled exceptions in isolated addin domains, gadget host.
  • Part 6. Cross-AppDomain events propagation powered by Juval Lowy's WCF Pub-Sub framework.

Удачи!

среда, 3 сентября 2008 г.

AddIns in Plugins or much of muchness. Part 2 MAF addin UI - gadget, Object model.

//тут введение. можно пошутить, но лучше не стоит.

Пора описать реализацию.

Object model.

Для того, чтобы System.AddIn framework мог обнаружить и хост мог подключить плагины, мы должны должны реализовать требуемый framework'ом pipeline, состоящий и 5 компонент:

  • контракта плагина и, если необходимо, хоста;
  • представления хоста (то как хост видит плагин - изолирует хост и от контракта и от самого плагина подавно);
  • представления плагина (то как плагин видит хост - -//-);
  • адаптеры стороны хоста, транслирующие вызовы представления хоста в вызовы контракта - наследники представления хоста; адаптеры, транслирующие вызовы контракта в вызовы представления хоста - наследники контракта;
  • адаптеры стороны плагина, транслирующие вызовы контракта в вызовы представления плагина - наследники контракта; адаптеры, транслирующие вызовы представления плагина в вызовы контракта - наследники представления хоста.
Теперь по пунктам. Плагины предназначены для публикации в хост гаджетов, соответственно и называются они XXXGadgetAddIn.

Contracts.




IGadgetAddInContract - контракт, с помощью которого хост общается с плагином (функциональность, которую хост требует от плагинов).
IHostContract - контракт, с помощью которого плагин может общаться с хостом (функциональность, которую хост предоставляет плагину).
IAddInTunedContract - вспомогательный контракт для передачи настроек плагина хосту (подробнее о нем я напишу в одной из следующих частей).
IServiceLocatorContract - обертка для передачи набора сервис-локаторов плагину.

Метод Initialize, согласно его имени, предназначен для инициаллизации плагина. В качестве параметра ему передается контракт объекта хоста. Возвращает же этот метод некий id, позволяющий однозначно идентифицировать плагин.



метод Initialize TestGadgetAddIn.

InitializeServices предназначен для инициализации сервисной инфраструктуры плагина, загруженного в другой домен или процесс (я использую ServiceLocator как форму IoC).



метод InitializeServices TestGadgetAddIn.


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

Последний метод контракта обязательно должен вызываться из основного потока приложения (UI-поток), т.к. он возвращает представление (gadget) - UI контрол, который будет добавлен в визуальное дерево представления хоста, что само собой влечет за собой требование идентичности Dispatcher'ов контролов.




HostViews.



Представление хоста, как я уже писал изолирует хост от контракта, позволяя изменять версии контракта и соответственно плагина безболезненно для хоста (т.е. он сможет работать даже с более новыми плагинами, реализующими другой, может быть более полный контракт), без перекомпиляции. Для этого лишь понадобится написать новый адаптер. Абстрактный класс представления плагина хостом наследует и реализует адаптер хоста. INativeHandleContract - это обертка, с помощью которой команде WPF удалость передать визуальный объект через границы домена. Реально в визуальном дереве хоста появится не ожидаемый контрол (gadget), который создает плагин, а специальный контрол AddInHost - наследник HwndHost, т.е. контрол позволяющий хостить внутри себя не-WPF контент. Отсюда вывод, что наш gadget будет рендериться не WPF-engine, а Win32. О проблемах, которые это за собой влечет и решении я напишу в одной из следующих частей.

HostAdapters.

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



Адаптер контракта плагина к представлению хоста.



Адаптер хоста к контракту.

Интересный момент здесь, то как передаются настройки - полученный контракт заворачивается в AddInTunedProxy, который восстанавливает UI-специфичные свойства из их сериализованного представления (свойство Image) и публикует настроечный UI при помощи встроенного класса FrameworkElementAdapters, определенного в System.Windows.Presentation.



Вызовы всех метод эта обертка делегирует внутреннему ITunedContract, которые выполняются в домене плагина.

AddInViews.

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




AddInAdapters.



Адаптер плагина к его контракту.
Просто делегирует вызовы контракту.




Адаптер контракта хоста к его представлению.
Наследует абстрактному классу представления хоста плагином. Настройки передаются в виде контракта AddInTunedToContractAdapter, который публикует proxy Tuned для AddInTunedProxy и UI.




Вот в кратце объектная модель решения. В следующей части я расскажу о проблемах, связанных с WPF-interop сценариями, которые возникают при попытке захостить UI плагин, и о решении этих проблем.

Удачи!