Condividi tramite


Panoramica del data binding

Questo argomento illustra come associare un controllo (o un altro elementi dell'interfaccia) a un singolo elemento oppure un controllo elementi a una raccolta di elementi in un'app UWP. Viene inoltre illustrato come controllare il rendering degli elementi, implementare una visualizzazione dei dettagli in base a una selezione e convertire i dati per la visualizzazione. Per informazioni dettagliate, vedere Informazioni approfondite sul data binding.

Prerequisiti

In questo argomento partiamo dal presupposto che tu sappia come creare una semplice app UWP. Per istruzioni sulla creazione della prima app UWP, vedi Introduzione alle app di Windows.

Creare il progetto

Per iniziare, crea un nuovo progetto App vuota (Windows universale). Chiama il progetto "Quickstart".

Binding a un singolo elemento

Ogni binding è costituito da una destinazione di binding e un'origine di binding. In genere, la destinazione è una proprietà di un controllo o un altro elemento dell'interfaccia utente e l'origine è una proprietà di un'istanza di classe (un modello di dati o un modello di visualizzazione). Questo esempio mostra come eseguire il binding di un controllo a un singolo elemento. La destinazione è la proprietà Text di un TextBlock. L’origine è un’istanza di una semplice classe denominata Recording che rappresenta una registrazione audio. Esaminiamo prima di tutto la classe.

Se usi C# o C++/CX, aggiungi una nuova classe al progetto e denomina la classe Registrazione.

Se stai usando C++/WinRT, aggiungi al progetto nuovi elementi di file Midl (.idl), denominati come illustrato nell'elenco di esempi di codice C++/WinRT seguente. Sostituisci il contenuto dei nuovi file con il codice MIDL 3.0 mostrato nell'elenco, crea il progetto per generare Recording.h, .cpp, RecordingViewModel.h e .cpp e aggiungi il codice ai file generati per trovare la corrispondenza con l'elenco. Per altre informazioni sui file generati e su come copiarli nel progetto, vedi Controlli XAML, binding a una proprietà C++/WinRT.

namespace Quickstart
{
    public class Recording
    {
        public string ArtistName { get; set; }
        public string CompositionName { get; set; }
        public DateTime ReleaseDateTime { get; set; }
        public Recording()
        {
            this.ArtistName = "Wolfgang Amadeus Mozart";
            this.CompositionName = "Andante in C for Piano";
            this.ReleaseDateTime = new DateTime(1761, 1, 1);
        }
        public string OneLineSummary
        {
            get
            {
                return $"{this.CompositionName} by {this.ArtistName}, released: "
                    + this.ReleaseDateTime.ToString("d");
            }
        }
    }
    public class RecordingViewModel
    {
        private Recording defaultRecording = new Recording();
        public Recording DefaultRecording { get { return this.defaultRecording; } }
    }
}
// Recording.idl
namespace Quickstart
{
    runtimeclass Recording
    {
        Recording(String artistName, String compositionName, Windows.Globalization.Calendar releaseDateTime);
        String ArtistName{ get; };
        String CompositionName{ get; };
        Windows.Globalization.Calendar ReleaseDateTime{ get; };
        String OneLineSummary{ get; };
    }
}

// RecordingViewModel.idl
import "Recording.idl";

namespace Quickstart
{
    runtimeclass RecordingViewModel
    {
        RecordingViewModel();
        Quickstart.Recording DefaultRecording{ get; };
    }
}

// Recording.h
// Add these fields:
...
#include <sstream>
...
private:
    std::wstring m_artistName;
    std::wstring m_compositionName;
    Windows::Globalization::Calendar m_releaseDateTime;
...

// Recording.cpp
// Implement like this:
...
Recording::Recording(hstring const& artistName, hstring const& compositionName, Windows::Globalization::Calendar const& releaseDateTime) :
    m_artistName{ artistName.c_str() },
    m_compositionName{ compositionName.c_str() },
    m_releaseDateTime{ releaseDateTime } {}

hstring Recording::ArtistName(){ return hstring{ m_artistName }; }
hstring Recording::CompositionName(){ return hstring{ m_compositionName }; }
Windows::Globalization::Calendar Recording::ReleaseDateTime(){ return m_releaseDateTime; }

hstring Recording::OneLineSummary()
{
    std::wstringstream wstringstream;
    wstringstream << m_compositionName.c_str();
    wstringstream << L" by " << m_artistName.c_str();
    wstringstream << L", released: " << m_releaseDateTime.MonthAsNumericString().c_str();
    wstringstream << L"/" << m_releaseDateTime.DayAsString().c_str();
    wstringstream << L"/" << m_releaseDateTime.YearAsString().c_str();
    return hstring{ wstringstream.str().c_str() };
}
...

// RecordingViewModel.h
// Add this field:
...
#include "Recording.h"
...
private:
    Quickstart::Recording m_defaultRecording{ nullptr };
...

// RecordingViewModel.cpp
// Implement like this:
...
Quickstart::Recording RecordingViewModel::DefaultRecording()
{
    Windows::Globalization::Calendar releaseDateTime;
    releaseDateTime.Year(1761);
    releaseDateTime.Month(1);
    releaseDateTime.Day(1);
    m_defaultRecording = winrt::make<Recording>(L"Wolfgang Amadeus Mozart", L"Andante in C for Piano", releaseDateTime);
    return m_defaultRecording;
}
...
// Recording.h
#include <sstream>
namespace Quickstart
{
    public ref class Recording sealed
    {
    private:
        Platform::String^ artistName;
        Platform::String^ compositionName;
        Windows::Globalization::Calendar^ releaseDateTime;
    public:
        Recording(Platform::String^ artistName, Platform::String^ compositionName,
            Windows::Globalization::Calendar^ releaseDateTime) :
            artistName{ artistName },
            compositionName{ compositionName },
            releaseDateTime{ releaseDateTime } {}
        property Platform::String^ ArtistName
        {
            Platform::String^ get() { return this->artistName; }
        }
        property Platform::String^ CompositionName
        {
            Platform::String^ get() { return this->compositionName; }
        }
        property Windows::Globalization::Calendar^ ReleaseDateTime
        {
            Windows::Globalization::Calendar^ get() { return this->releaseDateTime; }
        }
        property Platform::String^ OneLineSummary
        {
            Platform::String^ get()
            {
                std::wstringstream wstringstream;
                wstringstream << this->CompositionName->Data();
                wstringstream << L" by " << this->ArtistName->Data();
                wstringstream << L", released: " << this->ReleaseDateTime->MonthAsNumericString()->Data();
                wstringstream << L"/" << this->ReleaseDateTime->DayAsString()->Data();
                wstringstream << L"/" << this->ReleaseDateTime->YearAsString()->Data();
                return ref new Platform::String(wstringstream.str().c_str());
            }
        }
    };
    public ref class RecordingViewModel sealed
    {
    private:
        Recording ^ defaultRecording;
    public:
        RecordingViewModel()
        {
            Windows::Globalization::Calendar^ releaseDateTime = ref new Windows::Globalization::Calendar();
            releaseDateTime->Year = 1761;
            releaseDateTime->Month = 1;
            releaseDateTime->Day = 1;
            this->defaultRecording = ref new Recording{ L"Wolfgang Amadeus Mozart", L"Andante in C for Piano", releaseDateTime };
        }
        property Recording^ DefaultRecording
        {
            Recording^ get() { return this->defaultRecording; };
        }
    };
}

// Recording.cpp
#include "pch.h"
#include "Recording.h"

Esponi quindi la classe dell'origine di binding dalla classe che rappresenta la pagina del markup. Puoi eseguire questa operazione aggiungendo una proprietà di tipo RecordingViewModel a MainPage.

Se usi C++/WinRT, aggiorna prima di tutto MainPage.idl. Crea il progetto per rigenerare MainPage.h e .cpp e unisci le modifiche apportate ai file generati in quelle del progetto.

namespace Quickstart
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            this.ViewModel = new RecordingViewModel();
        }
        public RecordingViewModel ViewModel{ get; set; }
    }
}
// MainPage.idl
// Add this property:
import "RecordingViewModel.idl";
...
RecordingViewModel ViewModel{ get; };
...

