Partager via


Didacticiel : créer une application Win32 hébergeant du contenu WPF

Mise à jour : novembre 2007

Pour placer WPF dans des applications Win32, utilisez HwndSource, qui fournit le HWND comprenant votre contenu WPF. Dans un premier temps, vous créez HwndSource, en lui attribuant des paramètres similaires à CreateWindow. Vous indiquez ensuite à HwndSource le contenu WPF que vous souhaitez y inclure. Enfin, vous retirez HWND de HwndSource. Cette procédure pas à pas explique comment créer un WPF mixte dans l'application Win32, qui implémente de nouveau la boîte de dialogue Propriétés de Date et heure du système d'exploitation.

Composants requis

Consultez Vue d'ensemble de l'interopérabilité WPF et Win32.

Comment utiliser ce didacticiel

Ce didacticiel se concentre sur les étapes importantes de la production d'une application d'interopérabilité. Il s'appuie sur un exemple, Interopérabilité de l'horloge Win32, exemple, qui reflète le produit final. Ce didacticiel décrit les étapes comme si vous commenciez à travailler sur un projet Win32 existant qui vous appartient, éventuellement un projet préexistant, et que vous ajoutiez un WPF hébergé à votre application. Vous pouvez comparer votre produit final à Interopérabilité de l'horloge Win32, exemple.

Procédure pas à pas d'inclusion de Windows Presentation Framework dans Win32 (HwndSource)

Le graphique suivant illustre le produit final visé par ce didacticiel :

Boîte de dialogue Propriétés de date et d'heure

Vous pouvez recréer cette boîte de dialogue en créant le projet C++ Win32 dans Microsoft Visual Studio et en utilisant l'éditeur de boîtes de dialogue pour créer ce qui suit :

Boîte de dialogue Propriétés de date et d'heure

(L'utilisation de Microsoft Visual Studio n'est pas nécessaire pour employer HwndSource, pas plus qu'il n'est nécessaire d'utiliser C++ pour écrire des programmes Win32. Toutefois, cette méthode est relativement courante à cette fin et se prête bien à une explication pas à pas du didacticiel).

Vous devez suivre cinq sous-étapes particulières pour placer une horloge WPF dans la boîte de dialogue :

  1. Permettez à votre projet Win32 d'appeler le code managé (/clr) en modifiant ses paramètres dans Microsoft Visual Studio.

  2. Créez un WPFPage dans une DLL distincte.

  3. Placez ce WPFPage dans un HwndSource.

  4. Procurez-vous un HWND pour ce Page à l'aide de la propriété Handle.

  5. Utilisez Win32 pour déterminer où placer le HWND dans la plus grande application Win32

/clr

La première étape consiste à convertir ce projet Win32 non managé en un projet capable d'appeler le code managé. Utilisez l'option du compilateur /clr, qui sera liée aux DLL nécessaires à utiliser, et ajustez la méthode Main en vue de l'utiliser avec WPF.

Pour permettre l'utilisation du code managé dans le projet C++ : cliquez avec le bouton droit sur le projet win32clock, puis sélectionnez Propriétés. Dans la page de propriété Général (valeur par défaut), remplacez la prise en charge de Common Language Runtime par /clr.

Ajoutez ensuite les références aux DLL nécessaires pour WPF : PresentationCore.dll, PresentationFramework.dll, System.dll, WindowsBase.dll, UIAutomationProvider.dll et UIAutomationTypes.dll. (Les instructions suivantes supposent que le système d'exploitation est installé sur le lecteur C:.)

  1. Cliquez avec le bouton droit sur le projet win32clock, puis sélectionnez Références... Procédez ensuite comme suit dans cette boîte de dialogue :

  2. Cliquez avec le bouton droit sur le projet win32clock, puis sélectionnez Références.

  3. Cliquez sur Ajouter une nouvelle référence, cliquez sur l'onglet Parcourir, entrez C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll, puis cliquez sur OK.

  4. Répétez cette procédure pour PresentationFramework.dll : C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll.

  5. Répétez cette procédure pour WindowsBase.dll : C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll.

  6. Répétez cette procédure pour UIAutomationTypes.dll : C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll.

  7. Répétez cette procédure pour UIAutomationProvider.dll : C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll.

  8. Cliquez sur Ajouter une nouvelle référence, sélectionnez System.dll et cliquez sur OK.

  9. Cliquez sur OK afin de quitter les pages de propriétés win32clock pour l'ajout de références.

Enfin, ajoutez STAThreadAttribute à la méthode _tWinMain en vue de son utilisation avec WPF :

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

Cet attribut indique à Common Language Runtime (CLR) que, lorsqu'il initialise COM (Component Object Model), il doit utiliser un modèle de threads cloisonné (STA, Single-Threaded Apartment), qui est nécessaire pour WPF (et Windows Forms).

Créer une page Windows Presentation Framework

Vous créez ensuite une DLL qui définit un PageWPF. Il est souvent plus facile de créer le PageWPF comme une application autonome, et d'écrire et de déboguer ainsi la partie WPF. Une fois terminé, vous pouvez convertir ce projet en DLL en cliquant dessus avec le bouton droit, en sélectionnant Propriétés, en accédant à Application et en remplaçant le type de sortie par Bibliothèque de classes Windows.

Le projet de DLL WPF peut être combiné avec le projet Win32 (solution comprenant deux projets). Pour ce faire, cliquez avec le bouton droit sur la solution, puis sélectionnez Ajouter\Projet existant.

Pour utiliser cette DLL WPF à partir du projet Win32, vous devez ajouter une référence :

  1. Cliquez avec le bouton droit sur le projet win32clock, puis sélectionnez Références...

  2. Cliquez sur Ajouter une nouvelle référence.

  3. Cliquez sur l'onglet Projets. Sélectionnez WPFClock, puis cliquez sur OK.

  4. Cliquez sur OK afin de quitter les pages de propriétés win32clock pour l'ajout de références.

HwndSource

Vous utilisez ensuite HwndSource pour donner à WPFPage l'apparence d'un HWND. Vous ajoutez ce bloc de code dans un fichier 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();
    }
}
}

