XAML-kontroller; binda till en C++/WinRT-egenskap

En egenskap som effektivt kan bindas till en XAML-kontroll kallas för en observerbar egenskap. Den här idén baseras på det programvarudesignmönster som kallas observatörsmönster. Det här avsnittet visar hur du implementerar observerbara egenskaper i C++/WinRT och hur du binder XAML-kontroller till dem (för bakgrundsinformation, se Databindning).

Viktigt!

Viktiga begrepp och termer som stöder din förståelse av hur du använder och skapar körningsklasser med C++/WinRT finns i Använda API:er med C++/WinRT och Redigerings-API:er med C++/WinRT.

Vad betyder observerbar för en egenskap?

Anta att en runtimeklass med namnet BookSku har en egenskap med namnet Title. Om BookSku genererar händelsen INotifyPropertyChanged::P ropertyChanged när värdet för Rubrik ändras, innebär det att Rubrik är en observerbar egenskap. Det är beteendet hos BookSku (höja eller inte höja händelsen) som avgör vilka, om några, av dess egenskaper som kan observeras.

Ett XAML-textelement, eller en kontroll, kan binda till och hantera dessa händelser. Ett sådant element eller en kontroll hanterar händelsen genom att hämta de uppdaterade värdena och sedan uppdatera sig själv för att visa det nya värdet.

Note

Information om hur du installerar och använder VSIX (C++/WinRT Visual Studio Extension) och NuGet-paketet (som tillsammans tillhandahåller projektmall och byggstöd) finns i Visual Studio support för C++/WinRT.

Skapa en tom app (bokhandel)

Börja med att skapa ett nytt projekt i Microsoft Visual Studio. Skapa ett Blank App, Packaged (WinUI 3 in Desktop)-projekt för C++, och ge det namnet Bookstore. Kontrollera att Placera lösning och projekt i samma katalog är avmarkerat. Rikta in dig på den senaste allmänt tillgängliga (dvs. inte förhandsversionen) av Windows SDK.

Vi ska skapa en ny klass som representerar en bok som har en observerbar titelegenskap. Vi redigerar och använder klassen i samma kompileringsenhet. Men vi vill kunna binda till den här klassen i XAML, så den måste vara en runtime-klass. Och vi kommer att använda C++/WinRT för att både skapa och använda den.

Det första steget i att skapa en ny körningsklass är att lägga till ett nytt objekt av typen Midl File (.idl) i projektet. Ge det nya objektet BookSku.idlnamnet . Ta bort standardinnehållet i BookSku.idloch klistra in den här körningsklassdeklarationen.

// BookSku.idl
namespace Bookstore
{
    runtimeclass BookSku : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
    {
        BookSku(String title);
        String Title;
    }
}

Note

Dina vymodellklasser – ja, faktiskt alla runtime-klasser som du deklarerar i din applikation – behöver inte härledas från någon basklass. Den BookSku-klass som deklareras ovan är ett exempel på det. Det implementerar ett gränssnitt, men det härleds inte från någon basklass.

Alla körningsklasser som du deklarerar i programmet och som faktiskt härleds från en basklass kallas för komponerbara klasser. Och det finns begränsningar kring sammansättningsbara klasser. För att ett program ska klara Windows App Certification Kit-testerna som används av Visual Studio och Av Microsoft Store för att validera inskickade filer (och därför för att programmet ska kunna matas in i Microsoft Store), måste en sammansättningsbar klass slutligen härledas från en Windows-basklass. Det innebär att klassen vid själva roten i arvshierarkin måste vara en typ som har sitt ursprung i en namnrymd av typen Windows.* eller Microsoft.*. Om du behöver härleda en körningsklass från en basklass, till exempel för att implementera en BindableBase-klass för alla dina vymodeller att härleda från, kan du härleda från Microsoft. UI. Xaml.DependencyObject.

En vymodell är en abstraktion av en vy, så den är direkt bunden till vyn (XAML-markering). En datamodell är en abstraktion av data och används endast från dina vymodeller och är inte direkt bunden till XAML. Så du kan deklarera dina datamodeller inte som körningsklasser, utan som C++ structs eller klasser. De behöver inte deklareras i MIDL och du kan använda vilken arvshierarki du vill.