// MainPage.h
// Add this property and this field:
...
#include "RecordingViewModel.h"
...
    Quickstart::RecordingViewModel ViewModel();

private:
    Quickstart::RecordingViewModel m_viewModel{ nullptr };
...

// MainPage.cpp
// Implement like this:
...
MainPage::MainPage()
{
    InitializeComponent();
    m_viewModel = winrt::make<RecordingViewModel>();
}
Quickstart::RecordingViewModel MainPage::ViewModel()
{
    return m_viewModel;
}
...
// MainPage.h
...
#include "Recording.h"

namespace Quickstart
{
    public ref class MainPage sealed
    {
    private:
        RecordingViewModel ^ viewModel;
    public:
        MainPage();

        property RecordingViewModel^ ViewModel
        {
            RecordingViewModel^ get() { return this->viewModel; };
        }
    };
}

// MainPage.cpp
...
MainPage::MainPage()
{
    InitializeComponent();
    this->viewModel = ref new RecordingViewModel();
}

L’ultima operazione è associare un TextBlock alla proprietà ViewModel.DefaultRecording.OneLiner.

<Page x:Class="Quickstart.MainPage" ... >
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{x:Bind ViewModel.DefaultRecording.OneLineSummary}"
    HorizontalAlignment="Center"
    VerticalAlignment="Center"/>
    </Grid>
