Contrôles XAML ; lier à une propriété C++/WinRT

Une propriété pouvant être liée efficacement à un contrôle XAML est appelée propriété observable . Cette idée est basée sur le modèle de conception de logiciel appelé modèle observateur. Cette rubrique montre comment implémenter des propriétés observables dans C++/WinRT et comment lier des contrôles XAML à eux (pour obtenir des informations d’arrière-plan, consultez liaison de données).

Important

Pour connaître les concepts et termes essentiels qui prennent en charge votre compréhension de l’utilisation et de la création de classes runtime avec C++/ WinRT, consultez Consommer des API avec C++/WinRT et créer des API avec C++/WinRT.

Que signifie observable pour une propriété ?

Supposons qu’une classe runtime nommée BookSku possède une propriété nommée Title. Si BookSku déclenche l’événement INotifyPropertyChanged ::P ropertyChanged chaque fois que la valeur du titre change, cela signifie que Title est une propriété observable. C’est le comportement de BookSku — le fait de déclencher ou non l’événement — qui détermine quelles propriétés, le cas échéant, sont observables.

Un élément de texte XAML, ou un contrôle, peut être lié et gérer ces événements. Un tel élément ou contrôle gère l’événement en récupérant la ou les valeurs mises à jour, puis en se mettant à jour pour afficher la nouvelle valeur.

Note

Pour plus d’informations sur l’installation et l’utilisation de l’extension Visual Studio C++/WinRT (VSIX) et du package NuGet (qui fournissent ensemble un modèle de projet et une prise en charge de build), consultez Visual Studio prise en charge de C++/WinRT.

Créer une application vide (Bookstore)

Commencez par créer un projet dans Microsoft Visual Studio. Créez une application vide, empaquetée (WinUI 3 dans Desktop) pour le projet C++ et nommez-la Bookstore. Assurez-vous que placez la solution et le projet dans le même répertoire sont désactivés. Ciblez la dernière version en disponibilité générale (c’est-à-dire non en préversion) du KIT DE développement logiciel (SDK) Windows.

Nous allons définir une nouvelle classe pour représenter un livre doté d’une propriété observable pour le titre. Nous créons et consommons la classe dans la même unité de compilation. Mais nous voulons pouvoir établir une liaison à cette classe à partir de XAML et, pour cette raison, il s’agit d’une classe runtime. Et nous allons utiliser C++/WinRT pour le créer et l’utiliser.

La première étape de la création d’une nouvelle classe runtime consiste à ajouter un nouvel élément Midl File (.idl) au projet. Nommez le nouvel élément BookSku.idl. Supprimez le contenu par défaut de BookSku.idl, puis collez-le dans cette déclaration de classe runtime.

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

Note

Les classes de modèle d’affichage ( en fait, toute classe runtime que vous déclarez dans votre application) n’ont pas besoin de dériver d’une classe de base. La classe BookSku déclarée ci-dessus est un exemple de cela. Il implémente une interface, mais elle ne dérive d’aucune classe de base.

Toute classe runtime que vous déclarez dans l’application qui dérive d’une classe de base est appelée classe composable . Et il existe des contraintes autour des classes composables. Pour qu’une application passe les tests du Kit de certification des applications Windows utilisées par Visual Studio et par le Microsoft Store pour valider les soumissions (et par conséquent, pour que l’application soit ingérée avec succès dans le Microsoft Store), une classe composable doit finalement dériver d’une classe de base Windows. Cela signifie que la classe à la racine même de la hiérarchie d’héritage doit être un type provenant d’un espace de noms Windows.* ou Microsoft.*. Si vous devez dériver une classe runtime d’une classe de base, par exemple pour implémenter une classe BindableBase pour tous vos modèles de vue à dériver, vous pouvez dériver de Microsoft. UI. Xaml.DependencyObject.

Un modèle d’affichage est une abstraction d’une vue et est donc lié directement à la vue (balisage XAML). Un modèle de données est une abstraction des données, et il est consommé uniquement à partir de vos modèles d’affichage et n’est pas lié directement au code XAML. Vous pouvez donc déclarer vos modèles de données non pas en tant que classes runtime, mais en tant que structs ou classes C++. Ils n’ont pas besoin d’être déclarés dans MIDL, et vous êtes libre d’utiliser la hiérarchie d’héritage que vous aimez.

