Взаимодействие WPF и Win32

В этом разделе представлен обзор взаимодействия Windows Presentation Foundation (WPF) и кода Win32. WPF предоставляет многофункциональную среду для создания приложений. Однако если имеется сложный код Win32, возможно, более эффективным будет повторное использование части этого кода.

Основы взаимодействия WPF и Win32

Существуют два основных метода взаимодействия между WPF и кода Win32.

  • Размещение содержимого WPF в окне Win32. С помощью этого способа можно использовать дополнительные графические возможности WPF на платформе стандартных окон Win32 и приложения.

  • Размещение окна Win32 в содержимом WPF. С помощью этого способа можно использовать существующий пользовательский элемент управления Win32 в контексте другого содержимого WPF и передавать данные через границы.

Каждый из этих способов детально представлен в этом разделе. Дополнительные примеры кода размещения WPF в Win32 см. в разделе Пошаговое руководство. Размещение содержимого WPF в Win32. Дополнительные примеры кода размещения Win32 в WPF см. в разделе Пошаговое руководство. Размещение элемента управления Win32 в WPF.

Проекты взаимодействия WPF

Интерфейс API WPF является управляемым кодом, но большинство существующих программ Win32 создаются в виде неуправляемого кода C++. Невозможно вызвать API WPF из подлинной неуправляемой программы. Однако используя параметр /clr с компилятором Microsoft Visual C++, можно создать смешанную управляемую-неуправляемую программу, где могут без проблем смешиваться управляемые и неуправляемые вызовы API.

Одной из проблем на уровне проекта является невозможность компиляции файлов XAML в проект C++. Имеется несколько методов разделения проектов для решения данной проблемы.

  • Создайте библиотеку DLL на C#, содержащую все страницы XAML в виде скомпилированной сборки, а затем включите DLL в виде ссылки в исполняемый файл C++.

  • Создайте исполняемый файл C# для содержимого WPF и создайте ссылку на библиотеку DLL C++, содержащую содержимое Win32.

  • Используйте Load для загрузки XAML во время выполнения вместо компиляции XAML.

  • Не используйте XAML вообще и записывайте все wpF в коде, создавая дерево элементов из Application.

Используйте любой наиболее подходящий способ.

Примечание.

Если вы еще не пользовались C++/CLI, то можно заметить некоторые "новые" ключевые слова, например gcnew и nullptr, в примерах кода взаимодействия. Эти ключевые слова заменяют более старый синтаксис с двойным подчеркиванием (__gc) и обеспечивают более естественный синтаксис для управляемого кода в C++. Дополнительные сведения о возможностях управляемого кода C++/CLI см. в статьях Расширения компонентов для платформ среды выполнения.

Как в WPF используются дескрипторы HWND

Для эффективного использования взаимодействия HWND в WPF необходимо понимать, как в WPF используются дескрипторы HWND. Для любого дескриптора HWND недопустимо смешивать отрисовку WPF с отрисовкой DirectX или с отрисовкой GDI/ GDI+. Это имеет ряд последствий. В первую очередь для смешивания этих моделей отрисовки необходимо создать решение взаимодействия и использовать специальные сегменты взаимодействия для каждой модели отрисовки, которая выбрана для использования. Кроме того, получаемое при отрисовке изображение создает ограничение airspace для решения взаимодействия, которое можно применить. Концепция airspace более подробно рассматривается в разделе Общие сведения об областях применения технологий.

Все элементы WPF на экране в конечном счете поддерживаются дескриптором HWND. Если создается Window в WPF, WPF создает дескриптор HWND верхнего уровня и использует HwndSource, чтобы вставить Window и его содержимое WPF в дескриптор HWND. Остальная часть содержимого WPF в приложении также использует этот единственный дескриптор HWND. Исключением являются меню, поля с раскрывающимся списком и другие всплывающие окна. Эти элементы создают свое собственное окно верхнего уровня, поэтому меню WPF может выйти за край HWND окна, которое его содержит. Если используется HwndHost, чтобы вставить дескриптор HWND в WPF, WPF оповещает Win32 о способе расположения нового дочернего дескриптора HWND относительно HWND Window WPF.

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

Размещение содержимого WPF в окне Microsoft Win32