</Page>

Se usi C++/WinRT, dovrai rimuovere la funzione MainPage::ClickHandler per la creazione del progetto.

Ecco il risultato.

Associazione di un blocco di testo

Binding a una raccolta di elementi

Uno scenario comune prevede il binding a una raccolta di oggetti business. In C# e Visual Basic la classe ObservableCollection<T> generica è un'ottima scelta di raccolta per il data binding, perché implementa le interfacce INotifyPropertyChanged e INotifyCollectionChanged. Queste interfacce offrono la notifica delle modifiche ai binding quando vengono aggiunti o rimossi elementi oppure quando cambia una proprietà dell'elenco stesso. Se desideri che i controlli associati vengano aggiornati in base alle modifiche alle proprietà degli oggetti della raccolta, anche l’oggetto business deve implementare l’interfaccia INotifyPropertyChanged. Per altre info, vedi Informazioni approfondite sul data binding.

Se usi C++/WinRT, puoi ottenere altre informazioni sul binding a una raccolta osservabile in Controlli di elementi XAML, binding a una raccolta C++/WinRT. Se stai leggendo prima questo argomento, lo scopo del listato di codice C++/WinRT mostrato di seguito sarà più chiaro.

Nell’esempio seguente viene associato un oggetto ListView a una raccolta di oggetti Recording. Iniziamo aggiungendo la raccolta al nostro modello di visualizzazione. È sufficiente aggiungere questi nuovi membri alla classe RecordingViewModel.

public class RecordingViewModel
{
    ...
    private ObservableCollection<Recording> recordings = new ObservableCollection<Recording>();
    public ObservableCollection<Recording> Recordings{ get{ return this.recordings; } }
    public RecordingViewModel()
    {
        this.recordings.Add(new Recording(){ ArtistName = "Johann Sebastian Bach",
            CompositionName = "Mass in B minor", ReleaseDateTime = new DateTime(1748, 7, 8) });
        this.recordings.Add(new Recording(){ ArtistName = "Ludwig van Beethoven",
            CompositionName = "Third Symphony", ReleaseDateTime = new DateTime(1805, 2, 11) });
        this.recordings.Add(new Recording(){ ArtistName = "George Frideric Handel",
            CompositionName = "Serse", ReleaseDateTime = new DateTime(1737, 12, 3) });
    }
}
// RecordingViewModel.idl
// Add this property:
...
#include <winrt/Windows.Foundation.Collections.h>
...
Windows.Foundation.Collections.IVector<IInspectable> Recordings{ get; };
...

// RecordingViewModel.h
// Change the constructor declaration, and add this property and this field:
...
    RecordingViewModel();
    Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> Recordings();

private:
    Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> m_recordings;
...

