Controlli (basati su modelli) personalizzati XAML con C++/WinRTXAML custom (templated) controls with 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.For essential concepts and terms that support your understanding of how to consume and author runtime classes with C++/WinRT, see Consume APIs with C++/WinRT and Author APIs with 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.One of the most powerful features of the Universal Windows Platform (UWP) is the flexibility that the user-interface (UI) stack provides to create custom controls based on the XAML Control type. 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.The XAML UI framework provides features such as custom dependency properties and attached properties, and control templates, which make it easy to create feature-rich and customizable controls. Questo argomento descrive nel dettaglio i passaggi per la creazione di un controllo personalizzato basato su modello con C++/WinRT.This topic walks you through the steps of creating a custom (templated) control with C++/WinRT.

Creare un'app vuota (BgLabelControlApp)Create a Blank App (BgLabelControlApp)

Per iniziare, crea un nuovo progetto in Microsoft Visual Studio.Begin by creating a new project 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.Create a Blank App (C++/WinRT) project, set its name to BgLabelControlApp, and (so that your folder structure will match the walkthrough) make sure that Place solution and project in the same directory is unchecked. Specificare come destinazione la versione più recente disponibile a livello generale, ovvero non l'anteprima, di Windows SDK.Target the latest generally-available (that is, not preview) version of the 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.In a later section of this topic, you'll be directed to build your project (but don't build until then).

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, vedi Supporto di Visual Studio per C++/WinRT.For info about setting up Visual Studio for C++/WinRT development—including installing and using the C++/WinRT Visual Studio Extension (VSIX) and the NuGet package (which together provide project template and build support)—see Visual Studio support for C++/WinRT.

Creeremo una nuova classe per rappresentare un controllo personalizzato basato su modello.We're going to author a new class to represent a custom (templated) control. Desideriamo creare e usare la classe all'interno della stessa unità di compilazione.We're authoring and consuming the class within the same compilation unit. Poiché intendiamo poter istanziare la classe dal markup XAML, si tratterà di una classe di runtime.But we want to be able to instantiate this class from XAML markup, and for that reason it's going to be a runtime class. Useremo C++/WinRT per crearla e usarla.And we're going to use C++/WinRT to both author and consume it.

Il primo passaggio per la creazione di una nuova classe di runtime consiste nell'aggiungere un nuovo elemento File Midl (.idl) al progetto.The first step in authoring a new runtime class is to add a new Midl File (.idl) item to the project. Denomina tale elemento BgLabelControl.idl.Name it BgLabelControl.idl. Elimina il contenuto predefinito di BgLabelControl.idl e incollalo in questa dichiarazione di classe di runtime.Delete the default contents of BgLabelControl.idl, and paste in this runtime class declaration.

// 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.The listing above shows the pattern that you follow when declaring a dependency property (DP). Ogni proprietà di dipendenza ha due componenti.There are two pieces to each DP. In primo luogo si dichiara una proprietà statica di sola lettura di tipo DependencyProperty.First, you declare a read-only static property of type DependencyProperty. Ha il nome della proprietà di dipendenza più Property.It has the name of your DP plus Property. Userai questa proprietà statica nell'implementazione.You'll use this static property in your implementation. In secondo luogo si dichiara una proprietà di istanza di lettura/scrittura con il tipo e il nome della proprietà di dipendenza.Second, you declare a read-write instance property with the type and name of your DP. Se vuoi creare una proprietà associata, invece di una proprietà di dipendenza, vedi gli esempi di codice in Proprietà associate personalizzate.If you wish to author an attached property (rather than a DP), then see the code examples in Custom attached properties.

Nota

Se vuoi una proprietà di dipendenza con un tipo a virgola mobile, rendila double (Double in MIDL 3.0).If you want a DP with a floating-point type, then make it 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 "" .Declaring and implementing a DP of type float (Single in MIDL), and then setting a value for that DP in XAML markup, results in the error Failed to create a 'Windows.Foundation.Single' from the text ''.

Salvare il file.Save the 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.The project won't build to completion at the moment, but building now is a useful thing to do because it generates the source code files in which you'll implement the BgLabelControl runtime class. Eseguire la compilazione adesso. A questo punto, è possibile che si ottengano errori di compilazione di tipo "simbolo esterno non risolto".So go ahead and build now (the build errors you can expect to see at this stage have to do with an "unresolved external symbol").

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.During the build process, the midl.exe tool is run to create a Windows Runtime metadata file (\BgLabelControlApp\Debug\BgLabelControlApp\Unmerged\BgLabelControl.winmd) describing the runtime class. 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.Then, the cppwinrt.exe tool is run to generate source code files to support you in authoring and consuming your runtime class. Questi file includono gli stub necessari per iniziare l'implementazione della classe di runtime BgLabelControl dichiarata nel file IDL.These files include stubs to get you started implementing the BgLabelControl runtime class that you declared in your IDL. Gli stub sono \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.h e BgLabelControl.cpp.Those stubs are \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.h and 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\.Copy the stub files BgLabelControl.h and BgLabelControl.cpp from \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ into the project folder, which is \BgLabelControlApp\BgLabelControlApp\. In Esplora soluzioni assicurati che Mostra tutti i file sia attivato.In Solution Explorer, make sure Show All Files is toggled on. Fai clic con il pulsante destro del mouse sui file di stub copiati, quindi scegli Includi nel progetto.Right-click the stub files that you copied, and click Include In Project.

Verrà visualizzato static_assert nella parte superiore di BgLabelControl.h e BgLabelControl.cpp, che sarà necessario rimuovere prima che il progetto venga compilato.You'll see a static_assert at the top of BgLabelControl.h and BgLabelControl.cpp, which you'll need to remove. A questo punto, il progetto verrà compilato.Now the project will build.

Implementare la classe di controllo personalizzata BgLabelControlImplement the BgLabelControl custom control class

A questo punto apriamo \BgLabelControlApp\BgLabelControlApp\BgLabelControl.h e BgLabelControl.cpp e implementiamo la classe di runtime.Now, let's open \BgLabelControlApp\BgLabelControlApp\BgLabelControl.h and BgLabelControl.cpp and implement our runtime class. 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.In BgLabelControl.h, change the constructor to set the default style key, implement Label and LabelProperty, add a static event handler named OnLabelChanged to process changes to the value of the dependency property, and add a private member to store the backing field for LabelProperty.

Dopo queste aggiunte BgLabelControl.h avrà un aspetto simile al seguente.After adding those, your BgLabelControl.h looks like this. Puoi copiare e incollare questo listato di codice per sostituire il contenuto di BgLabelControl.h.You can copy and paste this code listing to replace the contents of 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.In BgLabelControl.cpp, define the static members like this. Puoi copiare e incollare questo listato di codice per sostituire il contenuto di BgLabelControl.cpp.You can copy and paste this code listing to replace the contents of 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.In this walkthrough, we won't be using OnLabelChanged. Tuttavia è presente e ti consente di vedere come registrare una proprietà di dipendenza con un callback di proprietà modificata.But it's there so that you can see how to register a dependency property with a property-changed callback. 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.The implementation of OnLabelChanged also shows how to obtain a derived projected type from a base projected type (the base projected type is DependencyObject, in this case). Inoltre mostra come ottenere un puntatore al tipo che implementa il tipo proiettato.And it shows how to then obtain a pointer to the type that implements the projected type. 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.That second operation will naturally only be possible in the project that implements the projected type (that is, the project that implements the runtime class).

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.If you haven't installed the Windows SDK version 10.0.17763.0 (Windows 10, version 1809), or later, then you need to call winrt::from_abi in the dependency property changed event handler above, instead of winrt::get_self.

Progettare lo stile predefinito per BgLabelControlDesign the default style for BgLabelControl

Nel relativo costruttore BgLabelControl imposta una chiave di stile predefinito.In its constructor, BgLabelControl sets a default style key for itself. Cos'è uno stile predefinito?But what is a default style? Un controllo personalizzato, basato su modello, 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 un stile di visualizzazione e/o un modello.A custom (templated) control needs to have a default style—containing a default control template—which it can use to render itself with in case the consumer of the control doesn't set a style and/or template. In questa sezione si aggiungerà un file di markup al progetto contenente lo stile predefinito.In this section we'll add a markup file to the project containing our default style.

Assicurati che l'impostazione Mostra tutti i file sia ancora attivata (in Esplora soluzioni).Make sure that Show All Files is still toggled on (in Solution Explorer). Nel nodo del progetto crea una nuova cartella (non un filtro) e assegnale il nome "Themes".Under your project node, create a new folder (not a filter, but a folder) and name it "Themes". Sotto Themes aggiungi un nuovo elemento di tipo Visual C++ > XAML > Visualizzazione XAML e denominalo "Generic.xaml".Under Themes, add a new item of type Visual C++ > XAML > XAML View, and name it "Generic.xaml". I nomi di file e cartelle devono essere questi affinché il framework XAML trovi lo stile predefinito per un controllo personalizzato.The folder and file names have to be like this in order for the XAML framework to find the default style for a custom control. Elimina il contenuto predefinito di Generic.xaml e incolla il markup riportato di seguito.Delete the default contents of Generic.xaml, and paste in the markup below.

<!-- \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.In this case, the only property that the default style sets is the control template. Il modello è costituito da un quadrato, il cui sfondo è associato alla proprietà Background presente in tutte le istanze del tipo Control di XAML, e da un elemento di testo, il cui testo è associato alla proprietà di dipendenza BgLabelControl::Label.The template consists of a square (whose background is bound to the Background property that all instances of the XAML Control type have), and a text element (whose text is bound to the BgLabelControl::Label dependency property).

Aggiungere un'istanza di BgLabelControl alla pagina principale dell'interfaccia utenteAdd an instance of BgLabelControl to the main UI page

Apri MainPage.xaml, che contiene il markup XAML per la nostra pagina dell'interfaccia utente principale.Open MainPage.xaml, which contains the XAML markup for our main UI page. Immediatamente dopo l'elemento Button, all'interno di StackPanel, aggiungi il markup seguente.Immediately after the Button element (inside the StackPanel), add the following markup.

<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.Also, add the following include directive to MainPage.h so that the MainPage type (a combination of compiling XAML markup and imperative code) is aware of the BgLabelControl custom control type. Se vuoi usare BgLabelControl da un'altra pagina XAML, aggiungi la stessa direttiva di inclusione anche al file di intestazione per quella pagina.If you want to use BgLabelControl from another XAML page, then add this same include directive to the header file for that page, too. In alternativa inserisci una sola direttiva di inclusione nel file di intestazione precompilato.Or, alternatively, just put a single include directive in your precompiled header file.

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

A questo punto compila ed esegui il progetto.Now build and run the project. Noterai che il modello di controllo predefinito è associato al pennello di sfondo e all'etichetta dell'istanza BgLabelControl nel markup.You'll see that the default control template is binding to the background brush, and to the label, of the BgLabelControl instance in the markup.

Questa procedura dettagliata ha illustrato un esempio semplice di un controllo personalizzato basato su modello in C++/WinRT.This walkthrough showed a simple example of a custom (templated) control in C++/WinRT. Puoi rendere i tuoi controlli personalizzati ricchi e pieni di funzioni quanto desideri.You can make your own custom controls arbitrarily rich and full-featured. 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.For example, a custom control can take the form of something as complicated as an editable data grid, a video player, or a visualizer of 3D geometry.

Implementazione di funzioni sottoponibili a override come MeasureOverride e OnApplyTemplateImplementing overridable functions, such as MeasureOverride and OnApplyTemplate

I controlli personalizzati si derivano dalla classe di runtime Control che a sua volta deriva da classi di runtime di base.You derive a custom control from the Control runtime class, which itself further derives from base runtime classes. Inoltre sono disponibili metodi sottoponibili a override di Control, FrameworkElement e UIElement di cui è possibile eseguire l'override nella classe derivata.And there are overridable methods of Control, FrameworkElement, and UIElement that you can override in your derived class. Ecco un esempio di codice che illustra come fare.Here's a code example showing you how to do that.

struct BgLabelControl : BgLabelControlT<BgLabelControl>
{
...
    // Control overrides.
    void OnPointerPressed(Windows::UI::Xaml::Input::PointerRoutedEventArgs const& /* e */) const { ... };

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

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

Le funzioni sottoponibili a override sono diverse nelle proiezioni di linguaggi diversi.Overridable functions present themselves differently in different language projections. In C#, ad esempio, le funzioni sottoponibili a override si presentano tipicamente come funzioni virtuali protette.In C#, for example, overridable functions typically appear as protected virtual functions. In C++/WinRT non sono né virtuali né protette, ma è comunque possibile eseguirne l'override e fornire la propria implementazione, come illustrato in precedenza.In C++/WinRT, they're neither virtual nor protected, but you can still override them and provide your own implementation, as shown above.

API importantiImportant APIs