Ключом к размещению содержимого WPF в окне Win32 является класс HwndSource. Этот класс упаковывает содержимое WPF в окно Win32, чтобы содержимое WPF можно было включить в пользовательский интерфейс в качестве дочернего окна. Следующий подход объединяет Win32 и WPF в одном приложении.

  1. Реализуйте содержимое WPF (содержимое корневого элемента) в виде управляемого класса. Как правило, класс наследуется от одного из классов, который может содержать несколько дочерних элементов и/или использоваться в качестве корневого элемента, например DockPanel или Page. В последующих шагах этот класс называется классом содержимого WPF, а его экземпляры называются объектами содержимого WPF.

  2. Реализуйте приложение Windows на C++/CLI. При запуске существующего неуправляемого приложения C++, как правило, можно разрешить ему вызывать управляемый код путем изменения параметров проекта с целью включения флага компилятора /clr(полное описание объектов, необходимых для поддержки компиляции /clr, в этом разделе не приводится).

  3. Установите в качестве потоковой модели однопотоковое подразделение (STA). Эта потоковая модель используется WPF.

  4. Обработайте уведомления WM_CREATE в процедуре окна.

  5. В обработчике (или функции, которую вызывает обработчик) выполните указанные ниже действия.

    1. Создайте новый объект HwndSource с родительским дескриптором HWND окна в качестве параметра parent.

    2. Создайте экземпляр вашего класса содержимого WPF.

    3. Назначьте ссылку на объект содержимого WPF свойству RootVisual объекта HwndSource.

    4. Свойство Handle объекта HwndSource содержит дескриптор окна (HWND). Чтобы получить HWND, который можно использовать в неуправляемой части приложения, приведите Handle.ToPointer() к HWND.

  6. Реализуйте управляемый класс, содержащий статическое поле, которое хранит ссылку на объект содержимого WPF. Этот класс не только позволяет получить ссылку на объект содержимого WPF из кода Win32, но и, что еще более важно, защищает ваш HwndSource от случайного удаления сборщиком мусора.

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

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

Примечание.

Определить класс содержимого WPF для шага 1 можно частично или полностью в XAML с помощью разделяемого класса по умолчанию класса содержимого. Для этого нужно создать отдельную сборку и сослаться на нее. Хотя в рамках компиляции в сборку, как правило, включается объект Application, этот объект Application не используется в итоге при взаимодействии. Вы просто используете один или несколько корневых классов для файлов XAML, на которые ссылается приложение, и ссылаетесь на их разделяемые классы. Оставшаяся часть процедуры практически аналогична описанной выше.

Каждое из этих действий будет показано в коде в разделе Пошаговое руководство. Размещение содержимого WPF в Win32.

Размещение окна Microsoft Win32 в WPF

Ключом к размещению окна Win32 в другом содержимом WPF является класс HwndHost. Он заключает окно в элемент WPF, который можно добавить в дерево элементов WPF. Кроме того, HwndHost поддерживает интерфейсы API, которые позволяют выполнять такие задачи, как обработка сообщений для размещенного окна. Ниже описываются основные действия.

  1. Создайте дерево элементов для приложения WPF (с помощью кода или разметки). Найдите соответствующую и допустимую точку в дереве элементов, где реализацию HwndHost можно добавить в качестве дочернего элемента. В оставшихся действиях процедуры этот элемент называется элементом резервирования.

  2. Выполните наследование от HwndHost, чтобы создать объект с содержимым Win32.

  3. В классе узла переопределите метод BuildWindowCore в HwndHost. Возвратите HWND размещенного окна. Вероятно, вам может потребоваться заключить существующие элементы управления в дочернее окно возвращаемого окна. Заключение элементов управления в основное окно представляет собой простой способ получения уведомлений от элементов управления для содержимого WPF. Этот способ позволяет избежать некоторых проблем Win32, касающихся обработки сообщений на границе размещаемых элементов управления.

  4. Переопределите методы DestroyWindowCore и WndProc в HwndHost. Цель этого — выполнить очистку и удалить ссылки на размещаемое содержимое, особенно если созданы ссылки на неуправляемые объекты.

  5. В файле кода программной части создайте экземпляр класса, размещающего элемент управления, и сделайте его дочерним классом элемента резервирования. Как правило, используется обработчик событий, такой как Loaded, или конструктор разделяемого класса. Но можно также добавить содержимое взаимодействия с помощью поведения среды выполнения.

  6. Обработайте сообщения выбранного окна, такие как уведомления элементов управления. Существуют два подхода. Оба предоставляют идентичный доступ к потоку сообщений, поэтому выбор во многом определяется удобством программирования.

    • Реализуйте обработку всех сообщений (а не только сообщений о завершении работы) в вашем переопределении метода WndProc в HwndHost.

    • Получите процесс элемента WPF размещения сообщений, обработав событие MessageHook. Это событие вызывается для каждого сообщения, которое отправляется в главную процедуру размещенного окна.

    • Невозможно обрабатывать сообщения из окон, которые находятся вне процесса, с помощью WndProc.

  7. Для взаимодействия с размещенным окном используйте вызов неуправляемого кода с целью вызова неуправляемой функции SendMessage.

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

