Controlli XAML, binding a una proprietà C++/WinRT

Una proprietà di cui è possibile eseguire in modo efficace il binding a un controllo XAML è nota come proprietà osservabile. Questo concetto è basato sul modello di progettazione del software noto come modello osservatore. Questo argomento illustra come implementare proprietà osservabili in C++/WinRT e come eseguire il binding a controlli XAML. Per informazioni generali, vedi Data binding.

Importante

Per i concetti essenziali e i termini che possono aiutarti a comprendere come usare e creare classi di runtime con C++/WinRT, vedi Usare API con C++/WinRT e Creare API con C++/WinRT.

Cosa vuol dire osservabile in riferimento a una proprietà?

Supponiamo che una classe di runtime denominata BookSku abbia una proprietà denominata Title. Se BookSku genera l'evento INotifyPropertyChanged::PropertyChanged ogni volta che il valore di Title cambia, significa che Title è una proprietà osservabile. È il comportamento di BookSku (generazione o meno dell'evento) che determina quali proprietà sono osservabili.

Un elemento di testo XAML o un controllo può eseguire l'associazione e gestire questi eventi. Un elemento o un controllo di questo tipo gestisce l'evento recuperando i valori aggiornati e poi si aggiorna per mostrare il nuovo valore.

Nota

Per informazioni sull'installazione e sull'uso dell'Estensione C++/WinRT per Visual Studio (VSIX) e del pacchetto NuGet, che insieme forniscono il modello di progetto e il supporto della compilazione, vedi Supporto di Visual Studio per C++/WinRT.

Creare un'app vuota (Bookstore)

Per iniziare, crea un nuovo progetto in Microsoft Visual Studio. Crea un progetto di app vuota (C++/WinRT) con il nome Bookstore. Assicurarsi che l'opzione Inserisci soluzione e progetto nella stessa directory sia deselezionata. Specificare come destinazione la versione più recente disponibile a livello generale, ovvero non l'anteprima, di Windows SDK.

Creeremo una nuova classe per rappresentare un libro che ha una proprietà titolo osservabile. Desideriamo creare e usare la classe all'interno della stessa unità di compilazione. Poiché tuttavia intendiamo poter eseguire il binding a questa classe da XAML, si tratterà di una classe di runtime. Useremo C++/WinRT per crearla e usarla.

Il primo passaggio per la creazione di una nuova classe di runtime consiste nell'aggiungere un nuovo elemento File Midl (.idl) al progetto. Denominare il nuovo elemento BookSku.idl. Elimina il contenuto predefinito di BookSku.idl e incollalo in questa dichiarazione di classe di runtime.

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

Nota

Le classi di modello di visualizzazione, in realtà qualsiasi classe di runtime dichiarata nell'applicazione, non devono derivare da una classe di base. La classe BookSku dichiarata in precedenza ne è un esempio. Implementa un'interfaccia, ma non deriva da alcuna classe di base.

Qualsiasi classe di runtime dichiarata all'interno dell'applicazione che deriva da una classe di base è definita classe componibile. Le classi componibili sono soggette a limitazioni. Affinché un'applicazione superi i test del Kit di certificazione app Windows utilizzato da Visual Studio e da Microsoft Store per convalidare gli invii, e possa essere inserita correttamente nel sistema di Microsoft Store, una classe componibile in ultima analisi deve derivare da una classe di base di Windows. Vale a dire che la classe radice della gerarchia di ereditarietà deve essere un tipo che ha origine in uno spazio dei nomi Windows.*. Se è necessario derivare una classe di runtime da una classe base, ad esempio per implementare una classe BindableBase per tutti i modelli di visualizzazione da cui effettuare la derivazione, è possibile derivare da Windows.UI.Xaml.DependencyObject.

Un modello di visualizzazione è un'astrazione di una visualizzazione e pertanto è associato direttamente a essa (il markup XAML). Un modello di dati è un'astrazione dei dati, viene usato solo dai modelli di visualizzazione e non è associato direttamente a XAML. Puoi quindi dichiarare i modelli di dati non come classi di runtime, ma come struct o classi di C++. Non devono essere dichiarati in MIDL e si è liberi di usare qualsiasi gerarchia di ereditarietà desiderata.

