Condividi tramite


Procedura dettagliata: hosting di un oggetto Clock WPF in Win32

Per inserire WPF in applicazioni Win32, utilizzare HwndSource che fornisce HWND con il contenuto WPF. Innanzitutto, viene creato HwndSource a cui si assegnano parametri simili a CreateWindow. Quindi, viene comunicato a HwndSource quale contenuto WPF si desidera inserire. Infine, viene estratto HWND da HwndSource. In questa procedura dettagliata viene illustrato come creare WPF misto nell'applicazione Win32 che implementa nuovamente la finestra di dialogo Proprietà data e ora del sistema operativo.

Prerequisiti

Vedere Interoperatività di WPF e Win32.

Istruzioni per l'utilizzo di questa esercitazione

In questa esercitazione vengono illustrati i passaggi più importanti nella produzione di un'applicazione di interoperatività. L'esercitazione è supportata da un esempio, Esempio di interoperatività con l'orologio Win32 che riflette il prodotto finale. In questa esercitazione vengono documentati i passaggi necessari a partire da un progetto Win32 esistente, magari preesistente, e con l'aggiunta di WPF ospitato nell'applicazione. È possibile confrontare il prodotto finale con Esempio di interoperatività con l'orologio Win32 (la pagina potrebbe essere in inglese).

Procedura dettagliata di Windows Presentation Framework in Win32 (HwndSource)

Nelle figure seguenti viene illustrato il prodotto finale di questa esercitazione:

Finestra di dialogo Proprietà data e ora

È possibile ricreare questa finestra di dialogo creando un progetto Win32 C++ in Microsoft Visual Studio e utilizzando l'editor della finestra di dialogo per creare quanto segue:

Finestra di dialogo Proprietà data e ora

Non è necessario Microsoft Visual Studio per utilizzare HwndSource, né occorre utilizzare C++ per scrivere programmi Win32, benché si tratti di un modo di procedere tipico adatto alla spiegazione di un'esercitazione.

È necessario eseguire cinque sottopassaggi particolari per inserire un orologio WPF nella finestra di dialogo:

  1. Consentire la chiamata di codice gestito (/clr) dal progetto Win32 modificando le impostazioni del progetto in Microsoft Visual Studio.

  2. Creare Page WPF in una DLL separata.

  3. Inserire Page WPF in HwndSource.

  4. Ottenere HWND per Page utilizzando la proprietà Handle.

  5. Utilizzare Win32 per decidere dove posizionare HWND all'interno dell'applicazione Win32 più grande.

/clr

Il primo passaggio consiste nel trasformare il progetto Win32 non gestito in un progetto in grado di chiamare codice gestito. Viene utilizzata l'opzione del compilatore /clr per stabilire il collegamento alle DLL necessarie da utilizzare e viene impostato il metodo Main per l'utilizzo con WPF.

Per consentire l'utilizzo di codice gestito nel progetto C++, fare clic con il pulsante destro del mouse sul progetto win32clock e selezionare Proprietà. Nella pagina delle proprietà Generale (impostazione predefinita), impostare il supporto Common Language Runtime su /clr.

In seguito, aggiungere riferimenti alle DLL necessarie per WPF: PresentationCore.dll, PresentationFramework.dll, System.dll, WindowsBase.dll, UIAutomationProvider.dll e UIAutomationTypes.dll. Nelle istruzioni riportate di seguito si suppone che il sistema operativo sia installato nell'unità C:.

  1. Fare clic con il pulsante destro del mouse sul progetto win32clock e scegliere Riferimenti..., quindi nella finestra di dialogo:

  2. Fare clic con il pulsante destro del mouse sul progetto win32clock e scegliere Riferimenti....

  3. Scegliere Aggiungi nuovo riferimento, fare clic sulla scheda Sfoglia, immettere C:\Programmi\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll e scegliere OK.

  4. Ripetere il passaggio per PresentationFramework.dll: C:\Programmi\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll.

  5. Ripetere il passaggio per WindowsBase.dll: C:\Programmi\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll.

  6. Ripetere il passaggio per UIAutomationTypes.dll: C:\Programmi\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll.

  7. Ripetere il passaggio per UIAutomationProvider.dll: C:\Programmi\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll.

  8. Scegliere Aggiungi nuovo riferimento, selezionare System.dll e fare clic su OK.

  9. Fare clic su OK per uscire dalle pagine delle proprietà di win32clock per l'aggiunta di riferimenti.

Infine, aggiungere STAThreadAttribute al metodo _tWinMain per l'utilizzo con WPF:

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