Spara filen och skapa projektet. Bygget kommer inte (helt) att lyckas ännu, men det kommer att göra några nödvändiga saker för oss. Mer specifikt körs verktyget under byggprocessen midl.exe för att skapa en Windows Runtime metadatafil som beskriver körningsklassen (filen placeras på disken på \Bookstore\Debug\Bookstore\Unmerged\BookSku.winmd). cppwinrt.exe Sedan körs verktyget för att generera källkodsfiler som stöder redigering och användning av körningsklassen. Dessa filer inkluderar stubs för att komma igång med att implementera BookSku-körningsklassen som du deklarerade i din IDL. Vi kommer att hitta dem på disk om en stund, men de stubbarna är \Bookstore\Bookstore\Generated Files\sources\BookSku.h och BookSku.cpp.

Högerklicka nu på projektnoden i Visual Studio och klicka på Öppna mapp i Utforskaren. Då öppnas projektmappen i Utforskaren. Nu bör du titta på innehållet i \Bookstore\Bookstore\ mappen. Därifrån går du till mappen \Generated Files\sources\ och kopierar stubfilerna BookSku.h och BookSku.cpp till urklippet. Gå tillbaka till projektmappen (\Bookstore\Bookstore\) och klistra in de två filer som du precis kopierade. I Prieskumník riešení med projektnoden markerad kontrollerar du slutligen att Visa alla filer är aktiverat. Högerklicka på de stub-filer som du kopierade och klicka på Include In Project.

Implementera BookSku

Nu öppnar vi \Bookstore\Bookstore\BookSku.h och BookSku.cpp och implementerar vår runtime-klass. Först ser du en static_assert högst upp i BookSku.h och BookSku.cpp, som du behöver ta bort.

Gör sedan dessa ändringar i BookSku.h.

  • I standardkonstruktorn ändrar du = default till = delete. Det beror på att vi inte vill ha någon standardkonstruktor.
  • Lägg till en privat medlem för att lagra rubriksträngen. Observera att vi har en konstruktor som tar ett winrt::hstring-värde . Det värdet är titelsträngen.
  • Lägg till ännu en privat medlem för händelsen som vi utlöser när titeln ändras.

När du har gjort de här ändringarna ser din BookSku.h ut så här.

// BookSku.h
#pragma once
#include "BookSku.g.h"

namespace winrt::Bookstore::implementation
{
    struct BookSku : BookSkuT<BookSku>
    {
        BookSku() = delete;
        BookSku(winrt::hstring const& title);

        winrt::hstring Title();
        void Title(winrt::hstring const& value);
        winrt::event_token PropertyChanged(Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& value);
        void PropertyChanged(winrt::event_token const& token);
    
    private:
        winrt::hstring m_title;
        winrt::event<Microsoft::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
    };
}
namespace winrt::Bookstore::factory_implementation
{
    struct BookSku : BookSkuT<BookSku, implementation::BookSku>
    {
    };
}

I BookSku.cppimplementerar du funktionerna så här.

// BookSku.cpp
#include "pch.h"
#include "BookSku.h"
#include "BookSku.g.cpp"

namespace winrt::Bookstore::implementation
{
    BookSku::BookSku(winrt::hstring const& title) : m_title{ title }
    {
    }

    winrt::hstring BookSku::Title()
    {
        return m_title;
    }

    void BookSku::Title(winrt::hstring const& value)
    {
        if (m_title != value)
        {
            m_title = value;
            m_propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"Title" });
        }
    }

    winrt::event_token BookSku::PropertyChanged(Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
    {
        return m_propertyChanged.add(handler);
    }

    void BookSku::PropertyChanged(winrt::event_token const& token)
    {
        m_propertyChanged.remove(token);
    }
}

I funktionen Title mutator kontrollerar vi om ett värde anges som skiljer sig från det aktuella värdet. Och i så fall uppdaterar vi rubriken och genererar även händelsen INotifyPropertyChanged::P ropertyChanged med ett argument som är lika med namnet på den egenskap som har ändrats. Detta gör att användargränssnittet (UI) vet vilken egenskaps värde som ska frågas om.

Projektet kommer att byggas igen nu, om du vill kontrollera det.

Deklarera och implementera BookstoreViewModel