Salvare il file e compilare il progetto. La compilazione non riuscirà ancora completamente, ma eseguirà alcune operazioni necessarie. In particolare, durante il processo di compilazione viene eseguito lo midl.exe strumento per creare un file di metadati di Windows Runtime che descrive la classe di runtime (il file viene inserito su disco in \Bookstore\Debug\Bookstore\Unmerged\BookSku.winmd). Viene quindi eseguito lo strumento cppwinrt.exe per generare i file di codice sorgente utili per la creazione e l'uso della classe di runtime. Questi file includono gli stub necessari per iniziare l'implementazione della classe di runtime BookSku dichiarata nel file IDL. Li troviamo su disco in un attimo, ma questi stub sono \Bookstore\Bookstore\Generated Files\sources\BookSku.h e BookSku.cpp.

Fare quindi clic con il pulsante destro del mouse sul nodo del progetto in Visual Studio e scegliere Apri cartella in Esplora file. Verrà aperta la cartella del progetto in Esplora file. Verrà ora esaminato il contenuto della cartella \Bookstore\Bookstore\. Da qui passare alla cartella \Generated Files\sources\ e copiare i file stub BookSku.h e BookSku.cpp negli Appunti. Tornare alla cartella del progetto (\Bookstore\Bookstore\) e incollare i due file appena copiati. Infine, in Esplora soluzioni con il nodo del progetto selezionato, assicurarsi che Mostra tutti i file sia attivato. Fai clic con il pulsante destro del mouse sui file di stub copiati, quindi scegli Includi nel progetto.

Implementare BookSku

A questo punto apri \Bookstore\Bookstore\BookSku.h e BookSku.cpp e implementa la classe di runtime. Verrà innanzitutto visualizzato static_assert nella parte superiore di BookSku.h e BookSku.cpp, che sarà necessario rimuovere.

Successivamente, apportare queste modifiche a BookSku.h.

  • Nel costruttore predefinito modificare = default in = delete. Questo perché non è necessario un costruttore predefinito.
  • Aggiungi un membro privato per archiviare la stringa del titolo. Si noti che è presente un costruttore che accetta un valore winrt::hstring. Questo valore è la stringa del titolo.
  • Aggiungi un altro membro privato per l'evento che verrà generato quando il titolo cambia.

Dopo aver apportato queste modifiche, BookSku.h avrà un aspetto simile al seguente.

// 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(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& value);
        void PropertyChanged(winrt::event_token const& token);
    
    private:
        winrt::hstring m_title;
        winrt::event<Windows::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
    };
}
namespace winrt::Bookstore::factory_implementation
{
    struct BookSku : BookSkuT<BookSku, implementation::BookSku>
    {
    };
}

In BookSku.cpp implementa le funzioni come mostrato di seguito.

// 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, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Title" });
        }
    }

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

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

Nella funzione mutatore Title controlliamo se viene impostato un valore diverso da quello corrente. In caso affermativo, aggiorniamo quindi il titolo e generiamo l'evento INotifyPropertyChanged::PropertyChanged con un argomento uguale al nome della proprietà che è stata modificata. In questo modo, l'interfaccia utente sarà in grado di riconoscere il valore della proprietà su cui eseguire nuovamente la query.

A questo punto, il progetto verrà ricompilato e sarà possibile esaminarlo.

Dichiarare e implementare BookstoreViewModel

Verrà eseguito il binding della pagina XAML principale a un modello di visualizzazione principale, il quale disporrà di varie proprietà, tra cui una di tipo BookSku. In questo passaggio si dichiarerà e implementerà la classe di runtime del modello di visualizzazione principale.

Aggiungi un nuovo elemento File Midl (.idl) denominato BookstoreViewModel.idl. Vedi anche Factoring delle classi di runtime nei file Midl (.idl).

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

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

Salvare e compilare (la compilazione non riuscirà ancora completamente, ma verranno generati file stub).

Copia BookstoreViewModel.h e BookstoreViewModel.cpp dalla cartella Generated Files\sources nella cartella di progetto e includili nel progetto. Aprire questi file, rimuovendo nuovamente static_assert, e implementare la classe di runtime come illustrato di seguito. Nota che, in BookstoreViewModel.h viene incluso BookSku.h che dichiara il tipo di implementazione per BookSku, ovvero winrt::Bookstore::implementation::BookSku. E che si rimuove = default dal costruttore predefinito.

Nota

