Exemplarische Vorgehensweise: Hosten einer WPF-Uhr in Win32
Wenn Sie WPF-Inhalte in Win32-Anwendungen platzieren möchten, verwenden Sie HwndSource. Hierdurch erhalten Sie das HWND für Ihren WPF-Inhalt. Erstellen Sie zunächst die HwndSource mit Parametern, die denen von CreateWindow ähneln. Informieren Sie dann HwndSource über den aufzunehmenden WPF-Inhalt. Abschließend extrahieren Sie das HWND aus HwndSource. In diesem Beispiel erfahren Sie, wie Sie WPF-Inhalt in einer Win32-Anwendung erstellen, die das Betriebssystemdialogfeld Eigenschaften von Datum und Uhrzeit erneut implementiert.
Vorbereitungsmaßnahmen
Weitere Informationen finden Sie unter Interaktion zwischen WPF und Win32.
So verwenden Sie dieses Lernprogramm
Dieses Lernprogramm konzentriert sich auf die wichtigsten, beim Erstellen einer Interoperationsanwendung erforderlichen Schritte. Das Lernprogramm wird durch ein Beispiel ergänzt (Beispiel für die Win32-Uhrinteroperation), das jedoch unser Endprodukt reflektiert. Die Schritte in diesem Lernprogramm basieren auf der Annahme, dass Sie mit einem eigenen, bereits vorhandenen Win32-Projekt arbeiten und Ihrer Anwendung gehosteten WPF-Inhalt hinzufügen. Sie können Ihr Endprodukt dann mit dem Beispiel unter Beispiel für die Win32-Uhrinteroperation vergleichen.
Beispiel für Windows Presentation Framework-Inhalte in Win32 (HwndSource)
Die folgende Grafik zeigt das in diesem Lernprogramm angestrebte Endprodukt:
Sie können dieses Dialogfeld erstellen, indem Sie zunächst ein C++-Win32-Projekt in Microsoft Visual Studio und dann Folgendes mit dem Dialog-Editor erstellen:
(Sie können HwndSource auch ohne Microsoft Visual Studio verwenden und Win32-Programme auch ohne C++ schreiben, die hier beschriebene Vorgehensweise ist jedoch ein relativ typischer Ansatz und gut für die schrittweise Erläuterung in einem Lernprogramm geeignet.)
Es sind fünf spezifische Schritte erforderlich, um eine WPF-Uhr in das Dialogfeld einzufügen:
Aktivieren Sie Ihr Win32-Projekt so, dass verwalteter Code (/clr) aufgerufen wird, indem Sie die Projekteinstellungen in Microsoft Visual Studio ändern.
Erstellen Sie in einer separaten DLL ein WPF-Page-Element.
Platzieren Sie dieses WPF-Page-Element in einem HwndSource-Element.
Rufen Sie mithilfe der Handle-Eigenschaft ein HWND für diese Page auf.
Verwenden Sie Win32, um zu entscheiden, an welcher Stelle in der größeren Win32-Anwendung das HWND platziert werden soll.
/clr
Konvertieren Sie zunächst das nicht verwaltete Win32-Projekt in ein Projekt, das verwalteten Code aufrufen kann. Verwenden Sie die /clr-Compileroption, die eine Verbindung mit den gewünschten erforderlichen DLLs herstellt, und passen Sie die Main-Methode an die Verwendung mit WPF an.
So ermöglichen Sie die Verwendung von verwaltetem Code im C++-Projekt: Klicken Sie mit der rechten Maustaste auf das win32clock-Projekt, und wählen Sie die Option Eigenschaften aus. Ändern Sie auf der Eigenschaftenseite Allgemein (Standardseite) die Common Language Runtime-Unterstützung in /clr.
Fügen Sie dann Verweise auf die für WPF erforderlichen DLLs hinzu: PresentationCore.dll, PresentationFramework.dll, System.dll, WindowsBase.dll, UIAutomationProvider.dll und UIAutomationTypes.dll. (Bei den folgenden Anleitungen wird davon ausgegangen, dass das Betriebssystem auf dem Laufwerk C: installiert ist.)
Klicken Sie mit der rechten Maustaste auf das win32clock-Projekt, und wählen Sie Verweise... aus. In diesem Dialogfeld:
Klicken Sie mit der rechten Maustaste auf das win32clock-Projekt, und wählen Sie Verweise... aus.
Klicken Sie auf Neuen Verweis hinzufügen, klicken Sie auf die Registerkarte Durchsuchen, geben Sie C:\Programme\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll ein, und klicken Sie auf OK.
Wiederholen Sie diese Schritte für die PresentationFramework.dll: C:\Programme\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll.
Wiederholen Sie diese Schritte für die WindowsBase.dll: C:\Programme\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll.
Wiederholen Sie diese Schritte für die UIAutomationTypes.dll: C:\Programme\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll.
Wiederholen Sie diese Schritte für die UIAutomationProvider.dll: C:\Programme\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll.
Klicken Sie auf Neuen Verweis hinzufügen, wählen Sie die System.dll aus, und klicken Sie auf OK.
Klicken Sie auf OK, um die win32clock-Eigenschaftenseiten zum Hinzufügen von Verweisen zu schließen.
Fügen Sie abschließend STAThreadAttribute zur _tWinMain-Methode für die Verwendung mit WPF hinzu:
[System::STAThreadAttribute]
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
Dieses Attribut zeigt der common language runtime (CLR) an, dass bei der Initialisierung von Component Object Model (COM) ein Singlethread-Apartment-Modell (STA), das für WPF (und für Windows Forms) benötigt wird, verwendet werden muss.
Erstellen einer Windows Presentation Framework-Seite
Als Nächstes erstellen Sie eine DLL, die eine WPF-Page definiert. Häufig ist es am einfachsten, die WPF-Page als eigenständige Anwendung zu erstellen und dann auf diese Weise den WPF-seitigen Teil zu schreiben und zu debuggen. Abschließend können Sie das Projekt in eine DLL konvertieren, indem Sie mit der rechten Maustaste auf das Projekt klicken, Eigenschaften auswählen, zur Anwendung wechseln und den Ausgabetyp in Windows-Klassenbibliothek ändern.
Das WPF-DLL-Projekt kann dann mit dem Win32-Projekt kombiniert werden (eine Projektmappe mit beiden Projekten): Klicken Sie mit der rechten Maustaste auf die Projektmappe, und wählen Sie Vorhandenes Projekt hinzufügen aus.
Um die WPF-DLL aus dem Win32-Projekt zu verwenden, müssen Sie einen Verweis hinzufügen:
Klicken Sie mit der rechten Maustaste auf das win32clock-Projekt, und wählen Sie Verweise... aus.
Klicken Sie auf Neuen Verweis hinzufügen.
Klicken Sie auf die Registerkarte Projekte. Wählen Sie die WPF-Uhr (WPFClock) aus, und klicken Sie auf OK.
Klicken Sie auf OK, um die win32clock-Eigenschaftenseiten zum Hinzufügen von Verweisen zu schließen.
HwndSource
Jetzt verwenden Sie das [T:System.Windows.Interop.HwndSource-Element], um das WPF-Page-Element wie ein HWND aussehen zu lassen. Fügen Sie den folgenden Codeblock zu einer C++-Datei hinzu:
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();
}
}
}
Dieser große Codeabschnitt bedarf wohl noch einiger Erklärungen. Beim ersten Teil handelt es sich um verschiedene Klauseln, damit nicht alle Aufrufe vollständig qualifiziert werden müssen:
namespace ManagedCode
{
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
using namespace System::Windows::Media;
Anschließend definieren Sie eine Funktion, die den WPF-Inhalt erstellt und in HwndSource platziert und das HWND zurückgibt:
HWND GetHwnd(HWND parent, int x, int y, int width, int height) {
Erstellen Sie zuerst eine HwndSource mit Parametern, die denen von CreateWindow ähneln:
HwndSource^ source = gcnew HwndSource(
0, // class style
WS_VISIBLE | WS_CHILD, // style
0, // exstyle
x, y, width, height,
"hi", // NAME
IntPtr(parent) // parent window
);
Dann erstellen Sie die WPF-Inhaltsklasse, indem Sie ihren Konstruktor aufrufen:
UIElement^ page = gcnew WPFClock::Clock();
Jetzt wird die Seite mit der HwndSource verknüpft:
source->RootVisual = page;
In der letzten Zeile wird nun noch das HWND für die HwndSource zurückgegeben:
return (HWND) source->Handle.ToPointer();
Positionieren des HWNDs
Nachdem Sie jetzt ein HWND erstellt haben, das die WPF-Uhr enthält, muss das HWND im Win32-Dialogfeld platziert werden. Falls Sie genau wissen, an welcher Stelle das HWND positioniert werden soll, übergeben Sie einfach seine Größe und Position an die zuvor definierte GetHwnd-Funktion. Da Sie jedoch das Dialogfeld über eine Ressourcendatei definiert haben, können Sie nicht genau wissen, wo die einzelnen HWNDs positioniert sind. Sie können mit dem Dialog-Editor in Microsoft Visual Studio ein Win32-STATIC-Steuerelement an der für die Uhr gewünschten Position einfügen ("Uhr hier einfügen") und dann die WPF-Uhr mithilfe dieses Steuerelements platzieren.
Bei der Behandlung von WM_INITDIALOG können Sie mit GetDlgItem das HWND für den STATIC-Platzhalter abrufen:
HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);
Anschließend werden die Größe und die Position dieses STATIC-Platzhalters berechnet, und die WPF-Uhr wird an dieser Stelle eingefügt:
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);
Jetzt blenden Sie den STATIC-Platzhalter aus:
ShowWindow(placeholder, SW_HIDE);
Und nun wird an dieser Stelle das HWND für die WPF-Uhr erstellt:
HWND clock = ManagedCode::GetHwnd(hDlg, point.x, point.y, width, height);
Damit dieses Lernprogramm für Sie interessanter wird und um eine echte WPF-Uhr zu erstellen, müssen Sie an dieser Stelle ein Steuerelement für die WPF-Uhr erstellen. Dies geschieht zum größten Teil mittels Markup mit nur einigen Ereignishandlern im Code-Behind. Da das Thema dieses Lernprogramms die Interoperation und nicht der Entwurf von Steuerelementen ist, wird der vollständige Code für die WPF-Uhr als Codeblock zur Verfügung gestellt, ohne weitere Informationen zum Aufbau und der Bedeutung der einzelnen Abschnitte. Sie können gerne mit diesem Code experimentieren und die Darstellung und Funktionen des Steuerelements ändern.
Es folgt das Markup:
<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>
Und dies ist das zugehörige Code-Behind:
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);
}
}
}
Das Endergebnis sieht wie folgt aus:
Wenn Sie Ihr Ergebnis mit dem Code für diese Abbildung vergleichen möchten, rufen Sie das Beispiel unter Beispiel für die Win32-Uhrinteroperation auf.
Siehe auch
Referenz
Konzepte
Interaktion zwischen WPF und Win32