Enregistrez le fichier et générez le projet. La build ne réussira pas encore (entièrement), mais elle fera certaines choses nécessaires pour nous. Plus précisément, pendant le processus de génération, l’outil midl.exe est exécuté pour créer un fichier de métadonnées Windows Runtime qui décrit la classe runtime (le fichier est placé sur le disque à \Bookstore\Debug\Bookstore\Unmerged\BookSku.winmd). Ensuite, l’outil cppwinrt.exe est exécuté pour générer des fichiers de code source pour vous aider à créer et à consommer votre classe runtime. Ces fichiers incluent des stubs pour commencer à implémenter la classe runtime BookSku que vous avez déclarée dans votre IDL. Nous les trouverons sur le disque dans un instant, mais ces stubs sont \Bookstore\Bookstore\Generated Files\sources\BookSku.h et BookSku.cpp.

Cliquez maintenant avec le bouton droit sur le nœud du projet dans Visual Studio, puis cliquez sur Ouvrir le dossier dans l’Explorateur de fichiers. Cela ouvre le dossier du projet dans l’Explorateur de fichiers. Vous devez maintenant examiner le contenu du \Bookstore\Bookstore\ dossier. À partir de là, accédez au dossier \Generated Files\sources\, puis copiez les fichiers stub BookSku.h et BookSku.cpp dans le presse-papiers. Retournez au dossier du projet (\Bookstore\Bookstore\) et collez les deux fichiers que vous venez de copier. Enfin, dans l’Explorateur de solutions, avec le nœud de projet sélectionné, assurez-vous que l’option Afficher tous les fichiers est activée. Cliquez avec le bouton droit sur les fichiers stub que vous avez copiés, puis cliquez sur Include Dans Project.

Implémenter BookSku

Nous allons maintenant ouvrir \Bookstore\Bookstore\BookSku.h et BookSku.cpp implémenter notre classe runtime. Tout d’abord, vous verrez une static_assert en haut de BookSku.h et de BookSku.cpp, que vous devrez supprimer.

Ensuite, dans BookSku.h, apportez ces modifications.

  • Sur le constructeur par défaut, passez = default à = delete. C’est parce que nous ne voulons pas d’un constructeur par défaut.
  • Ajoutez un membre privé pour stocker la chaîne de titre. Notez que nous avons un constructeur qui prend une valeur winrt ::hstring . Cette valeur correspond à la chaîne de titre.
  • Ajoutez un autre membre privé pour l’événement que nous déclencherons lorsque le titre change.

Après avoir apporté ces modifications, votre BookSku.h ressemblera à ceci.

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

Dans BookSku.cpp, implémentez les fonctions telles que celles-ci.

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

Dans la fonction mutator Title , nous vérifions si une valeur est définie différente de la valeur actuelle. Et, si c’est le cas, nous mettons alors à jour le titre et déclenchons également l’événement INotifyPropertyChanged::PropertyChanged avec comme argument le nom de la propriété qui a changé. Cela permet à l’interface utilisateur (UI) de savoir la valeur de quelle propriété interroger de nouveau.

Le projet sera à nouveau généré maintenant, si vous souhaitez vérifier cela.

Déclarer et implémenter BookstoreViewModel

Notre page XAML principale va être liée à un modèle d’affichage principal. Et ce modèle d’affichage va avoir plusieurs propriétés, y compris l’un des types BookSku. Dans cette étape, nous allons déclarer et implémenter notre classe runtime de modèle d’affichage principale.

Ajoutez un nouvel élément Midl File (.idl) nommé BookstoreViewModel.idl. Mais consultez également la factorisation des classes d’exécution dans des fichiers MIDL (.idl).

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

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

Enregistrez et générez (la build ne réussira pas encore, mais la raison pour laquelle nous créons est de générer à nouveau des fichiers stub).

Copiez BookstoreViewModel.h et BookstoreViewModel.cpp à partir du Generated Files\sources dossier dans le dossier du projet et incluez-les dans le projet. Ouvrez ces fichiers (en supprimant à nouveau static_assert) et implémentez la classe d’exécution comme indiqué ci-dessous. Notez comment, dans BookstoreViewModel.h, nous incluons BookSku.h, qui déclare le type d’implémentation pour BookSku (qui est winrt ::Bookstore ::implementation ::BookSku). Et nous supprimons = default du constructeur par défaut.