Nei listati seguenti per BookstoreViewModel.h e BookstoreViewModel.cpp, il codice illustra il metodo predefinito per costruire il membro dati m_bookSku. Questo è il metodo che risale alla prima versione di C++/WinRT ed è consigliabile avere familiarità almeno con questo modello. Con C++/WinRT versione 2.0 e successive, è disponibile un modulo di costruzione ottimizzato, noto come costruzione uniforme. Vedere Funzionalità nuove e aggiornate di C++/WinRT 2.0. Più avanti in questo argomento viene illustrato un esempio di costruzione 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;
    }
}

Nota

Il tipo di m_bookSku è il tipo proiettato, ovvero winrt::Bookstore::BookSku, e il parametro del modello che si usa con winrt::make è il tipo di implementazione, ovvero winrt::Bookstore::implementation::BookSku. Anche in questo caso make restituisce un'istanza del tipo proiettato.

A questo punto, il progetto verrà nuovamente compilato.

Aggiungere una proprietà di tipo BookstoreViewModel a MainPage

Apri MainPage.idl, che dichiara la classe di runtime che rappresenta la pagina dell'interfaccia utente principale.

  • Aggiungere una direttiva import per importare BookstoreViewModel.idl.
  • Aggiungere una proprietà di sola lettura denominata MainViewModel di tipo BookstoreViewModel.
  • Rimuovere la proprietà MyProperty.
// MainPage.idl
import "BookstoreViewModel.idl";

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

Salva il file. Il progetto non verrà ancora compilato completamente, ma è utile eseguire la compilazione ora perché vengono rigenerati automaticamente i file di codice sorgente in cui viene implementata la classe di runtime MainPage, ovvero \Bookstore\Bookstore\Generated Files\sources\MainPage.h e MainPage.cpp. Perciò esegui la compilazione adesso. L'errore di compilazione che puoi aspettarti di ottenere ora è "MainViewModel": non è un membro di "winrt::Bookstore::implementation::MainPage".

Se ometti l'inclusione di BookstoreViewModel.idl (vedi il listato di MainPage.idl sopra), verrà visualizzato l'errore previsto < vicino a "MainViewModel". Un altro suggerimento consiste nell'assicurarsi di lasciare tutti i tipi nello stesso spazio dei nomi: lo spazio dei nomi visualizzato nei listati di codice.

Per risolvere l'errore previsto è ora necessario copiare gli stub di funzioni di accesso per la proprietà MainViewModel dai file generati (\Bookstore\Bookstore\Generated Files\sources\MainPage.h e MainPage.cpp) in \Bookstore\Bookstore\MainPage.h e MainPage.cpp. I passaggi per eseguire questa operazione sono descritto di seguito.

In \Bookstore\Bookstore\MainPage.h eseguire questi passaggi.

  • Includere BookstoreViewModel.h che dichiara il tipo di implementazione per BookstoreViewModel, ovvero winrt::Bookstore::implementation::BookstoreViewModel.
  • Aggiungi un membro privato per archiviare il modello di visualizzazione. Si noti che la funzione di accesso della proprietà, così come il membro m_mainViewModel, viene implementata in termini di tipo proiettato per BookstoreViewModel, ovvero Bookstore::BookstoreViewModel.
  • Il tipo di implementazione è nello stesso progetto (unità di compilazione) dell'applicazione, verrà quindi creato m_mainViewModel tramite l'overload del costruttore che accetta std::nullptr_t.
  • Rimuovere la proprietà MyProperty.

Nota

Nella coppia di listati seguenti per MainPage.h e MainPage.cpp, il codice illustra il metodo predefinito per costruire il membro dati m_mainViewModel. Nella sezione seguente verrà illustrata una versione che usa invece la costruzione uniforme.

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

        Bookstore::BookstoreViewModel MainViewModel();

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

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

Come illustrato nell'elenco seguente, apportare a \Bookstore\Bookstore\MainPage.cpp queste modifiche.

  • Chiamare winrt::make (con il tipo di implementazione BookstoreViewModel) per assegnare una nuova istanza del tipo BookstoreViewModel proiettato a m_mainViewModel. Come illustrato in precedenza, il costruttore BookstoreViewModel crea un nuovo oggetto BookSku come membro dati privato, impostando inizialmente il titolo su L"Atticus".
  • Nel gestore eventi del pulsante (ClickHandler) aggiornare il titolo del libro al titolo pubblicato.
  • Implementare la funzione di accesso per la proprietà MainViewModel.
  • Rimuovere la proprietà MyProperty.
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"

