XAML-vezérlők; kötés egy C++/WinRT tulajdonsághoz

Az XAML-vezérlőhöz hatékonyan kötődő tulajdonságot megfigyelhető tulajdonságnak nevezzük. Ez az ötlet a megfigyelői mintaként ismert szoftvertervezési mintán alapul. Ez a témakör bemutatja, hogyan valósíthat meg megfigyelhető tulajdonságokat a C++/WinRT-ben, és hogyan kötheti hozzájuk az XAML-vezérlőket (a háttérinformációkért lásd : Adatkötés).

Fontos

Azokat az alapvető fogalmakat és kifejezéseket, amelyek támogatják a futtatókörnyezeti osztályok C+++/WinRT használatával történő felhasználásának és szerkesztésének megértését, olvassa el az API-k használata c++/WinRT-lel és Szerzői API-k c++/WinRT használatával című témakört.

Mit jelent a megfigyelhető tulajdonság?

Tegyük fel, hogy egy BookSku nevű futtatókörnyezeti osztálynak van egy Title nevű tulajdonsága. Ha a BookSku az INotifyPropertyChanged::P ropertyChanged eseményt emeli ki, amikor a Cím értéke megváltozik, akkor ez azt jelenti, hogy a Cím egy megfigyelhető tulajdonság. A BookSku viselkedése (az esemény emelése vagy nem emelése) határozza meg, hogy a tulajdonságai közül melyik észlelhető.

Az XAML-szövegelemek vagy vezérlőelemek ezekhez az eseményekhez kötődhetnek, és kezelhetik őket. Egy ilyen elem vagy vezérlőelem a frissített érték(ek) beolvasásával kezeli az eseményt, majd frissíti magát az új érték megjelenítéséhez.

Note

A C++/WinRT Visual Studio bővítmény (VSIX) és a NuGet-csomag telepítésével és használatával kapcsolatos információkért (amelyek együttesen nyújtanak projektsablont és buildtámogatást) lásd Visual Studio C++/WinRT támogatását.

Üres alkalmazás létrehozása (Könyvesbolt)

Először hozzon létre egy új projektet a Microsoft Visual Studióban. Hozzon létre egy Blank App, Packaged (WinUI 3 in Desktop) C++-projektet, és nevezze el Bookstore névre. Győződjön meg arról, hogy a Megoldás és projekt elhelyezése ugyanabban a könyvtárban nincs bejelölve. A Windows SDK legújabb általánosan elérhető (azaz nem előzetes verziójú) verzióját célozza meg.

Egy új osztályt fogunk létrehozni, amely egy megfigyelhető címtulajdonságú könyvet jelöl. Az osztályt ugyanabban a fordítási egységben hozzuk létre és használjuk. De az XAML-ből szeretnénk csatlakozni ehhez az osztályhoz, és ezért futtatókörnyezeti osztály lesz. És a C++/WinRT-t fogjuk használni mind a létrehozására, mind a használatára.

Az új futtatókörnyezeti osztály elkészítésének első lépése egy új Midl-fájl (.idl) elem hozzáadása a projekthez. Nevezze el az új elemet BookSku.idl. Törölje a modul alapértelmezett tartalmát BookSku.idl, és illessze be ezt a futtatókörnyezeti osztály deklarációját.

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

Note

A nézetmodell-osztályoknak – vagyis az alkalmazásban deklarált futtatókörnyezeti osztályoknak – nem kell alaposztályból származnia. Erre példa a Fent deklarált BookSku osztály. Implementál egy felületet, de nem származik alaposztályból.

Minden olyan futtatókörnyezeti osztályt, amelyet az alkalmazásban deklarál, amely nem alaposztályból származik, összeállítható osztálynak nevezzük. És vannak megkötések a összeállítható osztályok körül. Ahhoz, hogy egy alkalmazás megfeleltethesse a Visual Studio és a Microsoft Store által a beküldések ellenőrzéséhez használt Windows-alkalmazástanúsítvány-teszteket (és így ahhoz, hogy az alkalmazás sikeresen betölthető legyen a Microsoft Store-ba), a összeállítható osztálynak végül egy Windows-alaposztályból kell származnia. Ez azt jelenti, hogy az öröklési hierarchia gyökerénél lévő osztálynak egy Windows.* vagy Microsoft.* névtérből származó típusnak kell lennie. Ha valóban egy alaposztályból kell származtatnia egy futásidejű osztályt – például azért, hogy megvalósítson egy BindableBase osztályt, amelyből az összes nézetmodellje származhat –, akkor a Microsoft.UI.Xaml.DependencyObject osztályból származtathatja.

