Поделиться через


Руководство по созданию содержимого WPF, размещенного в приложении Win32

Обновлен: Ноябрь 2007

Для размещения WPF в приложениях Win32 используется HwndSource, предоставляющий HWND, который включает в себя содержимое WPF. Сначала создается HwndSource путем задания его параметров аналогично созданию окна. Затем HwndSource о WPF сообщается о содержимом, которое требуется поместить. Наконец, получается HWND из HwndSource. В данном пошаговом руководстве описывается создание смешанного WPF в приложении Win32, который заново реализует диалог Свойства даты и времени операционной системы.

Обязательные компоненты

См. раздел Общие сведения о взаимодействии WPF и Win32.

Использование этого руководства

Это руководство сосредоточено на важных шагах создания приложения взаимодействия. Руководство комплектуется демонстрационным примером, Пример взаимодействия с часами Win32, который является отражением конечного продукта. В руководстве документируются этапы, как если бы вы начали с собственным существующим Win32 проектом, или возможно с ранее существующим проектом, и добавили размещенный WPF в приложение. Можно сравнить конечный продукт с Пример взаимодействия с часами Win32.

Пример Windows Presentation Framework в Win32 (HwndSource)

На приведенном ниже рисунке показан предполагаемый конечный продукт этого руководства:

Диалоговое окно “Дата и свойства времени”

Этот диалог можно создать заново, создав проект Win32 C++ в Microsoft Visual Studio и с помощью редактора окон создав следующее:

Диалоговое окно “Дата и свойства времени”

(Нет необходимости применять Microsoft Visual Studio для использования HwndSource, и не нужно использовать C++ для написания программ Win32, но это довольно обычный способ, который поэтапно предоставляется в руководстве).

Чтобы поместить часы WPF в диалог, необходимо выполнить пять отдельных этапов:

  1. Включите для вашего проекта Win32 возможность вызова управляемого кода (/clr), изменив параметры проекта в Microsoft Visual Studio.

  2. Создайте WPFPage в отдельной библиотеке DLL.

  3. Поместите этотWPFPage в HwndSource.

  4. Получите HWND для этого Page с помощью свойства Handle.

  5. Используйте Win32, чтобы решить, где поместить HWND в большем Win32 приложении

/clr

Первым шагом является преобразование этого неуправляемого проекта Win32 в проект, который может вызывать управляемый код. Используйте параметр компилятора /clr, который свяжет необходимые библиотеки DLL, и настройте метод Main для использования в WPF.

Чтобы включить использование управляемого кода в проекте C++, щелкните правой кнопкой мыши на проекте win32clock и выберите Свойства. На странице свойств Общие (по умолчанию) измените поддержку CLR на /clr.

Затем добавьте ссылки на библиотеки DLL, необходимые для WPF: PresentationCore.dll, PresentationFramework.dll, System.dll, WindowsBase.dll, UIAutomationProvider.dll и UIAutomationTypes.dll. (Следующие инструкции предполагают, что операционная система установлена на диске C:)

  1. Щелкните правой кнопкой мыши на проекте win32clock и выберите Ссылки...; затем в этом диалоговом окне:

  2. Щелкните правой кнопкой мыши на проекте win32clock и выберите Ссылки....

  3. Нажмите кнопку Добавить новую ссылку, перейдите на вкладку «Обзор», введите «C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll» и нажмите кнопку «ОК».

  4. Повторите те же действия для PresentationFramework.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll.

  5. Повторите те же действия для WindowsBase.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\ WindowsBase.dll.

  6. Повторите те же действия для UIAutomationTypes.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll.

  7. Повторите те же действия для UIAutomationProvider.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll.

  8. Нажмите кнопку Добавить новую ссылку, выберите файл System.dll и нажмите кнопку ОК.

  9. Нажмите кнопку ОК, чтобы выйти из страницы свойств win32clock для добавления ссылок.

Наконец, добавьте STAThreadAttribute в метод _tWinMain для использования в WPF:

[System::STAThreadAttribute]
int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)

Этот атрибут сообщает среда CLR (common language runtime), что при инициализации Модель COM (Component Object Model) следует использовать модель однопотокового подразделения (STA), которая необходима для WPF (и для Windows Forms).

Создайте страницу Windows Presentation Framework

Далее, создайте библиотеку DLL, определяющую WPF Page. Часто таким способом проще всего создать PageWPF как самостоятельное приложение, а затем написать и отладить часть WPF. После этого проект можно преобразовать в DLL, щелкнув на нем правой кнопкой мыши, а затем щелкнув на кнопке Свойства, перейдя в приложение и изменив тип выходных данных на библиотеку классов Windows.

