Condividi tramite


Procedura dettagliata: Ospitare un orologio WPF in Win32

Per inserire WPF all'interno di applicazioni Win32, usa HwndSource, che fornisce l'HWND che contiene il tuo contenuto WPF. Prima di tutto si crea il HwndSource, assegnando parametri simili a CreateWindow. Dite quindi al HwndSource quale contenuto WPF desiderate inserire al suo interno. Infine, si ottiene HWND dal HwndSource. Questa procedura dettagliata illustra come creare un'applicazione WPF mista all'interno di un'applicazione Win32 che reimplementa la finestra di dialogo Proprietà data e ora del sistema operativo .

Prerequisiti

Vedere WPF e l'interoperabilità Win32.

Come usare questa esercitazione

Questa esercitazione si concentra sui passaggi importanti della produzione di un'applicazione di interoperabilità. L'esercitazione è supportata da un esempio, esempio di interoperabilità Win32 per orologio, che rappresenta il prodotto finale. Questa esercitazione illustra i passaggi come se si iniziasse con un progetto Win32 esistente, ad esempio un progetto preesistente, e si stesse aggiungendo un WPF ospitato all'applicazione. È possibile confrontare il prodotto finale con l'esempio di interoperabilità dell'orologio Win32 .

Guida di Windows Presentation Framework all'interno di Win32 (HwndSource)

La figura seguente illustra il prodotto finale previsto di questa esercitazione:

Screenshot che mostra la finestra di dialogo Proprietà Data e Ora.

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

finestra di dialogo Proprietà data e ora ricreate

Non è necessario usare Visual Studio per usare HwndSourcee non è necessario usare C++ per scrivere programmi Win32, ma si tratta di un modo abbastanza tipico per farlo e si presta bene a una spiegazione dettagliata dell'esercitazione.

Per inserire un orologio WPF nella finestra di dialogo, è necessario eseguire cinque passaggi secondari specifici:

  1. Abilita il progetto Win32 per effettuare chiamate a codice gestito (/clr) modificando le impostazioni del progetto in Visual Studio.

  2. Creare unPage WPF in una DLL separata.

  3. Metti quel WPFPage all'interno di un HwndSource.

  4. Ottieni un HWND per la Page usando la proprietà Handle.

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

/Clr

Il primo passaggio consiste nel trasformare questo progetto Win32 non gestito in un progetto che può chiamare codice gestito. Utilizzare l'opzione del compilatore /clr, che collega alle DLL necessarie di cui hai bisogno, e modificare il metodo Main per l'uso con WPF.

Per abilitare l'uso del codice gestito all'interno del progetto C++: fare clic con il pulsante destro del mouse sul progetto win32clock e selezionare Proprietà. Nella pagina delle proprietà Generale (impostazione predefinita), modificare il supporto per il Common Language Runtime in /clr.

Aggiungere quindi riferimenti alle DLL necessarie per WPF: PresentationCore.dll, PresentationFramework.dll, System.dll, WindowsBase.dll, UIAutomationProvider.dlle UIAutomationTypes.dll. Le istruzioni seguenti presuppongono che il sistema operativo sia installato nell'unità C: .

  1. Cliccare con il tasto destro sul progetto win32clock e selezionare Riferimenti..., e nel relativo dialogo:

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

  3. Fare clic su Aggiungi nuovo riferimento, quindi sulla scheda Sfoglia, inserire C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dlle fare clic su OK.

  4. Ripetere per PresentationFramework.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll.

  5. Ripetere per WindowsBase.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll.

  6. Ripetere per UIAutomationTypes.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll.

  7. Ripetere per UIAutomationProvider.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll.

  8. Fare clic su Aggiungi nuovo riferimento, selezionare System.dlle fare clic su OK.

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

Aggiungere infine il STAThreadAttribute al metodo _tWinMain da usare con WPF:

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

Questo attributo indica al Common Language Runtime (CLR) che, quando inizializza Component Object Model (COM), deve usare un modello a singolo thread (STA), necessario per WPF (e Windows Forms).

Creare una pagina di Windows Presentation Framework

Successivamente, si crea una DLL che definisce una WPFPage. Spesso è più semplice creare ilPage WPF come applicazione autonoma e scrivere ed eseguire il debug della parte WPF in questo modo. Al termine, il progetto può essere trasformato in una DLL cliccando con il tasto destro sul progetto, selezionando Proprietà, quindi entrando nella sezione Applicazione e modificando il tipo di output in Libreria di classi di Windows.

Il progetto 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, selezionare Aggiungi\Progetto esistente.

Per usare la DLL WPF dal progetto Win32, è necessario aggiungere un riferimento:

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

  2. Fare clic su Aggiungi nuovo riferimento.

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

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

HwndSource

Successivamente, si userà HwndSource per fare in modo che WPFPage appaia simile a un HWND. Questo 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();
    }
}
}

Si tratta di una lunga parte di codice che potrebbe usare una spiegazione. La prima parte è costituito da varie clausole in modo che non sia necessario qualificare completamente tutte le chiamate:

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

Poi, si definisce una funzione che crea il contenuto WPF, lo circonda con un HwndSource e restituisce l'HWND:

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

Prima di tutto si crea un HwndSource, i cui parametri sono 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
);

Creare quindi la classe di contenuto WPF chiamando il relativo costruttore:

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

Collega quindi la pagina al HwndSource:

source->RootVisual = page;

E nella riga finale restituire il valore HWND per il HwndSource:

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

Posizionamento di Hwnd

Ora che si dispone di un HWND che contiene l'orologio WPF, è necessario inserire tale HWND all'interno della finestra di dialogo Win32. Se si conoscesse solo dove inserire HWND, è sufficiente passare tale dimensione e posizione alla funzione GetHwnd definita in precedenza. Tuttavia, è stato usato un file di risorse per definire la finestra di dialogo, quindi non si è del tutto certi della posizione degli HWND. È possibile usare l'editor della finestra di dialogo di Visual Studio per inserire un controllo WIN32 STATIC in cui si desidera che l'orologio vada ("Inserisci orologio qui") e usarlo per posizionare l'orologio WPF.

Quando si gestisce WM_INITDIALOG, si utilizza GetDlgItem per recuperare l'HWND relativo al segnaposto STATIC.

HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);

È quindi possibile calcolare le dimensioni e la posizione del segnaposto STATIC, in modo da poter inserire l'orologio WPF in tale posizione:

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

Quindi si nasconde il segnaposto STATIC:

ShowWindow(placeholder, SW_HIDE);

Creare il formato HWND dell'orologio WPF in tale posizione:

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

Per rendere l'esercitazione interessante e per produrre un orologio WPF effettivo, è necessario a questo punto creare un controllo orologio WPF. È possibile farlo principalmente nel markup, con pochi gestori di eventi nel codice sottostante. Poiché questa esercitazione riguarda l'interoperabilità e non la progettazione dei controlli, il codice completo per l'orologio WPF viene fornito qui come blocco di codice, senza istruzioni discrete per la compilazione o il significato di ogni parte. È possibile provare a usare questo codice per modificare l'aspetto o la funzionalità del controllo.

Ecco il markup:

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

Di seguito è riportato il file di codice associato a corredo:

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 è simile al seguente:

finestra di dialogo Proprietà data e ora risultato finale

Per confrontare il risultato finale con il codice che ha generato questo screenshot, vedere Esempio di interoperabilità del Clock Win32.

Vedere anche