Vår huvudsakliga XAML-sida kommer att binda till en huvudvymodell. Och den vymodellen kommer att ha flera egenskaper, inklusive en av typen BookSku. I det här steget ska vi deklarera och implementera vår runtimeklass för huvudvymodellen.

Lägg till ett nytt Midl-filobjekt (.idl) med namnet BookstoreViewModel.idl. Men se även Att faktorisera ut runtime-klasser i Midl-filer (.idl).

// BookstoreViewModel.idl
import "BookSku.idl";

namespace Bookstore
{
    runtimeclass BookstoreViewModel
    {
        BookstoreViewModel();
        BookSku BookSku{ get; };
    }
}

Spara och skapa (bygget lyckas inte helt ännu, men anledningen till att vi skapar är att generera stub-filer igen).

Kopiera BookstoreViewModel.h och BookstoreViewModel.cpp från Generated Files\sources mappen till projektmappen och inkludera dem i projektet. Öppna filerna (ta bort static_assert igen) och implementera runtime-klassen enligt nedan. Observera hur, i BookstoreViewModel.h, vi inkluderar BookSku.h, som deklarerar implementeringstypen för BookSku (som är winrt::Bookstore::implementation::BookSku). Och vi tar bort = default från standardkonstruktorn.

Note

I listorna nedan för BookstoreViewModel.h och BookstoreViewModel.cppvisar koden standardsättet för att konstruera m_bookSku datamedlem. Det är den metod som går tillbaka till den första versionen av C++/WinRT, och det är en bra idé att åtminstone känna till mönstret. Med C++/WinRT version 2.0 och senare finns det en optimerad form av konstruktion tillgänglig för dig som kallas enhetlig konstruktion (se Nyheter och ändringar i C++/WinRT 2.0). Senare i det här avsnittet visar vi ett exempel på enhetlig konstruktion.

// BookstoreViewModel.h
#pragma once
#include "BookstoreViewModel.g.h"
#include "BookSku.h"

namespace winrt::Bookstore::implementation
{
    struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
    {
        BookstoreViewModel();

        Bookstore::BookSku BookSku();

    private:
        Bookstore::BookSku m_bookSku{ nullptr };
    };
}
namespace winrt::Bookstore::factory_implementation
{
    struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel, implementation::BookstoreViewModel>
    {
    };
}
// BookstoreViewModel.cpp
#include "pch.h"
#include "BookstoreViewModel.h"
#include "BookstoreViewModel.g.cpp"

namespace winrt::Bookstore::implementation
{
    BookstoreViewModel::BookstoreViewModel()
    {
        m_bookSku = winrt::make<Bookstore::implementation::BookSku>(L"Atticus");
    }

    Bookstore::BookSku BookstoreViewModel::BookSku()
    {
        return m_bookSku;
    }
}

Note

Typen av m_bookSku är den beräknade typen (winrt::Bookstore::BookSku) och mallparametern som du använder med winrt::make är implementeringstypen (winrt::Bookstore::implementation::BookSku). Trots det returnerar make en instans av den projicerade typen.

Projektet kommer att byggas igen nu.

Lägg till en egenskap av typen BookstoreViewModel i MainPage

Öppna MainPage.idl, som deklarerar runtimeklassen som representerar vår huvudsakliga användargränssnittssida.

  • Lägg till ett import direktiv för att importera BookstoreViewModel.idl.
  • Lägg till en skrivskyddad egenskap med namnet MainViewModel, av typen BookstoreViewModel.
  • Ta bort egenskapen MyProperty .
// MainPage.idl
import "BookstoreViewModel.idl";

namespace Bookstore
{
    runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

Spara filen. Projektet kommer ännu inte att kunna byggas helt, men att bygga nu är ändå bra eftersom det genererar om källkodsfilerna där körningsklassen MainPage implementeras (\Bookstore\Bookstore\Generated Files\sources\MainPage.h och MainPage.cpp). Så kom igen och bygg nu. Det byggfel som du kan förvänta dig i det här skedet är "MainViewModel": är inte medlem i "winrt::Bookstore::implementation::MainPage".

Om du utelämnar att inkludera BookstoreViewModel.idl (se listningen av MainPage.idl ovan), kommer du att se felet förväntade sig < nära "MainViewModel". Ett annat tips är att se till att du lämnar alla typer i samma namnområde – det namnområde som visas i kodlistorna.

För att lösa det fel som vi förväntar oss att se måste du nu kopiera accessor stubs för egenskapen MainViewModel från de genererade filerna (\Bookstore\Bookstore\Generated Files\sources\MainPage.h och MainPage.cpp) och till \Bookstore\Bookstore\MainPage.h och MainPage.cpp. De steg som ska utföras beskrivs härnäst.

I \Bookstore\Bookstore\MainPage.hutför du de här stegen.