A nézetmodell a nézet absztrakciója, így közvetlenül a nézethez kötődik (az XAML-jelölőnyelvhez). Az adatmodellek az adatok absztrakciói, és csak a nézetmodellekből származnak, és nem kapcsolódnak közvetlenül az XAML-hez. Így az adatmodelleket nem futtatókörnyezeti osztályokként deklarálhatja, hanem A C++ szerkezetek vagy osztályok szerint. Ezeket nem kell deklarálni a MIDL-ben, és szabadon használhatja a kívánt öröklési hierarchiát.

Mentse a fájlt, és hozza létre a projektet. A build még nem lesz (teljesen) sikeres, de el fog végezni helyettünk néhány szükséges dolgot. Pontosabban a buildelési folyamat során az midl.exe eszköz egy Windows-futtatókörnyezet metaadatfájl létrehozásához fut, amely leírja a futtatókörnyezeti osztályt (a fájl a lemezre kerül a következő helyen\Bookstore\Debug\Bookstore\Unmerged\BookSku.winmd: ). Ezután az cppwinrt.exe eszköz futtatásával forráskódfájlokat hozhat létre a futtatókörnyezeti osztály létrehozásához és használatához. Ezek a fájlok olyan csonkokat tartalmaznak, amelyekkel megkezdheti az IDL-ben deklarált BookSku futtatókörnyezeti osztály implementálását. Mindjárt megtaláljuk őket a lemezen, de ezek a stubok: \Bookstore\Bookstore\Generated Files\sources\BookSku.h és BookSku.cpp.

Most kattintson a jobb gombbal a projektcsomópontra a Visual Studio, majd kattintson a Mappa megnyitása a Fájlkezelőben parancsra. Ezzel megnyitja a projektmappát a Fájlkezelőben. Most a \Bookstore\Bookstore\ mappa tartalmát kell látnia. Innen lépjen a \Generated Files\sources\ mappába, és másolja a csonkfájlokat BookSku.h és BookSku.cpp a vágólapra. Lépjen vissza a projektmappára (\Bookstore\Bookstore\), és illessze be az imént másolt két fájlt. Végül a Megoldáskezelő-ben a kijelölt projektcsomópontnál győződjön meg arról, hogy a Show All Files be van kapcsolva. Kattintson az egér jobb gombjával a másolt csonkfájlokra, majd válassza a Projektbe foglalás parancsot.

Implementálja a BookSku-t

Most nyissuk meg a \Bookstore\Bookstore\BookSku.h és a BookSku.cpp elemet, és valósítsuk meg a futásidejű osztályunkat. Először a BookSku.h és a BookSku.cpp tetején megjelenik egy static_assert, amit el kell távolítania.

Ezután végezze el ezeket a módosításokat itt: BookSku.h

  • Az alapértelmezett konstruktorban cserélje le a(z) = default elemet erre: = delete. Ennek az az oka, hogy nem szeretnénk alapértelmezett konstruktort.
  • Adjon hozzá egy privát tagot a címet tároló karakterlánc számára. Vegye figyelembe, hogy van egy konstruktorunk, amely winrt::hstring értéket vesz fel. Ez az érték a címsor.
  • Adjon hozzá egy másik privát tagot ahhoz az eseményhez, amelyet a cím módosításakor emelünk ki.

Miután elvégezte ezeket a módosításokat, a(z) BookSku.h így fog kinézni.

// 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>
    {
    };
}

A(z) BookSku.cpp helyen valósítsa meg a függvényeket így.

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

A Címmutor függvényben ellenőrizzük, hogy az aktuális értéktől eltérő érték van-e beállítva. És ha igen, akkor frissítjük a címet, és az INotifyPropertyChanged::P ropertyChanged eseményt is előállítjuk egy olyan argumentummal, amely megegyezik a módosított tulajdonság nevével. Ez azért van, hogy a felhasználói felület (UI) tudja, melyik tulajdonság értékét szeretné újra lekérdezni.

A projekt most újra létrejön, ha ellenőrizni szeretné.

BookstoreViewModel deklarálása és implementálása

A fő XAML-oldal egy főnézeti modellhez fog kapcsolódni. Ez a nézetmodell több tulajdonsággal is rendelkezik, köztük a BookSku típus egyikével. Ebben a lépésben deklaráljuk és implementáljuk a főnézeti modell futtatókörnyezeti osztályát.

Adjon hozzá egy MIDL-fájl (.idl) nevű új BookstoreViewModel.idl elemet. Lásd még: A futtatókörnyezeti osztályok Midl-fájlokba (.idl) szervezése.

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

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

Mentés és buildelés (a buildelés még nem fog teljesen sikerülni, de ennek az az oka, hogy újra létrehozzuk a csonkfájlokat).