Con questo attributo si comunica a common language runtime (CLR) che, durante l'inizializzazione di Component Object Model (COM), è necessario utilizzare un modello di apartment a thread singolo (STA), richiesto per WPF e Windows Forms.

Creare una pagina di Windows Presentation Framework

Successivamente, viene creata una DLL che definisce Page WPF. È spesso più facile creare Page WPF come applicazione autonoma, quindi scrivere ed eseguire il debug della parte WPF nel modo indicato. Dopo questa operazione, è possibile convertire il progetto in una DLL facendo clic con il pulsante destro del mouse sul progetto stesso, scegliendo Proprietà, Applicazione, quindi impostando Tipo di output su Libreria di classi Windows.

Il progetto di DLL WPF può quindi essere combinato con il progetto Win32 (una soluzione che contiene due progetti). Fare clic con il pulsante destro del mouse sulla soluzione e selezionare Aggiungi progetto esistente.

Per utilizzare la DLL WPF dal progetto Win32, è necessario aggiungere un riferimento.

  1. Fare clic con il pulsante destro del mouse sul progetto win32clock e scegliere Riferimenti....

  2. Scegliere Aggiungi nuovo riferimento.

  3. Fare clic sulla scheda Progetti. Selezionare WPFClock e fare clic su OK.

  4. Fare clic su OK per uscire dalle pagine delle proprietà di win32clock per l'aggiunta di riferimenti.

HwndSource

Quindi, utilizzare HwndSource in modo che Page WPF abbia l'aspetto di HWND. Il seguente blocco di codice viene aggiunto a un file 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();
    }
}
}

Trattandosi di codice complesso, sono necessarie spiegazioni più dettagliate. Nella prima parte sono presenti varie clausole per evitare di indicare il nome completo di tutte le chiamate:

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

Viene quindi definita una funzione per creare il contenuto WPF, attorno al quale viene inserito HwndSource e viene restituito HWND:

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

Innanzitutto, viene creato HwndSource, con parametri simili a 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 
            );

Viene quindi creata la classe contenuto WPF mediante la chiamata al relativo costruttore:

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

La pagina viene quindi connessa a HwndSource:

        source->RootVisual = page;

Nella riga finale, viene restituito HWND per HwndSource:

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

Posizionamento di HWND

A questo punto si dispone di HWND contenente l'orologio WPF ed è necessario inserirlo nella finestra di dialogo di Win32. Se si conosce la posizione in cui inserire HWND, è sufficiente passare tali dimensione e posizione alla funzione GetHwnd definita in precedenza. In questo caso, tuttavia, si è utilizzato un file di risorse per definire la finestra di dialogo, pertanto non si conosce esattamente la posizione degli oggetti HWND. È possibile utilizzare l'editor finestre di Microsoft Visual Studio per inserire un controllo STATIC Win32 nel punto in cui si desidera posizionare l'orologio ("Insert clock here") e utilizzare tale controllo per posizionare l'orologio WPF.

Nella gestione di WM_INITDIALOG, viene utilizzato GetDlgItem per recuperare HWND per il segnaposto STATIC:

HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);

Vengono quindi calcolate la dimensione e la posizione del segnaposto STATIC, per consentire l'inserimento dell'orologio WPF in tale punto:

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

Viene quindi nascosto il segnaposto STATIC:

ShowWindow(placeholder, SW_HIDE);

Viene creato l'oggetto HWND orologio WPF in tale posizione:

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

Per rendere interessante l'esercitazione e realizzare un vero orologio WPF, è necessario creare un controllo orologio WPF a questo punto. È possibile procedere in questo modo nel markup, con pochi gestori eventi in code-behind. Poiché questa esercitazione fa riferimento all'interoperatività e non alla progettazione dei controlli, il codice completo per l'orologio WPF viene fornito come blocco di codice, senza istruzioni specifiche per la compilazione, né spiegazioni sulle singole parti. È possibile modificare questo codice per dare al controllo aspetto e funzionalità differenti.

Il markup è il seguente:

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

Il code-behind correlato è il seguente:

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

Il risultato finale sarà analogo al seguente:

Finestra di dialogo Proprietà data e ora

Per confrontare il risultato finale al codice con cui è stata prodotta la schermata qui riportata, vedere Esempio di interoperatività con l'orologio Win32.

Vedere anche

Riferimenti

HwndSource

Concetti

Interoperatività di WPF e Win32

Altre risorse

Esempio di interoperatività con l'orologio Win32