// RecordingViewModel.cpp
// Update/add implementations like this:
...
RecordingViewModel::RecordingViewModel()
{
    std::vector<Windows::Foundation::IInspectable> recordings;

    Windows::Globalization::Calendar releaseDateTime;
    releaseDateTime.Month(7); releaseDateTime.Day(8); releaseDateTime.Year(1748);
    recordings.push_back(winrt::make<Recording>(L"Johann Sebastian Bach", L"Mass in B minor", releaseDateTime));

    releaseDateTime = Windows::Globalization::Calendar{};
    releaseDateTime.Month(11); releaseDateTime.Day(2); releaseDateTime.Year(1805);
    recordings.push_back(winrt::make<Recording>(L"Ludwig van Beethoven", L"Third Symphony", releaseDateTime));

    releaseDateTime = Windows::Globalization::Calendar{};
    releaseDateTime.Month(3); releaseDateTime.Day(12); releaseDateTime.Year(1737);
    recordings.push_back(winrt::make<Recording>(L"George Frideric Handel", L"Serse", releaseDateTime));

    m_recordings = winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>(std::move(recordings));
}

Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> RecordingViewModel::Recordings() { return m_recordings; }
...
// Recording.h
...
public ref class RecordingViewModel sealed
{
private:
    ...
    Windows::Foundation::Collections::IVector<Recording^>^ recordings;
public:
    RecordingViewModel()
    {
        ...
        releaseDateTime = ref new Windows::Globalization::Calendar();
        releaseDateTime->Year = 1748;
        releaseDateTime->Month = 7;
        releaseDateTime->Day = 8;
        Recording^ recording = ref new Recording{ L"Johann Sebastian Bach", L"Mass in B minor", releaseDateTime };
        this->Recordings->Append(recording);
        releaseDateTime = ref new Windows::Globalization::Calendar();
        releaseDateTime->Year = 1805;
        releaseDateTime->Month = 2;
        releaseDateTime->Day = 11;
        recording = ref new Recording{ L"Ludwig van Beethoven", L"Third Symphony", releaseDateTime };
        this->Recordings->Append(recording);
        releaseDateTime = ref new Windows::Globalization::Calendar();
        releaseDateTime->Year = 1737;
        releaseDateTime->Month = 12;
        releaseDateTime->Day = 3;
        recording = ref new Recording{ L"George Frideric Handel", L"Serse", releaseDateTime };
        this->Recordings->Append(recording);
    }
    ...
    property Windows::Foundation::Collections::IVector<Recording^>^ Recordings
    {
        Windows::Foundation::Collections::IVector<Recording^>^ get()
        {
            if (this->recordings == nullptr)
            {
                this->recordings = ref new Platform::Collections::Vector<Recording^>();
            }
            return this->recordings;
        };
    }
};

Associa quindi un ListView alla proprietà ViewModel.Recordings.

<Page x:Class="Quickstart.MainPage" ... >
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView ItemsSource="{x:Bind ViewModel.Recordings}"
        HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
</Page>

Non abbiamo non abbiamo ancora fornito un modello di dati per la classe Recording, quindi il massimo che il framework dell’interfaccia utente può fare è chiamare ToString per ogni elemento in ListView. L’implementazione predefinita di ToString è la restituzione del nome del tipo.

Binding di una visualizzazione elenco 1

Per risolvere questo problema, possiamo eseguire l'override di ToString per restituire il valore di OneLineSummary oppure possiamo fornire un modello di dati. L'opzione del modello di dati è una soluzione più comune e più flessibile. Devi specificare il modello di dati usando la proprietà ContentTemplate di un controllo del contenuto o la proprietà ItemTemplate di un controllo elementi. Ecco due modi in cui potremmo progettare un modello dati per Recording, insieme a un’illustrazione del risultato.

<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center" VerticalAlignment="Center">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:Recording">
            <TextBlock Text="{x:Bind OneLineSummary}"/>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Binding di una visualizzazione elenco 2

<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center" VerticalAlignment="Center">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:Recording">
            <StackPanel Orientation="Horizontal" Margin="6">
                <SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
                <StackPanel>
                    <TextBlock Text="{x:Bind ArtistName}" FontWeight="Bold"/>
                    <TextBlock Text="{x:Bind CompositionName}"/>
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Binding di una visualizzazione elenco 3