Másolja BookstoreViewModel.h a BookstoreViewModel.cpp mappát a Generated Files\sources projektmappába, és vegye bele őket a projektbe. Nyissa meg ezeket a fájlokat (újra eltávolítva a static_assert elemet), és valósítsa meg a futásidejű osztályt az alább látható módon. Figyelje meg, hogy a(z) BookstoreViewModel.h fájlban belefoglaljuk a(z) BookSku.h elemet, amely deklarálja a(z) BookSku implementációs típusát (ami: winrt::Bookstore::implementation::BookSku). És eltávolítjuk a(z) = default-t az alapértelmezett konstruktorból.

Note

Az alábbi BookstoreViewModel.hBookstoreViewModel.cppfelsorolásban a kód bemutatja a m_bookSku adattag létrehozásának alapértelmezett módját. Ez az a módszer, amely a C++/WinRT első kiadására nyúlik vissza, és érdemes legalább ismerni a mintát. A C++/WinRT 2.0-s és újabb verziójával elérhető egy optimalizált építési forma, amely egységes szerkezetként ismert (lásd a híreket és a változásokat a C++/WinRT 2.0-s verziójában). A témakör későbbi részében bemutatunk egy példát az egységes szerkezetre.

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

A típus m_bookSku az előrejelzett típus (winrt::Bookstore::BookSku), és a winrt::make sablonparaméter a megvalósítás típusa (winrt::Bookstore::implementáció::BookSku). Ennek ellenére a make a leképezett típus egy példányát adja vissza.

A projekt most újra létrejön.

BookstoreViewModel típusú tulajdonság hozzáadása a MainPage-hez

Nyissa meg MainPage.idl, amely deklarálja a fő felhasználói felületi oldalt képviselő futtatókörnyezeti osztályt.

  • Adjon hozzá egy import direktívát a(z) BookstoreViewModel.idl importálásához.
  • Adjon hozzá egy MainViewModel nevű írásvédett tulajdonságot , amely BookstoreViewModel típusú.
  • Távolítsa el a MyProperty tulajdonságot.
// MainPage.idl
import "BookstoreViewModel.idl";

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

Mentse a fájlt. A projekt még nem fog teljesen sikeresen felépíteni, de az építés most hasznos dolog, mert újragenerálja azokat a forráskódfájlokat, amelyekben a MainPage futtatókörnyezeti osztály implementálva van (\Bookstore\Bookstore\Generated Files\sources\MainPage.h és MainPage.cpp). Tehát folytassa, és építsen most. Az ebben a szakaszban várható buildelési hiba a "MainViewModel": nem tagja a "winrt::Bookstore::implementation::MainPage"-nek.

Ha kihagyja a(z) BookstoreViewModel.idl include utasítást (lásd a fenti MainPage.idl felsorolását), akkor a következő hiba jelenik meg: < várt a(z) „MainViewModel” közelében. Egy másik tipp, hogy ügyeljen arra, hogy minden típus ugyanabban a névtérben maradjon – abban a névtérben, amely a kódpéldákban látható.

A várt módon megjelenő hiba elhárításához most ki kell másolnia a MainViewModel tulajdonság elérőmetódus-csonkjait a generált fájlokból (\Bookstore\Bookstore\Generated Files\sources\MainPage.h és MainPage.cpp), majd be kell illesztenie őket a(z) \Bookstore\Bookstore\MainPage.h és MainPage.cpp fájlba. Ennek lépéseit a következő lépésekkel ismertetjük.

A(z) \Bookstore\Bookstore\MainPage.h részben hajtsa végre a következő lépéseket.

  • Tartalmazza BookstoreViewModel.h, amely deklarálja a BookstoreViewModel implementálási típusát (ami winrt::Bookstore::implementáció::BookstoreViewModel).
  • Adjon hozzá egy privát tagot a nézetmodell tárolásához. Vegye figyelembe, hogy a tulajdonság kiegészítő függvénye (és a tag m_mainViewModel) a BookstoreViewModel (azaz Bookstore::BookstoreViewModel) előrejelzett típusában van implementálva.
  • A megvalósítási típus ugyanabban a projektben (fordítási egységben) található, mint az alkalmazás, ezért m_mainViewModel az std::nullptr_t konstruktor túlterhelésen keresztül építjük fel.
  • Távolítsa el a MyProperty tulajdonságot.

Note

Az alábbi MainPage.hMainPage.cpplistaelemek párjában a kód bemutatja a m_mainViewModel adattag létrehozásának alapértelmezett módját. Az alábbi szakaszban egy egységes szerkezetű verziót mutatunk be.

// 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 };
    };
}
...

Az \Bookstore\Bookstore\MainPage.cppalábbi felsorolásban látható módon végezze el az alábbi módosításokat.

  • Hívja meg a winrt::make parancsot (a BookstoreViewModel implementálási típusával együtt), hogy a tervezett BookstoreViewModel típus új példányát rendelje hozzá m_mainViewModel. Ahogy fent láttuk, a BookstoreViewModel konstruktor létrehoz egy új BookSku objektumot privát adattagként, és a címét kezdetben a következőre L"Atticus"állítja.
  • A gomb eseménykezelőjében (ClickHandler) frissítse a könyv címét a közzétett címére.
  • Implementálja a MainViewModel tulajdonság tartozékát.
  • Távolítsa el a MyProperty tulajdonságot.