Note

Dans les listes ci-dessous pour BookstoreViewModel.h et BookstoreViewModel.cpp, le code illustre la façon par défaut d’initialiser le membre de données m_bookSku. C’est la méthode qui remonte à la première version de C++/WinRT, et il est judicieux d’être au moins familiarisé avec le modèle. Avec C++/WinRT version 2.0 et ultérieure, il existe une forme optimisée de construction disponible pour vous, appelée construction uniforme (voir Actualités et modifications, en C++/WinRT 2.0). Plus loin dans cette rubrique, nous allons montrer un exemple de construction uniforme.

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

Le type de m_bookSku est le type projeté (winrt::Bookstore::BookSku), et le paramètre de modèle que vous utilisez avec winrt::make est le type d’implémentation (winrt::Bookstore::implementation::BookSku). Même dans ce cas, make renvoie une instance du type projeté.

Le projet va maintenant être recompilé.

Ajouter une propriété de type BookstoreViewModel à MainPage

Ouvrez MainPage.idl, qui déclare la classe runtime qui représente notre page d’interface utilisateur principale.

  • Ajoutez la directive import pour importer BookstoreViewModel.idl.
  • Ajoutez une propriété en lecture seule nommée MainViewModel, de type BookstoreViewModel.
  • Supprimez la propriété MyProperty .
// MainPage.idl
import "BookstoreViewModel.idl";

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

Enregistrez le fichier. Le projet ne réussira pas encore à générer, mais la génération est maintenant une chose utile car elle régénère les fichiers de code source dans lesquels la classe runtime MainPage est implémentée (\Bookstore\Bookstore\Generated Files\sources\MainPage.h et MainPage.cpp). Alors allez-y et construisez maintenant. L’erreur de génération que vous pouvez voir à ce stade est « MainViewModel » : n’est pas membre de « winrt ::Bookstore ::implementation ::MainPage ».

Si vous omettez l’inclusion de BookstoreViewModel.idl (voir la liste de MainPage.idl ci-dessus), vous verrez l’erreur expecting < near "MainViewModel". Un autre conseil est de veiller à laisser tous les types dans le même espace de noms, celui qui est affiché dans les extraits de code.

Pour résoudre l’erreur que nous nous attendons désormais à voir apparaître, vous devez maintenant copier les squelettes d’accesseur de la propriété MainViewModel depuis les fichiers générés (\Bookstore\Bookstore\Generated Files\sources\MainPage.h et MainPage.cpp) vers \Bookstore\Bookstore\MainPage.h et MainPage.cpp. Les étapes à suivre sont décrites ci-dessous.

Dans \Bookstore\Bookstore\MainPage.h, effectuez ces étapes.

  • Include BookstoreViewModel.h, qui déclare le type d’implémentation pour BookstoreViewModel (qui est winrt ::Bookstore ::implementation ::BookstoreViewModel).
  • Ajoutez un membre privé pour stocker le modèle d’affichage. Notez que la fonction d’accesseur de propriété (et le membre m_mainViewModel) est implémentée à partir du type projeté de BookstoreViewModel (qui est Bookstore::BookstoreViewModel).
  • Le type d’implémentation se trouve dans le même projet (unité de compilation) que l’application. Nous construisons donc m_mainViewModel via la surcharge du constructeur qui prend std ::nullptr_t.
  • Supprimez la propriété MyProperty .

Note

Dans la paire d’extraits de code ci-dessous pour MainPage.h et MainPage.cpp, le code illustre la méthode par défaut d’initialiser le membre de données m_mainViewModel. Dans la section qui suit, nous présenterons une version qui utilise plutôt une construction uniforme.

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

Dans \Bookstore\Bookstore\MainPage.cpp, comme indiqué dans la liste ci-dessous, apportez les modifications suivantes.

  • Appelez winrt ::make (avec le type d’implémentation BookstoreViewModel ) pour affecter une nouvelle instance du type BookstoreViewModel projeté à m_mainViewModel. Comme nous l’avons vu ci-dessus, le constructeur BookstoreViewModel crée un objet BookSku en tant que membre de données privé, définissant son titre initialement sur L"Atticus".
  • Dans le gestionnaire d’événements du bouton (ClickHandler), mettez à jour le titre du livre en son titre publié.
  • Implémentez l’accesseur pour la propriété MainViewModel .
  • Supprimez la propriété 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;
    }
}