Per altre informazioni sulla sintassi XAML, vedere Creare un’interfaccia utente con XAML. Per altre informazioni sul layout dei controlli, vedere Definire layout con XAML.

Aggiunta di una visualizzazione dei dettagli

Puoi scegliere di visualizzare tutti i dettagli degli oggetti Recording in elementi ListView. Questo tuttavia richiede molto spazio. In alternativa, è possibile visualizzare nell'elemento solo i dati sufficienti per identificarlo e quindi, quando l'utente effettua una selezione, visualizzare tutti i dettagli dell'elemento selezionato in un elemento separato dell'interfaccia utente, denominato visualizzazione dei dettagli. Questa soluzione è anche nota come visualizzazione master/dettagli o visualizzazione elenco/dettagli.

Si può procedere in due modi. Puoi associare la visualizzazione dei dettagli alla proprietà SelectedItem di ListView. In alternativa puoi usare un CollectionViewSource, in questo caso associa sia ListView che la visualizzazione dei dettagli a CollectionViewSource (in questo modo gestirà automaticamente l'elemento attualmente selezionato). Entrambe le tecniche sono illustrate di seguito e forniscono gli stessi risultati (mostrati nell'illustrazione).

Nota

Finora in questo argomento abbiamo usato solo l'estensione di markup {x:Bind}, ma entrambe le tecniche mostrate di seguito richiedono l'estensione di markup {Binding}, più flessibile ma con prestazioni inferiori.

Se stai usando le estensioni del componente C++/WinRT o Visual C++ (C++/CX), per usare l'estensione di markup {Binding} devi aggiungere l'attributo BindableAttribute a qualsiasi classe di runtime a cui vuoi eseguire l'associazione. Per usare {x:Bind}, questo attributo non è necessario.

Importante

Se stai usando C++/WinRT, l'attributo BindableAttribute è disponibile se hai installato Windows SDK 10.0.17763.0 (Windows 10, versione 1809) o versioni successive. Senza tale attributo, è necessario implementare le interfacce ICustomPropertyProvider e ICustomProperty per poter usare l'estensione di markup {Binding}.

Prima di tutto, ecco la tecnica SelectedItem.

// No code changes necessary for C#.
// Recording.idl
// Add this attribute:
...
[Windows.UI.Xaml.Data.Bindable]
runtimeclass Recording
...
[Windows::UI::Xaml::Data::Bindable]
public ref class Recording sealed
{
    ...
};

L'unica modifica necessaria riguarda il markup.

<Page x:Class="Quickstart.MainPage" ... >
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <ListView x:Name="recordingsListView" ItemsSource="{x:Bind ViewModel.Recordings}">
                <ListView.ItemTemplate>
                    <DataTemplate x:DataType="local:Recording">
                        <StackPanel Orientation="Horizontal" Margin="6">
                            <SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
                            <StackPanel>
                                <TextBlock Text="{x:Bind CompositionName}"/>
                            </StackPanel>
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
            <StackPanel DataContext="{Binding SelectedItem, ElementName=recordingsListView}"
            Margin="0,24,0,0">
                <TextBlock Text="{Binding ArtistName}"/>
                <TextBlock Text="{Binding CompositionName}"/>
                <TextBlock Text="{Binding ReleaseDateTime}"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Page>

Per la tecnica CollectionViewSource, aggiungi prima un oggetto CollectionViewSource come risorsa della pagina.

<Page.Resources>
    <CollectionViewSource x:Name="RecordingsCollection" Source="{x:Bind ViewModel.Recordings}"/>
</Page.Resources>

Imposta quindi i binding su ListView (che non deve più essere denominato) e sulla visualizzazione dei dettagli per usare CollectionViewSource. Tieni presente che eseguendo il binding della visualizzazione dei dettagli direttamente a CollectionViewSource, stai implicando che vuoi associare l’elemento corrente nei binding in cui non è possibile trovare il percorso nella raccolta stessa. Non è necessario specificare la proprietà CurrentItem come percorso per il binding, sebbene sia possibile farlo se esiste un’ambiguità.

...
<ListView ItemsSource="{Binding Source={StaticResource RecordingsCollection}}">
...
<StackPanel DataContext="{Binding Source={StaticResource RecordingsCollection}}" ...>
...

