Пошаговое руководство. Размещение часов WPF в Win32
Чтобы поместить WPF внутри Win32 приложения, используют HwndSource, который предоставляет HWND, который содержит ваши WPF содержимое. Сначала вы создаете HwndSource, предоставив ему параметры, аналогичные CreateWindow. Указать HwndSource о WPF содержимого, которое требуется поместить. Наконец, вы получаете HWND из HwndSource. В этом пошаговом руководстве описывается создание смешанного WPF внутри Win32 приложение, которое повторно реализует операционной системы свойства даты и времени диалоговое окно.
Предварительные требования
См. в разделе взаимодействие WPF и Win32.
Как использовать этот учебник
Основное внимание в этом учебнике уделяется важным шагам в процессе создания приложения взаимодействия. Пример, поддерживаемый руководства пример взаимодействия часов Win32, но этот пример характеризует работу конечного продукта. Этом учебнике приведены шаги, как если бы вы начали с существующим Win32 собственного проекта, возможно с уже существующим проектом и добавили размещенных WPF в приложение. Вы можете сравнить ваш конечный продукт с пример взаимодействия часов Win32.
Пошаговое руководство по WPF внутри Win32 (HwndSource)
На следующем рисунке показан конечный продукт, который должен получиться в результате выполнения инструкций из этого учебника:
Это диалоговое окно можно воссоздать, создав C++ Win32 в проекте Microsoft Visual Studioи использование редактора диалоговых окон для создания следующих:
(Необходимо использовать Microsoft Visual Studio использовать HwndSource, и вам не нужно использовать C++ для написания Win32 программы, но это является весьма распространенный способ сделать это и хорошо подходит для пошагового объяснения в учебнике).
Вам необходимо выполнить пять вложенных шагов, чтобы поставить WPF часов в диалоговое окно:
Включение вашего Win32 проект, чтобы вызывать управляемый код (/CLR), изменив параметры проекта в Microsoft Visual Studio.
Создание WPF Page в отдельной библиотеке DLL.
Поместите этот WPF Page внутри HwndSource.
Используйте Win32 выбрать место для размещения HWND внутри более крупной Win32 приложения
/clr
Первым делом необходимо преобразовать этот неуправляемый Win32 проект, в который можно вызывать управляемый код. Используйте параметр компилятора/CLR, который будет связан с необходимыми библиотеками DLL, вы хотите использовать и скорректирует основной метод для использования с WPF.
Чтобы включить использование управляемого кода в проекте C++: Правой кнопкой мыши проект win32clock и выберите свойства. На Общие страницы свойств (по умолчанию), измените поддержку общеязыковой на /clr
.
Добавьте ссылки на библиотеки DLL, необходимые для WPF: PresentationCore.dll, PresentationFramework.dll, System.dll, WindowsBase.dll, UIAutomationProvider.dll и UIAutomationTypes.dll. (Следующие инструкции предполагают, что операционная система установлена на диске C:.)
Щелкните правой кнопкой мыши проект win32clock и выберите ссылки... , а внутри этого диалогового окна:
Щелкните правой кнопкой мыши проект win32clock и выберите ссылки... .
Нажмите кнопку добавить новую ссылку, перейдите на вкладку, введите C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll и нажмите кнопку ОК.
Повторите для PresentationFramework.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll.
Повторите для WindowsBase.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll.
Повторите для UIAutomationTypes.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll.
Повторите для UIAutomationProvider.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll.
Нажмите кнопку добавить новую ссылку, выберите System.dll и нажмите кнопку ОК.
Нажмите кнопку ОК чтобы выйти из страниц свойств win32clock для добавления ссылок.
Наконец, добавьте STAThreadAttribute
для _tWinMain
метод для использования с WPF:
[System::STAThreadAttribute]
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
Этот атрибут сообщает CLR , когда он инициализирует Модель COM, его следует использовать модель однопотокового подразделения (STA), который необходим для WPF (и Windows Forms).
Создание страницы WPF
Создайте библиотеку DLL, которая определяет WPF Page. Часто бывает проще всего создать WPF Page как автономное приложение и записать и отладить WPF таким способом часть. После этого проект можно преобразовать в библиотеку DLL, щелкните правой кнопкой мыши проект, щелкнув свойства, перейдя в приложение и изменив тип выходных данных на библиотеку классов Windows.
WPF Проект библиотеки dll можно объединить с Win32 (одно решение, которое содержит два проекта) — проект, щелкните правой кнопкой мыши решение, выберите добавить/существующий проект.
Чтобы использовать эту WPF dll из Win32 проекта, необходимо добавить ссылку:
Щелкните правой кнопкой мыши проект win32clock и выберите ссылки... .
Нажмите кнопку добавить новую ссылку.
Щелкните вкладку Проекты. Выберите WPFClock, нажмите кнопку "ОК".
Нажмите кнопку ОК чтобы выйти из страниц свойств win32clock для добавления ссылок.
HwndSource
Далее используется HwndSource вносить WPF Page вид 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 редактор диалоговых окон, чтобы поместить Win32 СТАТИЧЕСКОГО элемента управления, где требуется часов («вставить часы здесь») и использовать его для размещения WPF часов.
Месте обработки WM_INITDIALOG можно использовать GetDlgItem
Чтобы получить HWND для местозаполнителя STATIC:
HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);
Затем вычисляется размер и положение местозаполнителя STATIC, чтобы можно было поместить WPF clock в этом месте:
Прямоугольник RECT;
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);
И создайте WPF HWND в этом расположении часов:
HWND clock = ManagedCode::GetHwnd(hDlg, point.x, point.y, width, height);
Чтобы сделать это руководство интересным и создать настоящие WPF часов, вам нужно будет создать WPF управления часов на этом этапе. Это делается, в основном, с помощью разметки, с использованием нескольких обработчиков событий в коде программной части. Поскольку это руководство о взаимодействии, а не о разработке элементов управления, полный код для WPF часов предоставляется здесь как блок кода без отдельных указаний по его построению и что означает каждая часть. Вы можете экспериментировать с этим кодом, чтобы изменить внешний вид и функциональность элемента управления.
Ниже приводится пример разметки:
<Page x:Class="WPFClock.Clock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://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.