Så här värdar du en WPF-klocka i Win32
Om du vill placera WPF i Win32-program använder du HwndSource, som tillhandahåller den HWND som innehåller ditt WPF-innehåll. Först skapar du HwndSourceoch ger den parametrar som liknar CreateWindow. Sedan berättar du för HwndSource om WPF-innehållet du vill ha i det. Slutligen får du ut HWND från HwndSource. Den här genomgången visar hur du skapar en blandad WPF i Win32-programmet som omimplementera operativsystemet dialogruta för datum- och tidsegenskaper.
Förutsättningar
Se WPF och Win32 Interoperation.
Så här använder du den här självstudien
Den här handledningen fokuserar på de viktiga stegen för att skapa en interoperabilitetsapplikation. Självstudien stöds av ett exempel, Win32 Clock Interoperation Sample, men det exemplet är representativt för slutprodukten. I den här självstudien beskrivs stegen som om du började med ett eget Win32-projekt, kanske ett befintligt projekt, och du lade till en värdbaserad WPF i ditt program. Du kan jämföra din slutprodukt med exemplet på Win32 klocksamverkan.
En genomgång av Windows Presentation Framework inuti Win32 (HwndSource)
Följande bild visar den avsedda slutprodukten i denna handledning:
Du kan återskapa den här dialogrutan genom att skapa ett C++ Win32-projekt i Visual Studio och använda dialogredigeraren för att skapa följande:
(Du behöver inte använda Visual Studio för att använda HwndSource, och du behöver inte använda C++ för att skriva Win32-program, men det här är ett ganska typiskt sätt att göra det och lämpar sig väl för en stegvis självstudieförklaring).
Du måste utföra fem specifika understeg för att kunna placera en WPF-klocka i dialogrutan:
Aktivera ditt Win32-projekt för att anropa hanterad kod (/clr) genom att ändra projektinställningarna i Visual Studio.
Skapa en WPF-Page i en separat DLL.
Placera WPF-Page i en HwndSource.
Använd Win32 för att bestämma var HWND ska placeras i det större Win32-programmet
/Clr
Det första steget är att omvandla det här ohanterade Win32-projektet till ett som kan anropa hanterad kod. Du använder kompilatoralternativet /clr, som länkar till nödvändiga DLL:er som du vill använda, och justerar main-metoden för användning med WPF.
Aktivera användning av hanterad kod i C++-projektet: Högerklicka på win32clock-projektet och välj Egenskaper. På egenskapssidan Allmänt (standard) ändrar du Stöd för Common Language Runtime till /clr
.
Lägg sedan till referenser till DLL:er som krävs för WPF: PresentationCore.dll, PresentationFramework.dll, System.dll, WindowsBase.dll, UIAutomationProvider.dlloch UIAutomationTypes.dll. (Följande instruktioner förutsätter att operativsystemet är installerat på C: enhet.)
Högerklicka på win32clock-projektet och välj Referenser...och i dialogrutan:
Högerklicka på win32clock-projektet och välj Referenser....
Klicka på Lägg till ny referens, klicka på fliken Bläddra, ange C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dlloch klicka på OK.
Upprepa för PresentationFramework.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll.
Upprepa för WindowsBase.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll.
Upprepa för UIAutomationTypes.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll.
Upprepa för UIAutomationProvider.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll.
Klicka på Lägg till ny referens, välj System.dlloch klicka på OK.
Klicka på OK för att avsluta win32clock-egenskapssidorna för att lägga till referenser.
Lägg slutligen till STAThreadAttribute
till metoden _tWinMain
för användning med WPF:
[System::STAThreadAttribute]
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
Det här attributet anger för CLR (Common Language Runtime) att när den initierar Komponentobjektmodell (COM) ska den använda en enkel trådad lägenhetsmodell (STA), vilket är nödvändigt för WPF (och Windows Forms).
Skapa en Windows Presentation Framework-sida
Sedan skapar du en DLL som definierar en WPF-Page. Det är ofta enklast att skapa WPF-Page som ett fristående program och skriva och felsöka WPF-delen på det sättet. När projektet är klart kan det omvandlas till en DLL genom att högerklicka på projektet, klicka på Egenskaper, gå till programmet och ändra utdatatyp till Windows-klassbibliotek.
WPF-dll-projektet kan sedan kombineras med Win32-projektet (en lösning som innehåller två projekt) – högerklicka på lösningen, välj Lägg till\Befintligt projekt.
Om du vill använda WPF-dll:en från Win32-projektet måste du lägga till en referens:
Högerklicka på win32clock-projektet och välj Referenser....
Klicka på Lägg till ny referens.
Klicka på fliken Projekt. Välj WPFClock och klicka på OK.
Klicka på OK för att avsluta win32clock-egenskapssidorna för att lägga till referenser.
HwndSource
Sedan använder du HwndSource för att få WPF-Page att se ut som en HWND. Du lägger till det här kodblocket i en C++-fil:
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();
}
}
}
Det här är en lång kod som kan behöva en förklaring. Den första delen är olika villkor så att du inte behöver fullt kvalificera alla anrop.
namespace ManagedCode
{
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
using namespace System::Windows::Media;
Sedan definierar du en funktion som skapar WPF-innehållet, placerar en HwndSource runt den och returnerar HWND:
HWND GetHwnd(HWND parent, int x, int y, int width, int height) {
Först skapar du en HwndSource, vars parametrar liknar 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
);
Sedan skapar du WPF-innehållsklassen genom att anropa konstruktorn:
UIElement^ page = gcnew WPFClock::Clock();
Sedan ansluter du sidan till HwndSource:
source->RootVisual = page;
I den sista raden returnerar du HWND för HwndSource:
return (HWND) source->Handle.ToPointer();
Positionera fönsterhandtaget
Nu när du har en HWND som innehåller WPF-klockan måste du placera HWND i win32-dialogrutan. Om du visste var du skulle placera HWND skulle du bara skicka den storleken och platsen till den GetHwnd
funktion som du definierade tidigare. Men du använde en resursfil för att definiera dialogrutan, så du är inte exakt säker på var någon av HWND:erna är placerade. Du kan använda Visual Studio-dialogredigeraren för att placera en Win32 STATIC-kontroll där du vill att klockan ska gå ("Infoga klocka här") och använda den för att placera WPF-klockan.
Där du hanterar WM_INITDIALOG använder du GetDlgItem
för att hämta HWND för platshållaren STATIC:
HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);
Sedan beräknar du storleken och positionen för platshållaren STATIC, så att du kan placera WPF-klockan på den platsen:
REKT-rektangel;
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);
Sedan döljer du platshållaren STATIC:
ShowWindow(placeholder, SW_HIDE);
Och skapa WPF-klockan HWND i det läget:
HWND clock = ManagedCode::GetHwnd(hDlg, point.x, point.y, width, height);
För att göra självstudien intressant och för att skapa en riktig WPF-klocka måste du skapa en WPF-klockkontroll vid denna tidpunkt. Du kan göra det till största delen i markup, med bara några få händelsehanterare i koden bakom. Eftersom den här självstudien handlar om interoperation och inte om kontrolldesign, tillhandahålls fullständig kod för WPF-klockan här som ett kodblock, utan diskreta instruktioner för att skapa den eller vad varje del betyder. Experimentera gärna med den här koden för att ändra kontrollens utseende och känsla eller funktionalitet.
Här är markering:
<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>
Och här är den medföljande koden bakom:
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);
}
}
}
Slutresultatet ser ut så här:
För att jämföra slutresultatet med koden som skapade den här skärmbilden, se Win32 Clock Interoperation Sample.
Se även
.NET Desktop feedback