Проект DLL WPF затем может быть объединен с проектом Win32 (одно решение, которое содержит два проекта) ― щелкните правой кнопкой мыши на решении и выберите Добавить существующий проект.

Чтобы использовать эту библиотеку WPF из проекта Win32, необходимо добавить ссылку:

  1. Щелкните правой кнопкой мыши на проекте win32clock и выберите Ссылки....

  2. Щелкните Добавить новую ссылку.

  3. Перейдите на вкладку Проекты. Выберите WPFClock, нажмите кнопку «ОК».

  4. Нажмите кнопку ОК, чтобы выйти из страницы свойств win32clock для добавления ссылок.

HwndSource

Далее, воспользуйтесь HwndSource, чтобы PageWPF выглядел как HWND. Добавьте этот блок кода в C++ файл:

namespace ManagedCode
{
    using namespace System;
    using namespace System::Windows;
    using namespace System::Windows::Interop;
    using namespace System::Windows::Media;

    HWND GetHwnd(HWND parent, int x, int y, int width, int height) {
        HwndSource^ source = gcnew HwndSource(
            0, // class style
            WS_VISIBLE | WS_CHILD, // style
            0, // exstyle
            x, y, width, height,
            "hi", // NAME
            IntPtr(parent)        // parent window 
            );
        
        UIElement^ page = gcnew WPFClock::Clock();
        source->RootVisual = page;
        return (HWND) source->Handle.ToPointer();
    }
}
}

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

namespace ManagedCode
{
    using namespace System;
    using namespace System::Windows;
    using namespace System::Windows::Interop;
    using namespace System::Windows::Media;

Затем определяется функция, которая создает содержимое WPF, помещает в него HwndSource и возвращает HWND:

    HWND GetHwnd(HWND parent, int x, int y, int width, int height) {

Сначала создается HwndSource, чьи параметры аналогичны параметрам CreateWindow:

        HwndSource^ source = gcnew HwndSource(
            0, // class style
            WS_VISIBLE | WS_CHILD, // style
            0, // exstyle
            x, y, width, height,
            "hi", // NAME
            IntPtr(parent) // parent window 
            );

Затем создается класс содержимого WPF путем вызова его конструктора:

        UIElement^ page = gcnew WPFClock::Clock();

Затем страница соединяется с HwndSource:

        source->RootVisual = page;

И в последней строке возвращается HWND для HwndSource:

        return (HWND) source->Handle.ToPointer();

Размещение Hwnd

Теперь, когда создан HWND, содержащий часы WPF, необходимо поместить этот HWND в диалоговое окно Win32. Если бы было известно, где размещается HWND, то можно было бы просто передать его размер и расположение в функцию GetHwnd, определенную ранее. Но поскольку для определения этого диалогового окна использовался файл ресурсов, точно не известно, где расположены HWND. Можно воспользоваться редактором диалоговых окон Microsoft Visual Studio для размещения элемента управления STATIC Win32, в котором должны пойти часы («Вставьте часы здесь»), и использовать его для размещения часов WPF.

Там, где выполняется обработка WM_INITDIALOG, используется GetDlgItem, чтобы получить HWND для заместителя STATIC:

HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);

Затем вычисляется размер и положение этого местозаполнителя STATIC, чтобы можно было поместить часы WPF в этом месте:

RECT rectangle;

GetWindowRect(placeholder, &rectangle);
int width = rectangle.right - rectangle.left;
int height = rectangle.bottom - rectangle.top;
POINT point;
point.x = rectangle.left;
point.y = rectangle.top;
result = MapWindowPoints(NULL, hDlg, &point, 1);

Затем местозаполнитель STATIC скрывается:

ShowWindow(placeholder, SW_HIDE);

И создается HWND часов WPF в этом расположении:

HWND clock = ManagedCode::GetHwnd(hDlg, point.x, point.y, width, height);

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

Разметка:

<Page x:Class="WPFClock.Clock"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    >
    <Grid>
        <Grid.Background>
            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
              <GradientStop Color="#fcfcfe" Offset="0" />
              <GradientStop Color="#f6f4f0" Offset="1.0" />
            </LinearGradientBrush>
        </Grid.Background>

