Controlli (basati su modelli) personalizzati XAML con C++/WinRT

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.

Una delle funzionalità più potenti della piattaforma UWP (Universal Windows Platform) è la flessibilità che fornisce lo stack di interfaccia utente (UI) per creare controlli personalizzati basati sul tipo Control di XAML. Il framework dell'interfaccia utente XAML offre funzionalità come proprietà di dipendenza personalizzate e proprietà associate, nonché modelli di controllo, che semplificano la creazione di controlli ricchi di funzionalità e personalizzabili. Questo argomento descrive nel dettaglio i passaggi per la creazione di un controllo personalizzato basato su modello con C++/WinRT.

Creare un'app vuota (BgLabelControlApp)

Per iniziare, crea un nuovo progetto in Microsoft Visual Studio. Crea un progetto App vuota (C++/WinRT), impostane il nome su BgLabelControlApp e, per fare in modo che la struttura di cartelle corrisponda a quella della procedura dettagliata, verifica 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.

In una sezione successiva di questo argomento sono riportate le istruzioni per compilare il progetto: evita però di eseguire la compilazione fino a quel momento.

Nota

Per informazioni sulla configurazione di Visual Studio per lo sviluppo in C++/WinRT, compresi l'installazione e l'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, vedere Supporto di Visual Studio per C++/WinRT.

Creeremo una nuova classe per rappresentare un controllo personalizzato basato su modello. Desideriamo creare e usare la classe all'interno della stessa unità di compilazione. Poiché intendiamo poter istanziare la classe dal markup 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. Assegna il nomeBgLabelControl.idl. Elimina il contenuto predefinito di BgLabelControl.idl e incollalo in questa dichiarazione di classe di runtime.

// BgLabelControl.idl
namespace BgLabelControlApp
{
    runtimeclass BgLabelControl : Windows.UI.Xaml.Controls.Control
    {
        BgLabelControl();
        static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

Il listato sopra mostra il modello a cui attenersi quando si dichiara una proprietà di dipendenza. Ogni proprietà di dipendenza ha due componenti. In primo luogo si dichiara una proprietà statica di sola lettura di tipo DependencyProperty. Ha il nome della proprietà di dipendenza più Property. Userai questa proprietà statica nell'implementazione. In secondo luogo si dichiara una proprietà di istanza di lettura/scrittura con il tipo e il nome della proprietà di dipendenza. Se vuoi creare una proprietà associata, invece di una proprietà di dipendenza, vedi gli esempi di codice in Proprietà associate personalizzate.

Nota

Se vuoi una proprietà di dipendenza con un tipo a virgola mobile, rendila double (Double in MIDL 3.0). Dichiarando e implementando una proprietà di dipendenza di tipo float (Single in MIDL) e poi impostando un valore per tale proprietà di dipendenza nel markup XAML, si ottiene l'errore Impossibile creare un "Windows.Foundation.Single" dal testo "<NUMBER>".

Salva il file. Al momento il progetto non verrà compilato completamente, ma è utile eseguire la compilazione ora perché vengono generati automaticamente i file di codice sorgente in cui verrà implementata la classe di runtime BgLabelControl. Eseguire la compilazione adesso. A questo punto, è possibile che si ottengano errori di compilazione di tipo "simbolo esterno non risolto".

Durante il processo di compilazione, viene eseguito lo strumento midl.exe per creare il file di metadati di Windows Runtime (\BgLabelControlApp\Debug\BgLabelControlApp\Unmerged\BgLabelControl.winmd) che descrive la classe di runtime. 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 BgLabelControl dichiarata nel file IDL. Gli stub sono \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.h e BgLabelControl.cpp.

Copia i file di stub BgLabelControl.h e BgLabelControl.cpp da \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ nella cartella del progetto, ovvero \BgLabelControlApp\BgLabelControlApp\. In Esplora soluzioni assicurarsi che l'opzione Mostra tutti i file sia attivata. Fai clic con il pulsante destro del mouse sui file di stub copiati, quindi scegli Includi nel progetto.

Verrà visualizzato static_assert nella parte superiore di BgLabelControl.h e BgLabelControl.cpp, che sarà necessario rimuovere prima che il progetto venga compilato. A questo punto, il progetto verrà compilato.

Implementare la classe di controllo personalizzata BgLabelControl

A questo punto apriamo \BgLabelControlApp\BgLabelControlApp\BgLabelControl.h e BgLabelControl.cpp e implementiamo la classe di runtime. In BgLabelControl.h modifica il costruttore per impostare la chiave di stile predefinito, implementa Label e LabelProperty, aggiungi un gestore di evento statico denominato OnLabelChanged per elaborare le modifiche al valore della proprietà di dipendenza e aggiungi un membro privato per archiviare il campo di supporto per LabelProperty.

Dopo queste aggiunte BgLabelControl.h avrà un aspetto simile al seguente. Puoi copiare e incollare questo listato di codice per sostituire il contenuto di BgLabelControl.h.

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

namespace winrt::BgLabelControlApp::implementation
{
    struct BgLabelControl : BgLabelControlT<BgLabelControl>
    {
        BgLabelControl() { DefaultStyleKey(winrt::box_value(L"BgLabelControlApp.BgLabelControl")); }

        winrt::hstring Label()
        {
            return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
        }

        void Label(winrt::hstring const& value)
        {
            SetValue(m_labelProperty, winrt::box_value(value));
        }

        static Windows::UI::Xaml::DependencyProperty LabelProperty() { return m_labelProperty; }

        static void OnLabelChanged(Windows::UI::Xaml::DependencyObject const&, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const&);

    private:
        static Windows::UI::Xaml::DependencyProperty m_labelProperty;
    };
}
namespace winrt::BgLabelControlApp::factory_implementation
{
    struct BgLabelControl : BgLabelControlT<BgLabelControl, implementation::BgLabelControl>
    {
    };
}

In BgLabelControl.cpp definisci i membri statici come segue. Puoi copiare e incollare questo listato di codice per sostituire il contenuto di BgLabelControl.cpp.

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

namespace winrt::BgLabelControlApp::implementation
{
    Windows::UI::Xaml::DependencyProperty BgLabelControl::m_labelProperty =
        Windows::UI::Xaml::DependencyProperty::Register(
            L"Label",
            winrt::xaml_typename<winrt::hstring>(),
            winrt::xaml_typename<BgLabelControlApp::BgLabelControl>(),
            Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &BgLabelControl::OnLabelChanged } }
    );