Il s'agit d'une longue section de code qui mérite explication. La première partie consiste en diverses clauses qui vous évitent d'avoir à qualifier complètement tous les appels :

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

Vous définissez ensuite une fonction qui crée le contenu WPF, place un HwndSource autour et renvoie le HWND.

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

Vous commencez par créer un HwndSource dont les paramètres sont similaires à 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 
            );

Vous créez ensuite la classe de contenu WPF en appelant son constructeur :

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

Vous connectez alors la page à HwndSource :

        source->RootVisual = page;

Enfin, sur la dernière ligne, renvoyez le HWND de HwndSource :

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

Positionner le HWND

Maintenant que vous avez un HWND contenant l'horloge WPF, vous devez placer celui-ci dans la boîte de dialogue Win32. Si vous saviez exactement où le placer, il vous suffirait de communiquer cette taille et cet emplacement à la fonction GetHwnd définie précédemment. Cependant, comme vous avez utilisé un fichier de ressources pour définir la boîte de dialogue, vous n'êtes pas absolument certain du positionnement des HWND. Vous pouvez utiliser l'éditeur de boîtes de dialogue Microsoft Visual Studio pour placer un contrôle statique Win32 là où vous souhaitez insérer l'horloge ("Insérer l'horloge ici"), et utilisez cette information pour positionner l'horloge WPF.

Lorsque vous gérez WM_INITDIALOG, vous utilisez GetDlgItem pour récupérer le HWND de l'espace réservé STATIC :

HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);

Vous calculez ensuite la taille et la position de cet espace réservé STATIC afin de pouvoir placer l'horloge WPF à cet emplacement :

Rectangle 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);

Vous masquez ensuite l'espace réservé STATIC :

ShowWindow(placeholder, SW_HIDE);

Et vous créez le HWND de l'horloge WPF à cet emplacement :

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

Pour rendre le didacticiel intéressant et créer une véritable horloge WPF, vous devrez créer un contrôle d'horloge WPF à ce stade. Cela est essentiellement possible dans les balises, avec quelques gestionnaires d'événements seulement dans le fichier code-behind. Dans la mesure où ce didacticiel traite de l'interopérabilité et non de la conception du contrôle, le code complet de l'horloge WPF est indiqué ici sous forme de bloc de code, sans instructions de développement détaillées ni explication de chaque partie. N'hésitez pas à vous prêter à des expérimentations avec ce code pour modifier l'apparence ou la fonctionnalité du contrôle.

Le balisage se présente comme suit :

<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>

Et voici le code-behind correspondant :

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);
        }
    }
}

Le résultat final se présente comme suit :

Boîte de dialogue Propriétés de date et d'heure

Pour comparer votre résultat final au code qui a permis d'obtenir cette capture d'écran, consultez Interopérabilité de l'horloge Win32, exemple.

Voir aussi

Tâches

Interopérabilité de l'horloge Win32, exemple

Concepts

Vue d'ensemble de l'interopérabilité WPF et Win32

Référence

HwndSource