Ecco il risultato identico in ogni caso.

Nota

Se usi C++, la tua interfaccia utente non sarà simile a quella illustrata di seguito: il rendering della proprietà ReleaseDateTime è diverso. Per altre informazioni, vedi la sezione seguente.

Binding di una visualizzazione elenco 4

Formattazione o conversione dei valori dei dati per la visualizzazione

C'è un problema con il rendering descritto in precedenza. La proprietà ReleaseDateTime non è solamente una data, è una DateTime (se stai usando C++, è un Calendar). Quindi, in C#, viene visualizzato con più precisione del necessario. Inoltre, in C++ viene eseguito il rendering come nome di tipo. Una soluzione consiste nell'aggiungere una proprietà di stringa alla classe Recording che restituisce l'equivalente di this.ReleaseDateTime.ToString("d"). Denominando tale proprietà ReleaseDate puoi indicare che restituisce una data e non data e ora. Denominandola ReleaseDateAsString, puoi indicare che restituisce una stringa.

Una soluzione più flessibile è usare un elemento denominato convertitore di valori. Ecco un esempio di come creare un convertitore di valori personalizzato. Se usi C#, aggiungi il codice seguente al file di codice sorgente Recording.cs. Se usi C++/WinRT, aggiungi un nuovo elemento dell'elemento di file Midl (.idl) al progetto, denominato come illustrato nel listato di esempi di codice C++/WinRT seguente, crea il progetto per generare StringFormatter.h e .cpp, aggiungi tali file al progetto e quindi incolla i listati di codice. Aggiungi anche #include "StringFormatter.h" a MainPage.h.

public class StringFormatter : Windows.UI.Xaml.Data.IValueConverter
{
    // This converts the value object to the string to display.
    // This will work with most simple types.
    public object Convert(object value, Type targetType,
        object parameter, string language)
    {
        // Retrieve the format string and use it to format the value.
        string formatString = parameter as string;
        if (!string.IsNullOrEmpty(formatString))
        {
            return string.Format(formatString, value);
        }

        // If the format string is null or empty, simply
        // call ToString() on the value.
        return value.ToString();
    }

    // No need to implement converting back on a one-way binding
    public object ConvertBack(object value, Type targetType,
        object parameter, string language)
    {
        throw new NotImplementedException();
    }
}
// pch.h
...
#include <winrt/Windows.Globalization.h>

// StringFormatter.idl
namespace Quickstart
{
    runtimeclass StringFormatter : [default] Windows.UI.Xaml.Data.IValueConverter
    {
        StringFormatter();
    }
}

// StringFormatter.h
#pragma once

#include "StringFormatter.g.h"
#include <sstream>

namespace winrt::Quickstart::implementation
{
    struct StringFormatter : StringFormatterT<StringFormatter>
    {
        StringFormatter() = default;

        Windows::Foundation::IInspectable Convert(Windows::Foundation::IInspectable const& value, Windows::UI::Xaml::Interop::TypeName const& targetType, Windows::Foundation::IInspectable const& parameter, hstring const& language);
        Windows::Foundation::IInspectable ConvertBack(Windows::Foundation::IInspectable const& value, Windows::UI::Xaml::Interop::TypeName const& targetType, Windows::Foundation::IInspectable const& parameter, hstring const& language);
    };
}

namespace winrt::Quickstart::factory_implementation
{
    struct StringFormatter : StringFormatterT<StringFormatter, implementation::StringFormatter>
    {
    };
}

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

namespace winrt::Quickstart::implementation
{
    Windows::Foundation::IInspectable StringFormatter::Convert(Windows::Foundation::IInspectable const& value, Windows::UI::Xaml::Interop::TypeName const& /* targetType */, Windows::Foundation::IInspectable const& /* parameter */, hstring const& /* language */)
    {
        // Retrieve the value as a Calendar.
        Windows::Globalization::Calendar valueAsCalendar{ value.as<Windows::Globalization::Calendar>() };

        std::wstringstream wstringstream;
        wstringstream << L"Released: ";
        wstringstream << valueAsCalendar.MonthAsNumericString().c_str();
        wstringstream << L"/" << valueAsCalendar.DayAsString().c_str();
        wstringstream << L"/" << valueAsCalendar.YearAsString().c_str();
        return winrt::box_value(hstring{ wstringstream.str().c_str() });
    }