  • Inkludera BookstoreViewModel.h, som deklarerar implementeringstypen för BookstoreViewModel (som är winrt::Bookstore::implementation::BookstoreViewModel).
  • Lägg till en privat medlem för att lagra vymodellen. Observera att åtkomstfunktionen för egenskapen (och medlemmen m_mainViewModel) implementeras i termer av den projicerade typen för BookstoreViewModel (det vill säga Bookstore::BookstoreViewModel).
  • Implementeringstypen finns i samma projekt (kompileringsenhet) som programmet, så vi skapar m_mainViewModel via konstruktorns överlagring som tar std::nullptr_t.
  • Ta bort egenskapen MyProperty .

Note

I de två kodlistorna nedan för MainPage.h och MainPage.cpp visar koden standardmetoden för att konstruera datamedlemmen m_mainViewModel. I avsnittet nedan visar vi en version som använder enhetlig konstruktion i stället.

// MainPage.h
...
#include "BookstoreViewModel.h"
...
namespace winrt::Bookstore::implementation
{
    struct MainPage : MainPageT<MainPage>
    {
        MainPage();

        Bookstore::BookstoreViewModel MainViewModel();

        void ClickHandler(Windows::Foundation::IInspectable const&, Microsoft::UI::Xaml::RoutedEventArgs const&);

    private:
        Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
    };
}
...

I \Bookstore\Bookstore\MainPage.cpp, som visas i listan nedan, gör du följande ändringar.

  • Anropa winrt::make (med implementeringstypen BookstoreViewModel ) för att tilldela en ny instans av den planerade BookstoreViewModel-typen till m_mainViewModel. Som vi såg ovan skapar BookstoreViewModel-konstruktorn ett nytt BookSku-objekt som en privat datamedlem och anger dess rubrik till L"Atticus".
  • Uppdatera bokens titel till dess publicerade titel i knappens händelsehanterare (ClickHandler).
  • Implementera accessorn för egenskapen MainViewModel .
  • Ta bort egenskapen MyProperty .
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"

using namespace winrt;
using namespace Microsoft::UI::Xaml;

namespace winrt::Bookstore::implementation
{
    MainPage::MainPage()
    {
        m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
        InitializeComponent();
    }

    void MainPage::ClickHandler(Windows::Foundation::IInspectable const& /* sender */, Microsoft::UI::Xaml::RoutedEventArgs const& /* args */)
    {
        MainViewModel().BookSku().Title(L"To Kill a Mockingbird");
    }

