Взаимодействие 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 использует Hwnds
Чтобы максимально использовать интероперабельность WPF с HWND, необходимо понять, как WPF использует HWND. Для любого HWND нельзя смешивать отрисовку WPF с отрисовкой DirectX или отрисовкой GDI или GDI+. Это имеет ряд последствий. В первую очередь для смешивания этих моделей отрисовки необходимо создать решение взаимодействия и использовать назначенные сегменты взаимодействия для каждой модели отрисовки, которую вы решили использовать. Кроме того, поведение отрисовки накладывает "зону ограничения", влияющую на возможности вашего решения по взаимодействию. Концепция "воздушного пространства" подробно описана в теме Технологические регионы.
Все элементы WPF на экране в конечном счете основаны на HWND. При создании WindowWPF, 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 в одном приложении.
Реализуйте содержимое WPF (корневой элемент содержимого) в качестве управляемого класса. Как правило, класс наследует от одного из классов, которые могут содержать несколько дочерних элементов и /или используются в качестве корневого элемента, например DockPanel или Page. В последующих шагах этот класс называется классом содержимого WPF, а экземпляры класса называются объектами содержимого WPF.
Реализуйте приложение Windows с помощью C++/CLI. Если вы начинаете с существующего неуправляемого приложения C++, обычно можно включить его вызов управляемого кода, изменив параметры проекта, чтобы включить флаг компилятора
/clr
(полная область действия, которую может потребоваться для поддержки компиляции/clr
, не описана в этом разделе).Задайте для модели потоков значение Single Threaded Apartment (STA). WPF использует эту модель потоков.
Обработайте уведомление WM_CREATE в процедуре окна.
В обработчике (или функции, вызываемой обработчиком), выполните следующие действия:
Создайте новый объект HwndSource с родительским окном HWND в качестве параметра
parent
.Создайте экземпляр класса контента WPF.
Назначьте свойству RootVisual объекта HwndSource ссылку на объект содержимого WPF.
Свойство HwndSource объекта Handle содержит дескриптор окна (HWND). Чтобы получить HWND, который можно использовать в неуправляемой части вашего приложения, приведите
Handle.ToPointer()
к типу HWND.
Реализуйте управляемый класс, содержащий статическое поле, содержащее ссылку на объект содержимого WPF. Этот класс позволяет получить ссылку на объект содержимого WPF из кода Win32, но, что более важно, предотвращает непреднамеренный сбор мусора для объекта HwndSource.
Получение уведомлений от объекта содержимого WPF путем подключения обработчика к одному или нескольким событиям объекта содержимого WPF.
Обмен данными с объектом содержимого WPF с помощью ссылки, хранящейся в статическом поле, для задания свойств, методов вызова и т. д.
Заметка
Вы можете выполнить некоторые или все определения класса содержимого WPF для шага One в XAML с помощью частичного класса содержимого по умолчанию, если вы создаете отдельную сборку, а затем ссылаетесь на нее. Хотя объект Application обычно включается в компиляцию XAML в сборку, вы в итоге не используете этот Application в процессе взаимодействия. Вместо этого вы просто используете один или несколько корневых классов для файлов XAML, на которые ссылается приложение, и обращаетесь к их частичным классам. Оставшаяся часть процедуры по сути аналогична приведенной выше процедуре.
Каждый из этих шагов иллюстрируется через код в разделе Пошаговое руководство: Размещение содержимого WPF в Win32.
Размещение окна Microsoft Win32 в WPF
Ключом к размещению окна Win32 в другом содержимом WPF является класс HwndHost. Этот класс упаковывает окно в элемент WPF, который можно добавить в дерево элементов WPF. HwndHost также поддерживает API, которые позволяют выполнять такие задачи, как обработка сообщений для размещенного окна. Основная процедура:
Создайте дерево элементов для приложения WPF (можно использовать код или разметку). Найдите соответствующую и допустимую точку в дереве элементов, где можно добавить реализацию HwndHost в качестве дочернего элемента. В оставшихся шагах этот элемент называется резервирующим элементом.
Производный от HwndHost для создания объекта, в котором хранится содержимое Win32.
В этом классе узла переопределите метод HwndHostBuildWindowCore. Возвращает HWND размещенного окна. Возможно, потребуется упаковать фактические элементы управления в качестве дочернего окна возвращаемого окна; Упаковка элементов управления в окне узла позволяет содержимому WPF получать уведомления от элементов управления. Этот метод помогает исправить некоторые проблемы Win32, связанные с обработкой сообщений на границе хоста управляющего элемента.
Переопределите методы HwndHost, DestroyWindowCore и WndProc. Здесь необходимо обработать очистку и удалить ссылки на размещенное содержимое, особенно если вы создали ссылки на неуправляемые объекты.
В файле программной части создайте экземпляр класса размещения элемента управления и сделайте его дочерним элементом резервирования. Обычно вы используете обработчик событий, например Loaded, или используете конструктор частичного класса. Но вы также можете добавить содержимое взаимодействия с помощью поведения среды выполнения.
Обрабатывайте сообщения выбранного окна, например, уведомления об управлении. Существует два подхода. Оба предоставляют идентичный доступ к потоку сообщений, поэтому ваш выбор в значительной степени является вопросом удобства программирования.
Реализуйте обработку сообщений для всех сообщений (а не только для сообщений завершения работы) в переопределении метода HwndHostWndProc.
Чтобы элемент размещения WPF обработал сообщения, обработайте событие MessageHook. Это событие вызывается для каждого сообщения, отправленного в основную процедуру вложенного окна.
Невозможно обрабатывать сообщения из окон, которые находятся вне процесса, с помощью WndProc.
Общайтесь с размещенным окном с помощью функции Platform Invoke для вызова неуправляемой функции
SendMessage
.
После выполнения этих действий создается приложение, которое работает с входными данными мыши. Вы можете добавить поддержку переключения между вкладками для размещенного окна, реализуя интерфейс IKeyboardInputSink.
Каждый из этих шагов проиллюстрирован с помощью кода в теме Пошаговое руководство: Размещение элемента управления Win32 в WPF.
Hwnds в WPF
Вы можете рассматривать HwndHost как специальный элемент управления. (Технически HwndHost является производным классом FrameworkElement, а не производным классом Control, но его можно рассматривать как элемент управления для целей взаимодействия.) HwndHost абстрагирует базовый характер размещенного содержимого Win32 таким образом, что оставшаяся часть WPF рассматривает размещенное содержимое как другой объект управления, который должен отрисовывать и обрабатывать входные данные. HwndHost обычно ведет себя как любой другой FrameworkElementWPF, хотя существуют важные различия в выводе (рисовании и графике) и вводе (мышь и клавиатура) из-за ограничений, которые поддерживают базовые HWND.
Заметные различия в поведении выходных данных
FrameworkElement, который является базовым классом HwndHost, имеет довольно несколько свойств, которые подразумевают изменения пользовательского интерфейса. К ним относятся такие свойства, как FrameworkElement.FlowDirection, которые изменяют макет элементов в этом элементе в качестве родительского элемента. Однако большинство этих свойств не сопоставляются с возможными эквивалентами Win32, даже если такие эквиваленты могут существовать. Слишком много этих свойств и их значения слишком специфичны для технологии визуализации, чтобы сопоставления были осуществимыми. Поэтому установка таких свойств, как FlowDirection на HwndHost, не влияет.
HwndHost нельзя поворачивать, масштабировать, деформировать или подвергать другим воздействиям с помощью преобразования.
HwndHost не поддерживает свойство Opacity (альфа-смешение). Если содержимое внутри HwndHost выполняет System.Drawing операции, содержащие альфа-информацию, это само по себе не является нарушением, но HwndHost в целом поддерживает только непрозрачность, равную 1,0 (100%).
HwndHost появится на вершине других элементов WPF в том же окне верхнего уровня. Однако созданное меню ToolTip или ContextMenu является отдельным окном верхнего уровня, поэтому будет работать правильно с HwndHost.
HwndHost не учитывает область отсечения родительского UIElement. Это может быть проблемой, если вы пытаетесь поместить класс HwndHost в область прокрутки или Canvas.
Заметные различия в поведении входных данных
Как правило, когда входные устройства находятся в рамках хостируемого региона Win32 HwndHost, входные события переходят непосредственно в Win32.
Пока курсор мыши находится на HwndHost, ваше приложение не получает события мыши WPF, и значение свойства WPF IsMouseOver будет
false
.Пока HwndHost имеет фокус клавиатуры, приложение не получит события клавиатуры WPF, а значение свойства WPF IsKeyboardFocusWithin будет
false
.Если фокус находится в HwndHost и изменяется на другой элемент управления внутри HwndHost, приложение не получит события WPF GotFocus или LostFocus.
Связанные свойства и события пера аналогичны, и не сообщают сведения, пока перо находится над HwndHost.
Вкладки, Мнемоники и ускорители
Интерфейсы IKeyboardInputSink и IKeyboardInputSite позволяют создавать простой интерфейс клавиатуры для смешанных приложений WPF и Win32:
Перемещение с помощью клавиши Tab между компонентами Win32 и WPF
Мнемоники и ускорители, которые работают как при фокусе в компоненте Win32, так и в компоненте WPF.
Классы HwndHost и HwndSource предоставляют реализации IKeyboardInputSink, но они могут не обрабатывать все входные сообщения, которые требуется использовать для более сложных сценариев. Переопределите соответствующие методы, чтобы получить нужное поведение клавиатуры.
Интерфейсы обеспечивают поддержку только того, что происходит при переходе между регионами WPF и Win32. В области Win32 поведение табуляции полностью контролируется реализованной в Win32 логикой табуляции, если таковая имеется.
См. также
.NET Desktop feedback