    Windows::Foundation::IInspectable StringFormatter::ConvertBack(Windows::Foundation::IInspectable const& /* value */, Windows::UI::Xaml::Interop::TypeName const& /* targetType */, Windows::Foundation::IInspectable const& /* parameter */, hstring const& /* language */)
    {
        throw hresult_not_implemented();
    }
}
...
public ref class StringFormatter sealed : Windows::UI::Xaml::Data::IValueConverter
{
public:
    virtual Platform::Object^ Convert(Platform::Object^ value, TypeName targetType, Platform::Object^ parameter, Platform::String^ language)
    {
        // Retrieve the value as a Calendar.
        Windows::Globalization::Calendar^ valueAsCalendar = dynamic_cast<Windows::Globalization::Calendar^>(value);

        std::wstringstream wstringstream;
        wstringstream << L"Released: ";
        wstringstream << valueAsCalendar->MonthAsNumericString()->Data();
        wstringstream << L"/" << valueAsCalendar->DayAsString()->Data();
        wstringstream << L"/" << valueAsCalendar->YearAsString()->Data();
        return ref new Platform::String(wstringstream.str().c_str());
    }

    // No need to implement converting back on a one-way binding
    virtual Platform::Object^ ConvertBack(Platform::Object^ value, TypeName targetType, Platform::Object^ parameter, Platform::String^ language)
    {
        throw ref new Platform::NotImplementedException();
    }
};
...

Nota

Per il listato di codice C++/WinRT precedente, in StringFormatter.idl, viene usato l'attributo predefinito per dichiarare IValueConverter come interfaccia predefinita. Nel listato, StringFormatter dispone solo di un costruttore e non di metodi, pertanto non viene generata alcuna interfaccia predefinita. L'attributo default è ottimale se non aggiungi membri di istanza a StringFormatter, perché non sarà richiesta alcuna QueryInterface per chiamare i metodi IValueConverter. In alternativa, puoi richiedere la generazione di un'interfaccia predefinita IStringFormatter annotando la classe di runtime stessa con l'attributo default_interface. Questa opzione è ottimale se si aggiungono membri di istanza a StringFormatter che vengono chiamati più spesso rispetto ai metodi di IValueConverter, perché non sarà necessario alcun QueryInterface per chiamare i membri dell'istanza.

Ora possiamo aggiungere un'istanza di StringFormatter come risorsa di pagina e usarla nel binding di TextBlock che mostra la proprietà ReleaseDateTime.

<Page.Resources>
    <local:StringFormatter x:Key="StringFormatterValueConverter"/>
</Page.Resources>
...
<TextBlock Text="{Binding ReleaseDateTime,
    Converter={StaticResource StringFormatterValueConverter},
    ConverterParameter=Released: \{0:d\}}"/>
...

Come puoi notare, per la flessibilità di formattazione usiamo il markup per passare una stringa di formato nel convertitore attraverso il parametro del convertitore. Negli esempi di codice mostrati in questo argomento, solo il convertitore di valore C# usa questo parametro. Tuttavia, puoi facilmente passare una stringa di formato di tipo C++ come parametro del convertitore, e usarla nel convertitore di valori con una funzione di formattazione come wprintf o swprintf.

Ecco il risultato.

visualizzazione di una data con formattazione personalizzata

Nota

A partire da Windows 10 versione 1607, il framework XAML fornisce un convertitore da valori booleani a Visibility. Il convertitore mappa true al valore di enumerazione Visibility.Visible e false a Visibility.Collapsed, quindi puoi associare una proprietà Visibility a un valore booleano senza creare un convertitore. Per usare il convertitore predefinito, la versione minima dell'SDK di destinazione dell'app deve essere 14393 o successiva. Non puoi usarlo quando l'app è destinata alle versioni precedenti di Windows 10. Per altre info sulle versioni di destinazione, vedi Codice adattivo per la versione.

Vedi anche