// 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;
    }
}

Egységes szerkezet

Egységes szerkezet használata winrt::make helyett, MainPage.h deklarálva és inicializálva m_mainViewModel egyetlen lépésben, az alább látható módon.

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

Ezután a MainPage konstruktorban MainPage.cppnincs szükség a kódra m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.

Az egységes építésről és a kód példáiról további információt az egységes építés és a közvetlen megvalósítási hozzáférés engedélyezése című témakörben talál.

A gomb kötése a Cím tulajdonsághoz

Nyissa meg a MainPage.xamlfájlt, amely tartalmazza a fő felhasználói felület oldalának XAML jelölését. Ahogy az alábbi felsorolásban látható, távolítsa el a nevet a gombból, és módosítsa a Content tulajdonság értékét egy literálból egy kötési kifejezésre. Jegyezze fel a Mode=OneWay kötési kifejezés tulajdonságát (egyirányú a nézetmodelltől a felhasználói felületig). A tulajdonság nélkül a felhasználói felület nem válaszol a tulajdonság által módosított eseményekre.

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

Most építse fel és futtassa a projektet. Kattintson a gombra a Click eseménykezelő végrehajtásához. Ez a kezelő meghívja a könyv címmutátorfüggvényét; ez a mutáció eseményt hoz létre, amely tudatja a felhasználói felülettel, hogy a Cím tulajdonság megváltozott; és a gomb újra lekérdezi a tulajdonság értékét a saját tartalomértékének frissítéséhez.

A {Binding} korrektúrabővítmény használata C++/WinRT használatával

A C++/WinRT jelenleg kiadott verziójához a {Binding} jelölőbővítmény használatához implementálnia kell az ICustomPropertyProvider és az ICustomProperty interfészeket.

Elem–elem kötés

Egy XAML-elem tulajdonságát egy másik XAML-elem tulajdonságához kötheti. Íme egy példa arra, hogyan néz ez ki jelölőnyelvben.

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

A myTextBox nevű XAML-entitást csak olvasható tulajdonságként kell deklarálnia a MIDL-fájljában (.idl).

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

Itt van ennek a szükségnek az oka. Az XAML-fordító által érvényesítendő összes típus (beleértve a(z) {x:Bind} alkalmazásban használt típusokat is) Windows metaadatokból (WinMD) olvasható. Mindössze annyit kell tennie, hogy hozzáadja a csak olvasható tulajdonságot a MIDL-fájlhoz. Ne implementálja, mert az automatikusan létrehozott XAML-kód biztosítja a megvalósítást.

Objektumok használata XAML-jelölőnyelvből

Az XAML {x:Bind} jelölőbővítmény használatával felhasznált összes entitást nyilvánosan közzé kell tenni az IDL-ben. Továbbá, ha a XAML-jelölés egy másik, szintén a jelölésben szereplő elemre való hivatkozást tartalmaz, akkor az adott jelölés getterének jelen kell lennie az IDL-ben.

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

A ChangeColorButton elem a UseCustomColorCheckBox elemre hivatkozik kötéssel. Ezért az oldal IDL-jének deklarálnia kell egy UseCustomColorCheckBox nevű, csak olvasható tulajdonságot ahhoz, hogy az elérhető legyen az adatkötés számára.

A UseCustomColorCheckBox kattintási eseménykezelő delegáltja klasszikus XAML delegálási szintaxist használ, így nincs szükség bejegyzésre az IDL-ben; csak nyilvánosnak kell lennie a megvalósítási osztályban. Másrészt a ChangeColorButton egy kattintásos eseménykezelővel is rendelkezik {x:Bind} , amelynek az IDL-be is be kell lépnie.

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

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

Nem kell implementációt megadnia a UseCustomColorCheckBox tulajdonsághoz. Az XAML kódgenerátor ezt teszi Az Ön számára.

Kötés logikai típushoz

Ezt diagnosztikai módban teheti meg:

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

C++/CX-ben a true vagy a false jelenik meg; C++/WinRT-ben viszont a Windows.Foundation.IReference`1<Boolean> jelenik meg.

Ehelyett használja a(z) x:Bind elemet, ha logikai értékhez köt.

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

A Windows implementációs kódtárak (WIL) használata

A Windows implementációs kódtárak (WIL) segítséget nyújtanak a köthető tulajdonságok írásának megkönnyítéséhez. Lásd : Értesítési tulajdonságok a WIL dokumentációjában.

Fontos API-k