Каждое из этих действий будет показано в коде в разделе Пошаговое руководство. Размещение элемента управления Win32 в WPF.

Дескрипторы HWND внутри WPF

HwndHost можно рассматривать как специальное управление. (Технически является HwndHost производным классом FrameworkElementControl , а не производным классом, но его можно рассматривать как элемент управления для целей взаимодействия.) HwndHost абстрагирует базовый характер размещенного содержимого Win32 таким образом, что оставшаяся часть WPF считает размещенное содержимое другим объектом управления, который должен отображать и обрабатывать входные данные. Как правило, HwndHost ведет себя как и любой другой элемент FrameworkElement WPF, хотя в зависимости от ограничений на поддерживаемые базовыми дескрипторами HWND возможности могут существовать некоторые существенные различия, связанные с выводом (рисование и графика) и вводом (мышь и клавиатура) данных.

Важные различия в режиме вывода

  • FrameworkElement, который является базовым классом HwndHost, имеет довольно много свойств, которые предусматривают изменения пользовательского интерфейса. К ним относятся такие свойства, как FrameworkElement.FlowDirection, которые изменяют макет элементов внутри этого элемента как родительского. Однако большая часть этих свойств не сопоставляется с возможными эквивалентами Win32, даже если такие эквиваленты могут существовать. Многие из этих свойств и их значений слишком специфичны для технологии отрисовки, чтобы сопоставление было целесообразным. Таким образом, установка таких свойств, как FlowDirection в HwndHost, не оказывает влияния.

  • Объект HwndHost невозможно поворачивать, масштабировать, отклонять или преобразовывать иным образом.

  • HwndHost не поддерживает свойство Opacity (альфа-смешение). Если содержимое внутри HwndHost выполняет операции System.Drawing, включающие альфа-информацию, которая сама по себе не является нарушением, но HwndHost, как целое, поддерживает только Opacity (Прозрачность) = 1,0 (100%).

  • HwndHost появится поверх других элементов WPF в том же окне верхнего уровня. Однако сгенерированное меню ToolTip или ContextMenu является отдельным окном верхнего уровня, и поэтому будет корректно вести себя с HwndHost.

  • HwndHost не учитывает отсеченную область своего родительского UIElement. Может возникнуть проблема, если попытаться поместить класс HwndHost в область прокрутки или Canvas.

Важные различия в режиме ввода

  • В общем случае, когда устройства ввода находятся внутри размещенной области Win32 HwndHost, события ввода поступают непосредственно в приложение Win32.

  • Пока указатель мыши находится над HwndHost, ваше приложение не получит события мыши WPF, а значение свойства IsMouseOver WPF будет false.

  • Пока HwndHost имеет фокус на клавиатуре, ваше приложение не будет получать события клавиатуры WPF, а значением свойства IsKeyboardFocusWithin WPF будет false.

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

  • Свойства и события, связанные с пером, являются аналогичными и не предоставляют сведений, когда перо находится над объектом HwndHost.

Переходы, назначенные клавиши и сочетания клавиш

Интерфейсы IKeyboardInputSink и IKeyboardInputSite позволяют создать бесшовную клавиатуру для смешанных приложений WPF и Win32:

  • Переход по клавише TAB между компонентами Win32 и WPF

  • Назначенные клавиши и сочетания клавиш, которые работают, когда фокус находится внутри компонента Win32 или WPF.

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

Интерфейсы обеспечивают поддержку только для событий перехода между областями WPF и Win32. Внутри области Win32 поведение перехода по клавише TAB полностью контролируется логикой перехода, реализованной в Win32 (при наличии).

См. также