using namespace winrt;
using namespace Windows::UI::Xaml;

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

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

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

Costruzione uniforme

Per usare la costruzione uniforme anziché winrt::make, in MainPage.h dichiarare e inizializzare m_mainViewModel in un solo passaggio, come illustrato di seguito.

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

Nel costruttore MainPage in MainPage.cpp non è quindi necessario il codice m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.

Per altre informazioni sulla costruzione uniforme ed esempi di codice, vedere Acconsentire esplicitamente alla costruzione uniforme e all'accesso diretto all'implementazione.

Associare il pulsante alla proprietà Title

Apri MainPage.xaml, che contiene il markup XAML per la nostra pagina dell'interfaccia utente principale. Come mostrato nel listato seguente, rimuovi il nome dal pulsante e modifica il relativo valore di proprietà Content da espressione letterale a espressione di binding. Nota la proprietà Mode=OneWay nell'espressione di binding, unidirezionale dal modello di visualizzazione all'interfaccia utente. Senza tale proprietà, l'interfaccia utente non risponde agli eventi della proprietà modificata.

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

A questo punto compila ed esegui il progetto. Fai clic sul pulsante per eseguire il gestore eventi Click. Il gestore chiama la funzione mutatore del titolo del libro, la quale genera un evento per indicare all'interfaccia utente che la proprietà Title è stata modificata. Il pulsante esegue di nuovo una query sul valore della proprietà per aggiornare il proprio valore Content.

Uso dell'estensione di markup {Binding} con C++/WinRT

Con la versione attualmente rilasciata di C++/WinRT, per poter usare l'estensione di markup {Binding} è necessario implementare le interfacce ICustomPropertyProvider e ICustomProperty.

Binding da elemento a elemento

Puoi eseguire il binding della proprietà di un elemento XAML con la proprietà di un altro elemento XAML. Ecco un esempio di questa operazione nel markup.

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

Devi dichiarare l'entità XAML denominata myTextBox come proprietà di sola lettura nel file Midl (con estensione idl).

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

Ecco il motivo per cui è necessario includere questa dichiarazione. Tutti i tipi che il compilatore XAML deve convalidare, inclusi quelli usati in {x:Bind}, vengono letti dai metadati di Windows (WinMD). Tu devi solo aggiungere la proprietà di sola lettura al file Midl. Non devi eseguire alcuna implementazione, perché il code-behind XAML generato fornisce l'implementazione automaticamente.

Utilizzo di oggetti dal markup XAML

Tutte le entità utilizzate con l'estensione di markup {x:Bind} XAML devono essere esposte pubblicamente in IDL. Inoltre, se il markup XAML contiene un riferimento a un altro elemento che si trova anch'esso nel markup, il getter per tale markup deve essere presente in 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'elemento ChangeColorButton fa riferimento all'elemento UseCustomColorCheckBox tramite binding. Pertanto, l'IDL per questa pagina deve dichiarare una proprietà di sola lettura denominata UseCustomColorCheckBox per essere accessibile al binding.

Il delegato del gestore dell'evento click per UseCustomColorCheckBox usa la sintassi del delegato XAML classico e quindi non richiede una voce nell'IDL. È sufficiente che sia pubblico nella classe di implementazione. D'altra parte, anche ChangeColorButton dispone di un gestore dell'evento click {x:Bind}, che deve essere anch'esso inserito nell'IDL.

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

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

Non è necessario fornire un'implementazione per la proprietà UseCustomColorCheckBox. Questa operazione viene eseguita automaticamente dal generatore di codice XAML.

Binding a un valore booleano

Questa operazione può essere eseguita in modalità di diagnostica:

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

Visualizzare true o false in C++/CX; ma visualizzare Windows.Foundation.IReference`1<Boolean> in C++/WinRT.

Usare invece x:Bind quando si esegue il binding a un valore booleano.

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

Uso delle librerie di implementazione di Windows (WIL)

Le librerie di implementazione di Windows (WIL) forniscono helper per semplificare la scrittura di proprietà associabili. Vedere Notifying Properties (Notifica delle proprietà) nella documentazione di WIL.

API importanti