    Bookstore::BookstoreViewModel MainPage::MainViewModel()
    {
        return m_mainViewModel;
    }
}

Enhetlig konstruktion

Om du vill använda enhetlig konstruktion i stället för winrt::make i MainPage.h deklarera och initiera m_mainViewModel i bara ett steg, enligt nedan.

// MainPage.h
...
#include "BookstoreViewModel.h"
...
struct MainPage : MainPageT<MainPage>
{
    ...
private:
    Bookstore::BookstoreViewModel m_mainViewModel;
};
...

Och sedan, i konstruktorn för MainPage i MainPage.cpp, behövs inte koden m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.

Mer information om enhetlig konstruktion och kodexempel finns i Anmäl dig till enhetlig konstruktion och direkt implementeringsåtkomst.

Binda knappen till egenskapen Rubrik

Öppna MainPage.xaml, som innehåller XAML-markering för vår huvudsida för användargränssnittet. Som du ser i listan nedan tar du bort namnet från knappen och ändrar egenskapsvärdet Innehåll från en literal till ett bindningsuttryck. Observera egenskapen Mode=OneWay i bindningsuttrycket (enkelriktad från visningsmodellen till användargränssnittet). Utan den egenskapen svarar användargränssnittet inte på ändrade händelser för egenskapen.

<Button Click="ClickHandler" Content="{x:Bind MainViewModel.BookSku.Title, Mode=OneWay}"/>

Skapa och kör nu projektet. Klicka på knappen för att köra Click-händelsehanteraren. Den händelsehanteraren anropar bokens titelns mutatorfunktion; mutatorn utlöser en händelse för att låta användargränssnittet veta att egenskapen Title har ändrats; och knappen hämtar sedan egenskapens värde på nytt för att uppdatera sitt eget Content-värde.

Använda markeringstillägget {Binding} med C++/WinRT

För den aktuella versionen av C++/WinRT måste du implementera gränssnitten ICustomPropertyProvider och ICustomProperty för att kunna använda markeringstillägget {Binding}.

Bindning mellan element

Du kan binda egenskapen för ett XAML-element till egenskapen för ett annat XAML-element. Här är ett exempel på hur det ser ut i HTML-kod.

<TextBox x:Name="myTextBox" />
<TextBlock Text="{x:Bind myTextBox.Text, Mode=OneWay}" />

Du måste deklarera den namngivna XAML-entiteten myTextBox som en skrivskyddad egenskap i Midl-filen (.idl).

// MainPage.idl
runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
{
    MainPage();
    Microsoft.UI.Xaml.Controls.TextBox myTextBox{ get; };
}

Här är anledningen till denna nödvändighet. Alla typer som XAML-kompilatorn behöver verifiera (inklusive de som används i {x:Bind}) läses från Windows Metadata (WinMD). Allt du behöver göra är att lägga till den skrivskyddade egenskapen i Midl-filen. Implementera den inte eftersom den automatiskt genererade XAML-koden bakom tillhandahåller implementeringen åt dig.

Använda objekt från XAML-markering

Alla entiteter som används med XAML {x:Bind}-markeringstillägget måste exponeras offentligt i IDL. Dessutom, om XAML-kod innehåller en referens till ett annat element som också finns i XAML-koden, måste hämtningsmetoden för det elementet finnas i IDL.

<Page x:Name="MyPage">
    <StackPanel>
        <CheckBox x:Name="UseCustomColorCheckBox" Content="Use custom color"
             Click="UseCustomColorCheckBox_Click" />
        <Button x:Name="ChangeColorButton" Content="Change color"
            Click="{x:Bind ChangeColorButton_OnClick}"
            IsEnabled="{x:Bind UseCustomColorCheckBox.IsChecked.Value, Mode=OneWay}"/>
    </StackPanel>
</Page>

Elementet ChangeColorButton refererar till elementet UseCustomColorCheckBox via bindning. IDL för den här sidan måste därför deklarera en skrivskyddad egenskap med namnet UseCustomColorCheckBox för att den ska vara tillgänglig för bindning.

Händelsehanterardelegaten för klickhändelsen för UseCustomColorCheckBox använder klassisk delegatsyntax i XAML, så den behöver ingen post i IDL; den behöver bara vara offentlig i din implementeringsklass. Å andra sidan har ChangeColorButton också en {x:Bind} klickhändelsehanterare, som också måste gå in i IDL.

runtimeclass MyPage : Microsoft.UI.Xaml.Controls.Page
{
    MyPage();

    // These members are consumed by binding.
    void ChangeColorButton_OnClick();
    Microsoft.UI.Xaml.Controls.CheckBox UseCustomColorCheckBox{ get; };
}

Du behöver inte ange någon implementering för egenskapen UseCustomColorCheckBox . XAML-kodgeneratorn gör det åt dig.

Bindning till booleskt värde

Du kan göra detta i diagnostikläge:

<TextBlock Text="{Binding CanPair}"/>

Det visar true eller false i C++/CX, men det visas Windows.Foundation.IReference`1<Boolean> i C++/WinRT.

Använd x:Bind i stället vid bindning till ett booleskt värde.

<TextBlock Text="{x:Bind CanPair}"/>

Använda Windows implementeringsbibliotek (WIL)

I Windows Implementeringsbibliotek (WIL) kan du enkelt skriva bindbara egenskaper. Se Egenskaper med meddelanden i WIL-dokumentationen.

Viktiga API:er