    void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& /* e */)
    {
        if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
        {
            // Call members of the projected type via theControl.

            BgLabelControlApp::implementation::BgLabelControl* ptr{ winrt::get_self<BgLabelControlApp::implementation::BgLabelControl>(theControl) };
            // Call members of the implementation type via ptr.
        }
    }
}

In questa procedura dettagliata non useremo OnLabelChanged. Tuttavia è presente e ti consente di vedere come registrare una proprietà di dipendenza con un callback di proprietà modificata. L'implementazione di OnLabelChanged mostra anche come ottenere un tipo derivato proiettato da un tipo di base proiettato; in questo caso il tipo di base proiettato è DependencyObject. Inoltre mostra come ottenere un puntatore al tipo che implementa il tipo proiettato. Naturalmente la seconda operazione sarà possibile solo nel progetto che implementa il tipo proiettato, vale a dire il progetto che implementa la classe di runtime.

Nota

Se non hai installato la versione 10.0.17763.0 o successiva di Windows SDK (Windows 10 versione 1809), devi chiamare winrt::from_abi nel gestore eventi cambiato della proprietà di dipendenza di cui sopra, anziché winrt::get_self.

Progettare lo stile predefinito per BgLabelControl

Nel relativo costruttore BgLabelControl imposta una chiave di stile predefinito. Cos'è uno stile predefinito? Un controllo personalizzato (basato su modelli) deve avere uno stile predefinito, contenente un modello di controllo predefinito, da usare per il proprio rendering nel caso in cui l'utente del controllo non imposti uno stile e/o un modello. In questa sezione si aggiungerà un file di markup al progetto contenente lo stile predefinito.

Assicurati che l'impostazione Mostra tutti i file sia ancora attivata (in Esplora soluzioni). Nel nodo del progetto crea una nuova cartella (non un filtro) e assegnale il nome "Themes". Sotto Themes aggiungi un nuovo elemento di tipo Visual C++>XAML>Visualizzazione XAML e denominalo "Generic.xaml". I nomi di file e cartelle devono essere questi affinché il framework XAML trovi lo stile predefinito per un controllo personalizzato. Elimina il contenuto predefinito di Generic.xaml e incolla il markup riportato di seguito.

<!-- \Themes\Generic.xaml -->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BgLabelControlApp">

    <Style TargetType="local:BgLabelControl" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:BgLabelControl">
                    <Grid Width="100" Height="100" Background="{TemplateBinding Background}">
                        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{TemplateBinding Label}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

In questo caso l'unica proprietà che lo stile predefinito imposta è il modello di controllo. Il modello è costituito da un quadrato, il cui sfondo è associato alla proprietà Background presente in tutte le istanze del tipo Control XAML, e da un elemento di testo, il cui testo è associato alla proprietà di dipendenza BgLabelControl::Label.

Aggiungere un'istanza di BgLabelControl alla pagina principale dell'interfaccia utente

Apri MainPage.xaml, che contiene il markup XAML per la nostra pagina dell'interfaccia utente principale. Immediatamente dopo l'elemento Button, all'interno di StackPanel, aggiungi il markup seguente.

<local:BgLabelControl Background="Red" Label="Hello, World!"/>

Inoltre aggiungi la direttiva di inclusione seguente a MainPage.h in modo che il tipo MainPage, ovvero una combinazione di markup XAML di compilazione e codice imperativo, conosca il tipo di controllo personalizzato BgLabelControl. Se vuoi usare BgLabelControl da un'altra pagina XAML, aggiungi la stessa direttiva di inclusione anche al file di intestazione per quella pagina. In alternativa inserisci una sola direttiva di inclusione nel file di intestazione precompilato.

// MainPage.h
...
#include "BgLabelControl.h"
...

A questo punto compila ed esegui il progetto. Noterai che il modello di controllo predefinito è associato al pennello di sfondo e all'etichetta dell'istanza BgLabelControl nel markup.

Questa procedura dettagliata ha illustrato un esempio semplice di un controllo personalizzato basato su modello in C++/WinRT. Puoi rendere i tuoi controlli personalizzati ricchi e pieni di funzioni quanto desideri. Ad esempio, un controllo personalizzato può assumere la forma complessa di una griglia di dati modificabile, un lettore video o un visualizzatore di geometria 3D.

Implementazione di funzioni sottoponibili a override come MeasureOverride e OnApplyTemplate

Vedere la sezione in Chiamare ed eseguire l'override del tipo di base con C++/WinRT.

API importanti