Construction uniforme

Pour utiliser la construction uniforme au lieu de winrt ::make, dans MainPage.h déclarer et initialiser m_mainViewModel en une seule étape, comme indiqué ci-dessous.

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

Et puis, dans le constructeur MainPage dans MainPage.cpp, il n’y a pas besoin du code m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.

Pour plus d’informations sur la construction uniforme et les exemples de code, consultez Opt in to uniform construction, and direct implementation access.

Lier le bouton à la propriété Title

Ouvrez MainPage.xaml, qui contient le balisage XAML de notre page d’interface utilisateur principale. Comme indiqué dans la liste ci-dessous, supprimez le nom du bouton et remplacez sa valeur de propriété Content d’un littéral par une expression de liaison. Notez la Mode=OneWay propriété sur l’expression de liaison (unidirectionnelle du modèle d’affichage à l’interface utilisateur). Sans cette propriété, l’interface utilisateur ne répond pas aux événements modifiés de propriété.

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

Maintenant, compilez et exécutez le projet. Cliquez sur le bouton pour exécuter le gestionnaire d’événements Click . Ce gestionnaire appelle la fonction mutateur de titre du livre ; ce mutateur déclenche un événement pour indiquer à l’interface utilisateur que la propriété Title a changé ; et le bouton interroge à nouveau la valeur de cette propriété pour mettre à jour sa propre valeur de contenu .

Utilisation de l’extension de balisage {Binding} avec C++/WinRT

Pour la version actuellement publiée de C++/WinRT, pour pouvoir utiliser l’extension de balisage {Binding}, vous devez implémenter les interfaces ICustomPropertyProvider et ICustomProperty.

Liaison d’élément à élément

Vous pouvez lier la propriété d’un élément XAML à la propriété d’un autre élément XAML. Voici un exemple de ce à quoi cela ressemble dans le balisage.

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

Vous devez déclarer l’entité myTextBox XAML nommée en tant que propriété en lecture seule dans votre fichier Midl (.idl).

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

Voici la raison de cette nécessité. Tous les types que le compilateur XAML doit valider (y compris ceux utilisés dans {x :Bind}) sont lus à partir de Windows Métadonnées (WinMD). Il vous suffit d’ajouter la propriété en lecture seule à votre fichier Midl. Ne l’implémentez pas, car le code-behind XAML généré automatiquement fournit l’implémentation pour vous.

Consommation d’objets à partir du balisage XAML

Toutes les entités consommées à l’aide de l’extension de balisage XAML {x :Bind} doivent être exposées publiquement dans IDL. En outre, si le balisage XAML contient une référence à un autre élément qui est également dans le balisage, le getter pour ce balisage doit être présent dans 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>

L’élément ChangeColorButton fait référence à l’élément UseCustomColorCheckBox via la liaison. Par conséquent, l’IDL de cette page doit déclarer une propriété en lecture seule nommée UseCustomColorCheckBox pour qu’elle soit accessible à la liaison.

Le délégué du gestionnaire de l’événement de clic pour UseCustomColorCheckBox utilise la syntaxe classique des délégués XAML ; il n’a donc pas besoin d’une déclaration dans l’IDL et doit simplement être public dans votre classe d’implémentation. En revanche, ChangeColorButton possède également un gestionnaire d’événement de clic {x:Bind}, qui doit lui aussi figurer dans l’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; };
}

Vous n’avez pas besoin de fournir une implémentation pour la propriété UseCustomColorCheckBox . Le générateur de code XAML le fait pour vous.

Liaison vers un booléen

Vous pouvez le faire en mode de diagnostic :

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

Cela s’affiche true ou false en C++/CX, mais il s’affiche Windows.Foundation.IReference`1<Boolean> en C++/WinRT.

Utilisez plutôt x:Bind lors de la liaison à une valeur booléenne.

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

Utilisation des bibliothèques d’implémentation Windows (WIL)

Les bibliothèques d’implémentation Windows (WIL) fournissent des helpers pour faciliter l’écriture de propriétés pouvant être liées. Voir Notifying Properties dans la documentation WIL.

API importantes