Návod: Hostování hodin WPF ve Win32

Chcete-li umístit WPF do aplikací Win32, použijte HwndSource, který poskytuje HWND, který obsahuje váš obsah WPF. Nejprve vytvoříte parametr HwndSource, který bude vypadat podobně jako CreateWindow. Pak řeknete HwndSource o obsahu WPF, který chcete uvnitř. Konečně, dostaneš HWND ven z HwndSource. Tento názorný postup ukazuje, jak vytvořit smíšenou aplikaci WPF uvnitř aplikace Win32, která znovu vytvoří dialogové okno Vlastnosti data a času operačního systému.

Předpoklady

Viz interoperabilita WPF a Win32.

Jak používat tento kurz

Tento kurz se zaměřuje na důležité kroky při vytváření interoperační aplikace. Tento kurz je podporován ukázkou Win32 Clock Interoperation Sample, ale tento vzorek je reflexní pro koncový produkt. Tento kurz popisuje kroky, jako kdybyste začali s existujícím projektem Win32, možná už existujícím projektem, a přidali jste do své aplikace hostovaný WPF. Koncový produkt můžete porovnat s ukázkou spolupráce Win32 Clock.

Návod k rozhraní Windows Presentation Framework v systému Win32 (HwndSource)

Následující obrázek ukazuje zamýšlený koncový produkt tohoto kurzu:

Screenshot that shows the Date and Time Properties dialog box.

Toto dialogové okno můžete znovu vytvořit tak, že vytvoříte projekt C++ Win32 v sadě Visual Studio a pomocí editoru dialogů vytvoříte následující:

Recreated Date and Time Properties dialog box

(K použití HwndSourcenemusíte používat Visual Studio a nemusíte používat C++ k psaní programů Win32, ale je to poměrně typický způsob, jak to udělat, a hodí se pro vysvětlení krokových kurzů).

Abyste mohli vložit hodiny WPF do dialogového okna, musíte provést pět konkrétních dílčích kroků:

  1. Povolte projekt Win32 tak, aby volal spravovaný kód (/clr) změnou nastavení projektu v sadě Visual Studio.

  2. Vytvořte WPFPage v samostatné knihovně DLL.

  3. Vložte wpfPage dovnitř HwndSource.

  4. Získejte HWND pro toto Page použití Handle vlastnosti.

  5. Použití Win32 k rozhodnutí, kam umístit HWND v rámci větší aplikace Win32

/Clr

Prvním krokem je převést tento nespravovaný projekt Win32 na projekt, který může volat spravovaný kód. Použijete možnost kompilátoru /clr, která propojuje s potřebnými knihovnami DLL, které chcete použít, a upravíte metodu Main pro použití s WPF.

Pokud chcete povolit použití spravovaného kódu v projektu C++: Klikněte pravým tlačítkem myši na projekt win32clock a vyberte Vlastnosti. Na stránce vlastností Obecné (výchozí) změňte podporu modulu ClR (Common Language Runtime) na /clr.

Dále přidejte odkazy na knihovny DLL potřebné pro WPF: PresentationCore.dll, PresentationFramework.dll, System.dll, WindowsBase.dll, UIAutomationProvider.dll a UIAutomationTypes.dll. (Následující pokyny předpokládají, že operační systém je nainstalován na jednotce C: .)

  1. Klikněte pravým tlačítkem myši na projekt win32clock a vyberte Odkazy... a v dialogovém okně:

  2. Klikněte pravým tlačítkem na projekt win32clock a vyberte Odkazy....

  3. Klepněte na tlačítko Přidat nový odkaz, klepněte na tlačítko Procházet kartu, zadejte C:\Program Files\Reference Sestavení\Microsoft\Framework\v3.0\PresentationCore.dll a klepněte na tlačítko OK.

  4. Opakujte pro PresentationFramework.dll: C:\Program Files\Reference Assemblyes\Microsoft\Framework\v3.0\PresentationFramework.dll.

  5. Opakujte pro WindowsBase.dll: C:\Program Files\Reference Assemblyes\Microsoft\Framework\v3.0\WindowsBase.dll.

  6. Opakujte pro UIAutomationTypes.dll: C:\Program Files\Reference Assemblyes\Microsoft\Framework\v3.0\UIAutomationTypes.dll.

  7. Opakujte pro UIAutomationProvider.dll: C:\Program Files\Reference Assemblyes\Microsoft\Framework\v3.0\UIAutomationProvider.dll.

  8. Klepněte na tlačítko Přidat nový odkaz, vyberte System.dll a klepněte na tlačítko OK.

  9. Kliknutím na tlačítko OK ukončete stránky vlastností win32clock pro přidání odkazů.

