Share via


Procedura dettagliata: Ospitare un orologio WPF in Win32

Per inserire WPF all'interno di applicazioni Win32, usare HwndSource, che fornisce HWND che contiene il contenuto WPF. Creare prima di tutto , HwndSourceassegnando parametri simili a CreateWindow. Viene quindi indicato il HwndSource contenuto WPF desiderato all'interno di esso. Infine, si ottiene HWND dall'oggetto HwndSource. In questa procedura dettagliata viene illustrato come creare un'applicazione WPF mista all'interno di Un'applicazione Win32 che reimposta la finestra di dialogo Proprietà data e ora del sistema operativo.

Prerequisiti

Vedere Interoperatività di WPF e Win32.

Utilizzo dell'esercitazione

Questa esercitazione si concentra sui passaggi importanti per la produzione di un'applicazione di interoperatività. L'esercitazione è supportata dall'Esempio di interoperatività con clock Win32, ma tale esempio riflette il prodotto finale. Questa esercitazione illustra i passaggi come se si iniziasse con un progetto Win32 esistente personalizzato, ad esempio un progetto preesistente e si aggiungeva un wpf ospitato all'applicazione. È possibile confrontare il prodotto finale con l'Esempio di interoperatività con clock Win32.

Procedura dettagliata di Windows Presentation Framework in Win32 (HwndSource)

Il grafico seguente illustra il prodotto finale previsto di questa esercitazione:

Screenshot that shows the Date and Time Properties dialog box.

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

Recreated Date and Time Properties dialog box

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. Abilitare il progetto Win32 per chiamare codice gestito (/clr) modificando le impostazioni del progetto in Visual Studio.

  2. Creare un wpfPage in una DLL separata.

  3. Inserire wpfPage all'interno di un oggetto HwndSource.

  4. Ottenere un HWND per tale Page oggetto usando la Handle proprietà .

  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. Usare l'opzione del compilatore /clr, che collega alle DLL necessarie da usare e modificare il metodo Main da usare con WPF.

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

Aggiungere quindi riferimenti alle DLL necessarie per WPF: PresentationCore.dll, PresentationFramework.dll, System.dll, WindowsBase.dll, UIAutomationProvider.dll e UIAutomationTypes.dll. (le istruzioni seguenti presuppongono 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, in tale finestra di dialogo,

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

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

  4. Ripetere l'operazione per PresentationFramework.dll: C:\Programmi\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll.

  5. Ripetere l'operazione per WindowsBase.dll: C:\Programmi\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll.

  6. Ripetere l'operazione per UIAutomationTypes.dll: C:\Programmi\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll.

  7. Ripetere l'operazione per UIAutomationProvider.dll: C:\Programmi\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll.

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

  9. Fare clic su OK per uscire dalla pagina delle proprietà di win32clock per aggiungere riferimenti.

Aggiungere infine al STAThreadAttribute_tWinMain metodo da usare con WPF:

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

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

Creare una pagina di Windows Presentation Framework

Successivamente, si crea una DLL che definisce un wpfPage. Spesso è più semplice creare WPFPage come applicazione autonoma e scrivere ed eseguire il debug della parte WPF in questo modo. Al termine, il progetto può essere convertito in una DLL facendo clic con il pulsante destro del mouse su di esso, scegliendo Proprietà, accedendo all'applicazione e modificando il tipo di output su Libreria di classi 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 scegliere 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 dalla pagina delle proprietà di win32clock per aggiungere riferimenti.

HwndSource

Successivamente, si usa HwndSource per fare in modo che WPFPage sia simile a un HWND. Aggiungere questo blocco di codice 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();
    }
}
}

Poiché si tratta di una parte estesa di codice possono essere necessarie alcune spiegazioni. La prima parte contiene varie clausole che evitano di specificare 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;

Si definisce quindi una funzione che crea il contenuto WPF, lo inserisce in HwndSource un oggetto e restituisce HWND:

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

Prima di tutto si crea un HwndSourceoggetto , 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();

Si connette quindi la pagina a HwndSource:

source->RootVisual = page;

E nella riga finale restituire il valore HWND per :HwndSource

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

Posizionamento dell'oggetto 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 definita in precedenza.If you knew just where to put the HWND, you would just pass that size and location to the GetHwnd function you defined earlier. Tuttavia, si è usato un file di risorse per definire la finestra di dialogo perciò non si conosce esattamente la posizione degli oggetti 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.

Dove si gestisce WM_INITDIALOG, si usa GetDlgItem per recuperare HWND per il 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);

Dopodiché 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 interessante l'esercitazione e per produrre un orologio WPF reale, è necessario creare un controllo orologio WPF a questo punto. È possibile eseguire questa operazione principalmente nel markup, con solo alcuni gestori eventi in code-behind. 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. Modificare questo codice per variare l'aspetto o le funzionalità del controllo.

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

Questo è il code-behind associato:

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 è il seguente:

Final result Date and Time Properties dialog box

Per confrontare il risultato finale al codice che ha generato questa schermata, vedere Esempio di interoperatività clock Win32.

Vedi anche