        <Grid Name="PodClock" HorizontalAlignment="Center" VerticalAlignment="Center">
            <Grid.Resources>
                <Storyboard x:Key="sb">
                    <DoubleAnimation From="0" To="360" Duration="12:00:00" RepeatBehavior="Forever"
                        Storyboard.TargetName="HourHand"
                        Storyboard.TargetProperty="(Rectangle.RenderTransform).(RotateTransform.Angle)" 
                        />
                    <DoubleAnimation From="0" To="360" Duration="01:00:00" RepeatBehavior="Forever"
                        Storyboard.TargetName="MinuteHand"  
                        Storyboard.TargetProperty="(Rectangle.RenderTransform).(RotateTransform.Angle)"
                        />
                    <DoubleAnimation From="0" To="360" Duration="0:1:00" RepeatBehavior="Forever"
                        Storyboard.TargetName="SecondHand"  
                        Storyboard.TargetProperty="(Rectangle.RenderTransform).(RotateTransform.Angle)"
                        />
                </Storyboard>
            </Grid.Resources>

          <Ellipse Width="108" Height="108" StrokeThickness="3">
            <Ellipse.Stroke>
              <LinearGradientBrush>
                <GradientStop Color="LightBlue" Offset="0" />
                <GradientStop Color="DarkBlue" Offset="1" />
              </LinearGradientBrush>
            </Ellipse.Stroke>
          </Ellipse>
          <Ellipse VerticalAlignment="Center" HorizontalAlignment="Center" Width="104" Height="104" Fill="LightBlue" StrokeThickness="3">
            <Ellipse.Stroke>
              <LinearGradientBrush>
                <GradientStop Color="DarkBlue" Offset="0" />
                <GradientStop Color="LightBlue" Offset="1" />
              </LinearGradientBrush>
            </Ellipse.Stroke>          
          </Ellipse>
            <Border BorderThickness="1" BorderBrush="Black" Background="White" Margin="20" HorizontalAlignment="Right" VerticalAlignment="Center">
                <TextBlock Name="MonthDay" Text="{Binding}"/>
            </Border>
            <Canvas Width="102" Height="102">
                <Ellipse Width="8" Height="8" Fill="Black" Canvas.Top="46" Canvas.Left="46" />
                <Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="0" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="30" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="60" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="90" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="120" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="150" />
                      </Rectangle.RenderTransform>
                    </Rectangle>
                    <Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
                      <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="180" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="210" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="240" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="270" />
                      </Rectangle.RenderTransform>
                    </Rectangle>
                    <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                      <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="300" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="330" />
                    </Rectangle.RenderTransform>
                </Rectangle>


                <Rectangle x:Name="HourHand" Canvas.Top="21" Canvas.Left="48" 
                            Fill="Black" Width="4" Height="30">
                    <Rectangle.RenderTransform>
                        <RotateTransform x:Name="HourHand2" CenterX="2" CenterY="30" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle x:Name="MinuteHand" Canvas.Top="6" Canvas.Left="49" 
                        Fill="Black" Width="2" Height="45">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="1" CenterY="45" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle x:Name="SecondHand" Canvas.Top="4" Canvas.Left="49" 
                        Fill="Red" Width="1" Height="47">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="0.5" CenterY="47" />
                    </Rectangle.RenderTransform>
                </Rectangle>
            </Canvas>
        </Grid>
    </Grid>
</Page>

Сопутствующий код:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace WPFClock
{
    /// <summary>
    /// Interaction logic for Clock.xaml
    /// </summary>
    public partial class Clock : Page
    {
        private DispatcherTimer _dayTimer;

        public Clock()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Clock_Loaded);

        }

        void Clock_Loaded(object sender, RoutedEventArgs e) {
            // set the datacontext to be today's date
            DateTime now = DateTime.Now;
            DataContext = now.Day.ToString();

            // then set up a timer to fire at the start of tomorrow, so that we can update
            // the datacontext
            _dayTimer = new DispatcherTimer();
            _dayTimer.Interval = new TimeSpan(1, 0, 0, 0) - now.TimeOfDay;
            _dayTimer.Tick += new EventHandler(OnDayChange);
            _dayTimer.Start();

            // finally, seek the timeline, which assumes a beginning at midnight, to the appropriate
            // offset
            Storyboard sb = (Storyboard)PodClock.FindResource("sb");
            sb.Begin(PodClock, HandoffBehavior.SnapshotAndReplace, true);
            sb.Seek(PodClock, now.TimeOfDay, TimeSeekOrigin.BeginTime);
        }

        private void OnDayChange(object sender, EventArgs e)
        {
            // date has changed, update the datacontext to reflect today's date
            DateTime now = DateTime.Now;
            DataContext = now.Day.ToString();
            _dayTimer.Interval = new TimeSpan(1, 0, 0, 0);
        }
    }
}

Результат выглядит следующим образом:

Диалоговое окно “Дата и свойства времени”

Чтобы сравнить конечный результат с кодом, который создал данный снимок экрана, см. раздел Пример взаимодействия с часами Win32.

См. также

Задачи

Пример взаимодействия с часами Win32

Основные понятия

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

Ссылки

HwndSource