Creare controlli XAML con C++/WinRT

Questo articolo illustra la creazione di un controllo XAML basato su modelli per WinUI 3 con C++/WinRT. I controlli basati su modelli ereditano da Microsoft.UI.Xaml.Controls.Control e hanno una struttura visiva e un comportamento visivo che possono essere personalizzati tramite i modelli di controllo XAML. Questo articolo descrive lo stesso scenario dell'articolo Controlli (basati su modelli) personalizzati XAML con C++/WinRT, ma è stato adattato in modo da usare WinUI 3.

Prerequisiti

  1. Introduzione a WinUI
  2. Scaricare e installare la versione più recente dell'estensione di Visual Studio C++/WinRT (VSIX)

Creare un'app vuota (BgLabelControlApp)

Per iniziare, crea un nuovo progetto in Microsoft Visual Studio. Nella finestra di dialogo Create a new project selezionare il modello di progetto App vuota (WinUI in UWP), assicurandosi di selezionare la versione del linguaggio C++. Impostare il nome del progetto su "BgLabelControlApp" in modo che i nomi di file siano allineati al codice riportato negli esempi seguenti. Impostare Versione di destinazione su Windows 10, versione 1903 (build 18362) e Versione minima su Windows 10, versione 1803 (build 17134). Questa procedura dettagliata si applicherà anche per le app desktop create con il modello di progetto Blank App, Packaged (WinUI in Desktop) (App vuota, in pacchetto - WinUI in desktop). È necessario solo assicurarsi di eseguire tutte le operazioni nel progetto BgLabelControlApp (Desktop).

Modello di progetto di app vuota

Aggiungere all'app un controllo basato su modelli

Per aggiungere un controllo basato su modelli, fare clic sul menu Progetto nella barra degli strumenti oppure fare clic con il pulsante destro del mouse sul progetto in Esplora soluzioni e scegliere Aggiungi nuovo elemento. In Visual C++->WinUI selezionare il modello Controllo personalizzato (WinUI). Assegnare al nuovo controllo il nome "BgLabelControl" e fare clic su Aggiungi. In questo modo vengono aggiunti al progetto tre nuovi file. BgLabelControl.h è l'intestazione contenente le dichiarazioni del controllo e BgLabelControl.cpp contiene l'implementazione C++/WinRT del controllo. BgLabelControl.idl è il file della definizione di interfaccia che consente di creare un'istanza del controllo come classe di runtime.

Implementare la classe di controllo personalizzato BgLabelControl

Per implementare la classe di runtime, nei passaggi seguenti verrà aggiornato il codice nei file BgLabelControl.idl, BgLabelControl.h e BgLabelControl.cpp della directory di progetto.

Poiché per la classe del controllo basato su modelli verrà creata un'istanza dal markup XAML, si tratterà di una classe di runtime. Quando si compila il progetto finito, il compilatore MIDL (midl.exe) userà il file BgLabelControl.idl per generare il file di metadati di Windows Runtime (con estensione winmd) per il controllo, a cui faranno riferimento gli utenti del componente. Per altre informazioni sulla creazione di classi di runtime, vedere Creare API con C++ /WinRT.

Il controllo basato su modelli creato esporrà un'unica proprietà, una stringa, che verrà usata come etichetta per il controllo. Sostituire il contenuto di BgLabelControl.idl con il codice seguente.

// BgLabelControl.idl
namespace BgLabelControlApp
{
    runtimeclass BgLabelControl : Microsoft.UI.Xaml.Controls.Control
    {
        BgLabelControl();
        static Microsoft.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. Dichiarare in primo luogo 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 si desidera creare una proprietà associata anziché una proprietà di dipendenza, vedere gli esempi di codice in Proprietà associate personalizzate.

Si noti che le classi XAML a cui viene fatto riferimento nel codice precedente sono incluse negli spazi dei nomi Microsoft.UI.Xaml. Questo è ciò che le distingue come controlli WinUI rispetto ai controlli XAML di UWP, che sono definiti negli spazi dei nomi Windows.UI.XAML.

Sostituire il contenuto del file BgLabelControl.h con il codice seguente.

// 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 Microsoft::UI::Xaml::DependencyProperty LabelProperty() { return m_labelProperty; }

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

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

Il codice sopra riportato implementa le proprietà Label e LabelProperty, aggiunge un gestore di evento statico denominato OnLabelChanged per elaborare le modifiche del valore della proprietà di dipendenza e aggiunge un membro privato per archiviare il campo sottostante per LabelProperty. Anche in questo caso, le classi XAML a cui viene fatto riferimento nel file di intestazione sono incluse negli spazi dei nomi Microsoft.UI.Xaml che appartengono al framework WinUI 3, anziché negli spazi dei nomi Windows.UI.Xaml usati dal framework dell'interfaccia utente della piattaforma UWP.

Sostituire quindi il contenuto del file BgLabelControl.cpp con il codice seguente.

// BgLabelControl.cpp
#include "pch.h"
#include "BgLabelControl.h"
#if __has_include("BgLabelControl.g.cpp")
#include "BgLabelControl.g.cpp"
#endif

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

    void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const& d, Microsoft::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.
        }
    }
}