Nakonec přidejte metodu STAThreadAttribute_tWinMain pro použití s WPF:

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

Tento atribut říká modulu CLR (Common Language Runtime), že při inicializaci modelu COM (Component Object Model) by měl používat jeden model bytu s vlákny (STA), který je nezbytný pro WPF (a model Windows Forms).

Vytvoření stránky windows Presentation Framework

Dále vytvoříte knihovnu DLL, která definuje WPFPage. Často je nejjednodušší vytvořit WPFPage jako samostatnou aplikaci a napsat a ladit část WPF tímto způsobem. Po dokončení lze tento projekt převést na knihovnu DLL tak, že kliknete pravým tlačítkem myši na projekt, kliknete na Vlastnosti, přejdete do aplikace a změníte typ výstupu na knihovnu tříd systému Windows.

Projekt knihovny DLL WPF lze poté kombinovat s projektem Win32 (jedno řešení, které obsahuje dva projekty) – klikněte pravým tlačítkem myši na řešení a vyberte Přidat\Existující projekt.

Chcete-li použít tuto knihovnu DLL WPF z projektu Win32, musíte přidat odkaz:

  1. Klikněte pravým tlačítkem na projekt win32clock a vyberte Odkazy....

  2. Klikněte na Přidat nový odkaz.

  3. Klikněte na kartu Projekty . Vyberte WPFClock, klepněte na tlačítko OK.

  4. Kliknutím na tlačítko OK ukončete stránky vlastností win32clock pro přidání odkazů.

Hwndsource

Dále použijete HwndSource k tomu, aby WPFPage vypadal jako HWND. Tento blok kódu přidáte do souboru 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();
    }
}
}

Toto je dlouhá část kódu, která by mohla použít nějaké vysvětlení. První část je různá klauzule, takže nemusíte plně opravovat všechna volání:

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

Pak definujete funkci, která vytvoří obsah WPF, vloží HwndSource kolem něj a vrátí HWND:

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

Nejprve vytvoříte objekt HwndSource, jehož parametry jsou podobné 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
);

Pak vytvoříte třídu obsahu WPF voláním jeho konstruktoru:

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

Pak připojíte stránku k HwndSource:

source->RootVisual = page;

A v posledním řádku vraťte HWND pro HwndSource:

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

Umístění hwndu

Teď, když máte HWND, který obsahuje WPF hodiny, musíte tento HWND dát do dialogového okna Win32. Kdybyste věděli, kam umístit HWND, stačí předat tuto velikost a umístění do GetHwnd funkce, kterou jste definovali dříve. K definování dialogového okna jste ale použili soubor prostředků, takže si nejste úplně jistí, kde jsou umístěné žádné pevné disky HWN. Editor dialogových oken sady Visual Studio můžete použít k umístění ovládacího prvku Win32 STATIC na místo, kam mají hodiny jít ("Sem vložte hodiny") a použijte ho k umístění hodin WPF.

Pokud zpracováváte WM_INITDIALOG, použijete GetDlgItem k načtení HWND pro zástupný symbol STATIC:

HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);

Potom vypočítáte velikost a umístění tohoto zástupného symbolu STATIC, abyste mohli umístit hodiny WPF na toto místo:

OBDÉLNÍK 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);

Zástupný symbol STATIC pak skryjete:

ShowWindow(placeholder, SW_HIDE);

A vytvořte hwND hodin WPF v daném umístění:

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

Aby byl kurz zajímavý a vytvořit skutečný WPF hodiny, budete muset vytvořit WPF hodiny řízení v tomto okamžiku. Můžete to provést většinou v kódu s několika obslužnými rutinami událostí za kódem. Vzhledem k tomu, že tento kurz se týká spolupráce a ne o návrhu řízení, kompletní kód hodin WPF je zde k dispozici jako blok kódu bez samostatných instrukcí pro jeho sestavení nebo význam jednotlivých částí. Experimentujte s tímto kódem a změňte vzhled a chování nebo funkčnost ovládacího prvku.

Tady je kód:

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

A tady je doprovodný kód:

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

Konečný výsledek vypadá takto:

Final result Date and Time Properties dialog box

Pokud chcete porovnat svůj konečný výsledek s kódem, který vytvořil tento snímek obrazovky, podívejte se na ukázku spolupráce s hodinou Win32 Clock.

Viz také