Questa procedura dettagliata non userà il callback OnLabelChanged, ma viene fornita per illustrare come registrare una proprietà di dipendenza con un callback modificato da proprietà. L'implementazione di OnLabelChanged mostra anche come ottenere da un tipo previsto di base un tipo previsto derivato. In questo caso il tipo previsto di base è 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.

La funzione xaml_typename viene fornita dallo spazio dei nomi Windows.UI.Xaml.Interop, che non è incluso per impostazione predefinita nel modello di progetto WinUI 3. Aggiungere una riga al file di intestazione precompilata per il progetto, pch.h, per includere il file di intestazione associato a questo spazio dei nomi.

// pch.h
...
#include <winrt/Windows.UI.Xaml.Interop.h>
...

Definire lo stile predefinito per BgLabelControl

Nel relativo costruttore BgLabelControl imposta una chiave di stile predefinito. Un controllo 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". In Themes aggiungere un nuovo elemento di tipo Visual C++ > WinUI > Dizionario risorse (WinUI) e denominarlo "Generic.xaml". I nomi di file e cartelle devono essere simili a questi affinché il framework XAML trovi lo stile predefinito per un controllo basato su modelli. Eliminare il contenuto predefinito di Generic.xaml e incollare 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 dell'interfaccia utente principale

Apri MainWindow.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!"/>

Aggiungere anche la direttiva di inclusione seguente a MainWindow.h in modo che il tipo MainWindow (una combinazione di markup XAML e codice imperativo) sia a conoscenza del tipo di controllo basato su modelli 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.

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

Risultato del controllo basato su modelli

Implementazione di funzioni sottoponibili a override come MeasureOverride e OnApplyTemplate

I controlli basati su modelli derivano dalla classe di runtime Control, che a sua volta deriva da classi di runtime di base. Sono disponibili inoltre metodi sottoponibili a override di Control, FrameworkElement e UIElement di cui è possibile eseguire l'override nella classe derivata. Ecco un esempio di codice che illustra come fare.

// Control overrides.
void OnPointerPressed(Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& /* e */) const { ... };

// FrameworkElement overrides.
Windows::Foundation::Size MeasureOverride(Windows::Foundation::Size const& /* availableSize */) const { ... };
void OnApplyTemplate() const { ... };

// UIElement overrides.
Microsoft::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer() const { ... };

Le funzioni sottoponibili a override sono diverse nelle proiezioni di linguaggi diversi. In C#, ad esempio, le funzioni sottoponibili a override si presentano tipicamente come funzioni virtuali protette. In C++/WinRT non sono né virtuali né protette, ma è comunque possibile eseguirne l'override e fornire la propria implementazione, come illustrato in precedenza.

Generazione dei file di origine del controllo senza l'uso di un modello

Questa sezione illustra come generare i file di origine necessari per creare il controllo personalizzato senza usare il modello di elemento Controllo personalizzato.

Aggiungere prima un nuovo elemento File Midl (con estensione idl) al progetto. Scegliere Aggiungi nuovo elemento... dal menu Progetto e digitare "MIDL" nella casella di ricerca per trovare l'elemento file con estensione idl. Assegnare al nuovo file il nome BgLabelControl.idl in modo che sia coerente con i passaggi descritti in questo articolo. Eliminare il contenuto predefinito di BgLabelControl.idl e incollarlo nella dichiarazione della classe di runtime illustrata nei passaggi precedenti.

Dopo aver salvato il nuovo file con estensione idl, il passaggio successivo consiste nel generare il file di metadati di Windows Runtime (con estensione winmd) e gli stub per i file di implementazione con estensione cpp e h, che verranno usati per implementare il controllo basato su modelli. Generare questi file compilando la soluzione, che farà sì che il compilatore MIDL (midl.exe) compili il file idl creato. Si noti che la soluzione non verrà compilata correttamente e in Visual Studio verranno visualizzati errori di compilazione nella finestra di output, ma verranno comunque generati i file necessari.

Copiare i file stub BgLabelControl.h e BgLabelControl.cpp da \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ nella cartella del progetto. 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.

Il compilatore inserisce una riga static_assert all'inizio di BgLabelControl.h e BgLabelControl.cpp per impedire la compilazione dei file generati. Quando si implementa il controllo, rimuovere queste righe dai file inseriti nella directory del progetto. Per questa procedura dettagliata è possibile semplicemente sovrascrivere l'intero contenuto dei file con il codice riportato in precedenza.

Vedi anche