Condividi tramite


Associazione dati approfondita

API importanti

Annotazioni

In questo argomento vengono descritte in dettaglio le funzionalità di data binding. Per una breve introduzione pratica, vedere Panoramica del data binding.

Questo argomento riguarda il data binding per le API che risiedono nello spazio dei nomi Windows.UI.Xaml.Data.

Il data binding è un modo per consentire all'interfaccia utente dell'app di visualizzare i dati e, facoltativamente, di rimanere sincronizzati con tali dati. Il data binding consente di separare il problema dei dati dall'interfaccia utente e ciò comporta un modello concettuale più semplice, nonché una migliore leggibilità, testabilità e manutenibilità dell'app.

È possibile usare il data binding per visualizzare semplicemente i valori di un'origine dati quando l'interfaccia utente viene visualizzata per la prima volta, ma non per rispondere alle modifiche apportate a tali valori. Si tratta di una modalità di associazione denominata una tantum e funziona bene per un valore che non cambia durante il runtime. In alternativa, è possibile scegliere di "osservare" i valori e di aggiornare l'interfaccia utente quando cambiano. Questa modalità è denominata unidirezionale e funziona bene per i dati di sola lettura. In definitiva, è possibile scegliere di osservare e aggiornare, in modo che le modifiche apportate dall'utente ai valori nell'interfaccia utente vengano automaticamente inserite nell'origine dati. Questa modalità è denominata bidirezionale e funziona bene per i dati di lettura/scrittura. Ecco alcuni esempi.

  • È possibile usare la modalità monouso per associare un'immagine alla foto dell'utente corrente.
  • È possibile utilizzare la modalità unidirezionale per associare un controllo ListView a una raccolta di articoli di notizie in tempo reale raggruppati per sezione di giornale.
  • È possibile usare la modalità bidirezionale per associare un controllo TextBox al nome di un cliente in un modulo.

Indipendentemente dalla modalità, esistono due tipi di binding e vengono in genere dichiarati nel markup dell'interfaccia utente. È possibile scegliere di usare l'estensione di markup {x:Bind} o l'estensione di markup {Binding}. E puoi anche usare una combinazione dei due nella stessa app, anche nello stesso elemento dell'interfaccia utente. {x:Bind} è una novità per Windows 10 e offre prestazioni migliori. Tutti i dettagli descritti in questo argomento si applicano a entrambi i tipi di binding, a meno che non venga specificato in modo esplicito.

App di esempio che illustrano {x:Bind}

App di esempio che illustrano {Binding}

Ogni associazione implica questi pezzi

  • Un'origine di associazione. Questa è l'origine dei dati per l'associazione e può essere un'istanza di qualsiasi classe con membri i cui valori si desidera visualizzare nell'interfaccia utente.
  • Destinazione di associazione. Si tratta di dependencyProperty di FrameworkElement nell'interfaccia utente che visualizza i dati.
  • Oggetto di associazione. Questa è la parte che trasferisce i valori dei dati dall'origine alla destinazione e, facoltativamente, dalla destinazione all'origine. L'oggetto di associazione viene creato in fase di caricamento XAML dall'estensione di markup {x:Bind} o {Binding} .

Nelle sezioni seguenti verranno esaminate più in dettaglio l'origine di associazione, la destinazione di associazione e l'oggetto di associazione. Le sezioni verranno collegate insieme all'esempio di associazione del contenuto di un pulsante a una proprietà stringa denominata NextButtonText, che appartiene a una classe denominata HostViewModel.

Origine dell'associazione

Ecco un'implementazione molto rudimentale di una classe che è possibile usare come origine di associazione.

Se usi C++/WinRT, aggiungi nuovi elementi Midl File (con estensione idl) al progetto, denominato come illustrato nell'elenco di codice di esempio C++/WinRT riportato di seguito. Sostituire il contenuto di questi nuovi file con il codice MIDL 3.0 visualizzato nell'elenco, compilare il progetto per generare HostViewModel.h e .cppe quindi aggiungere codice ai file generati in modo che corrispondano all'elenco. Per altre info su questi file generati e su come copiarli nel progetto, vedi Controlli XAML, binding a una proprietà C++/WinRT.

public class HostViewModel
{
    public HostViewModel()
    {
        this.NextButtonText = "Next";
    }

    public string NextButtonText { get; set; }
}
// HostViewModel.idl
namespace DataBindingInDepth
{
    runtimeclass HostViewModel
    {
        HostViewModel();
        String NextButtonText;
    }
}

// HostViewModel.h
// Implement the constructor like this, and add this field:
...
HostViewModel() : m_nextButtonText{ L"Next" } {}
...
private:
    std::wstring m_nextButtonText;
...

// HostViewModel.cpp
// Implement like this:
...
hstring HostViewModel::NextButtonText()
{
    return hstring{ m_nextButtonText };
}

void HostViewModel::NextButtonText(hstring const& value)
{
    m_nextButtonText = value;
}
...

L'implementazione di HostViewModel e la relativa proprietà NextButtonText sono appropriate solo per l'associazione una tantum. Tuttavia, le associazioni unidirezionale e bidirezionale sono estremamente comuni e in questi tipi di associazione l'interfaccia utente viene aggiornata automaticamente in risposta alle modifiche apportate ai valori dei dati dell'origine di associazione. Affinché tali tipi di binding funzionino correttamente, è necessario rendere l'origine di binding "osservabile" all'oggetto di associazione. Pertanto, nell'esempio, se si vuole eseguire l'associazione unidirezionale o bidirezionale alla proprietà NextButtonText , tutte le modifiche apportate in fase di esecuzione al valore di tale proprietà devono essere rese osservabili all'oggetto di associazione.

Un modo per eseguire questa operazione consiste nel derivare la classe che rappresenta l'origine di associazione da DependencyObject ed esporre un valore di dati tramite DependencyProperty. Ecco come un FrameworkElement diventa osservabile. FrameworkElements sono origini di binding valide fin dall'inizio.

Un modo più leggero per rendere osservabile una classe e una classe necessaria per le classi che dispongono già di una classe base, consiste nell'implementare System.ComponentModel.INotifyPropertyChanged. Questo implica semplicemente l'implementazione di un singolo evento denominato PropertyChanged. Di seguito è riportato un esempio di utilizzo di HostViewModel .

...
using System.ComponentModel;
using System.Runtime.CompilerServices;
...
public class HostViewModel : INotifyPropertyChanged
{
    private string nextButtonText;

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public HostViewModel()
    {
        this.NextButtonText = "Next";
    }

    public string NextButtonText
    {
        get { return this.nextButtonText; }
        set
        {
            this.nextButtonText = value;
            this.OnPropertyChanged();
        }
    }

    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        // Raise the PropertyChanged event, passing the name of the property whose value has changed.
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
// HostViewModel.idl
namespace DataBindingInDepth
{
    runtimeclass HostViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
    {
        HostViewModel();
        String NextButtonText;
    }
}

// HostViewModel.h
// Add this field:
...
    winrt::event_token PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
    void PropertyChanged(winrt::event_token const& token) noexcept;

private:
    winrt::event<Windows::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
...

// HostViewModel.cpp
// Implement like this:
...
void HostViewModel::NextButtonText(hstring const& value)
{
    if (m_nextButtonText != value)
    {
        m_nextButtonText = value;
        m_propertyChanged(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"NextButtonText" });
    }
}

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

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

La proprietà NextButtonText è ora osservabile. Quando si crea un binding unidirezionale o bidirezionale a tale proprietà (verrà illustrato come in seguito), l'oggetto di associazione risultante sottoscrive l'evento PropertyChanged . Quando viene generato l'evento, il gestore dell'oggetto di associazione riceve un argomento contenente il nome della proprietà modificata. Questo è il modo in cui l'oggetto binding conosce il valore della proprietà da leggere di nuovo.

Pertanto, non è necessario implementare più volte il modello illustrato sopra, se si usa C# è sufficiente derivare dalla classe di base BindableBase disponibile nell'esempio QuizGame (nella cartella "Common"). Ecco un esempio dell'aspetto.

public class HostViewModel : BindableBase
{
    private string nextButtonText;

    public HostViewModel()
    {
        this.NextButtonText = "Next";
    }

    public string NextButtonText
    {
        get { return this.nextButtonText; }
        set { this.SetProperty(ref this.nextButtonText, value); }
    }
}
// Your BindableBase base class should itself derive from Windows::UI::Xaml::DependencyObject. Then, in HostViewModel.idl, derive from BindableBase instead of implementing INotifyPropertyChanged.

Annotazioni

Per C++/WinRT, qualsiasi classe di runtime dichiarata nell'applicazione che deriva da una classe di base è nota come classe componibile . Esistono vincoli per le classi componibili. Affinché un'applicazione superi i test del Kit di certificazione app Windows usata da Visual Studio e da Microsoft Store per convalidare gli invii (e pertanto affinché l'applicazione venga inserita correttamente in Microsoft Store), una classe componibile deve in ultima analisi derivare da una classe di base di Windows. Ciò significa che la classe nella radice della gerarchia di ereditarietà deve essere un tipo che ha origine in uno spazio dei nomi Windows.*. Se devi derivare una classe di runtime da una classe base, ad esempio per implementare una classe BindableBase da cui derivare tutti i modelli di visualizzazione, puoi derivare da Windows.UI.Xaml.DependencyObject.

La generazione dell'evento PropertyChanged con un argomento String.Empty o null indica che tutte le proprietà non indicizzatore nell'oggetto devono essere rilette. È possibile generare l'evento per indicare che le proprietà dell'indicizzatore nell'oggetto sono state modificate usando un argomento "Item[indexer]" per indicizzatori specifici (dove l'indicizzatore è il valore di indice) o un valore "Item[]" per tutti gli indicizzatori.

Un'origine di associazione può essere considerata come un singolo oggetto le cui proprietà contengono dati o come raccolta di oggetti. Nel codice C# e Visual Basic è possibile associare una sola volta a un oggetto che implementa List(Of T) per visualizzare una raccolta che non cambia in fase di esecuzione. Per una raccolta osservabile (osservando quando gli elementi vengono aggiunti e rimossi dalla raccolta), associare invece unidirezionale a ObservableCollection(Of T). Nel codice C++/CX puoi eseguire il binding a Vector<T> per le raccolte osservabili e non osservabili e C++/WinRT ha i propri tipi. Per eseguire l'associazione alle classi di raccolta personalizzate, usare le indicazioni riportate nella tabella seguente.

Scenario C# e VB (CLR) C++/WinRT C++/CX
Eseguire l'associazione a un oggetto . Può essere qualsiasi oggetto. Può essere qualsiasi oggetto. L'oggetto deve avere BindableAttribute o implementare ICustomPropertyProvider.
Ottenere notifiche di modifica delle proprietà da un oggetto associato. L'oggetto deve implementare INotifyPropertyChanged. L'oggetto deve implementare INotifyPropertyChanged. L'oggetto deve implementare INotifyPropertyChanged.
Eseguire l'associazione a una raccolta. List(Of T) IVector di IInspectable o IBindableObservableVector. Vedi Controlli elementi XAML, binding a una raccolta e raccolte C++/WinRTcon C++/WinRT. Vettore<T>
Ottenere notifiche di modifica della raccolta da una raccolta associata. ObservableCollection(Of T) IObservableVector di IInspectable. Ad esempio, winrt::single_threaded_observable_vector<T>. IObservableVector<T>. Vettore<T> implementa questa interfaccia.
Implementare una raccolta che supporta l'associazione. Estendere List(Of T) o implementare IList, IList(Of Object), IEnumerable o IEnumerable(Of Object). Il binding a IList(Of T) generico e IEnumerable(Of T) non è supportato. Implementare IVector di IInspectable. Vedi Controlli elementi XAML, binding a una raccolta e raccolte C++/WinRTcon C++/WinRT. Implementare IBindableVector, IBindableIterable, IVector<Object^>, IIterable<Object^>, IVector<IInspectable*o IIterable<IInspectable*>>. L'associazione a IVector<T> generico e T IIterable<non> è supportata.
Implementare una raccolta che supporta le notifiche di modifica della raccolta. Estendere ObservableCollection(Of T) o implementare (non generico) IList e INotifyCollectionChanged. Implementare IObservableVector di IInspectable o IBindableObservableVector. Implementare IBindableVector e IBindableObservableVector.
Implementare una raccolta che supporta il caricamento incrementale. Estendere ObservableCollection(Of T) o implementare (non generico) IList e INotifyCollectionChanged. Implementare anche ISupportIncrementalLoading. Implementare IObservableVector di IInspectable o IBindableObservableVector. Implementare inoltre ISupportIncrementalLoading Implementare IBindableVector, IBindableObservableVector e ISupportIncrementalLoading.

È possibile associare controlli elenco a origini dati arbitrariamente di grandi dimensioni e ottenere comunque prestazioni elevate usando il caricamento incrementale. Ad esempio, è possibile associare controlli elenco ai risultati delle query di immagini Bing senza dover caricare tutti i risultati contemporaneamente. Al contrario, si caricano solo alcuni risultati immediatamente e si caricano risultati aggiuntivi in base alle esigenze. Per supportare il caricamento incrementale, è necessario implementare ISupportIncrementalLoading in un'origine dati che supporta le notifiche di modifica della raccolta. Quando il motore di data binding richiede più dati, l'origine dati deve effettuare le richieste appropriate, integrare i risultati e quindi inviare le notifiche appropriate per aggiornare l'interfaccia utente.

Destinazione di binding

Nei due esempi seguenti la proprietà Button.Content è la destinazione di associazione e il relativo valore viene impostato su un'estensione di markup che dichiara l'oggetto di associazione. Viene visualizzato il primo {x:Bind} e quindi {Binding}. La dichiarazione di associazioni nel markup è il caso comune (è pratico, leggibile e utilizzabile). Tuttavia, se necessario, è possibile evitare markup e creare in modo imperativo (a livello di codice) un'istanza della classe Binding .

<Button Content="{x:Bind ...}" ... />
<Button Content="{Binding ...}" ... />

Se usi estensioni del componente C++/WinRT o Visual C++ (C++/CX), dovrai aggiungere l'attributo BindableAttribute a qualsiasi classe di runtime con cui vuoi usare l'estensione di markup {Binding} .

Importante

Se usi C++/WinRT, l'attributo BindableAttribute è disponibile se hai installato Windows SDK versione 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} .

Oggetto binding dichiarato con {x:Bind}

È necessario eseguire un passaggio prima di creare il markup {x:Bind} . È necessario esporre la classe di origine dell'associazione dalla classe che rappresenta la pagina di markup. A questo scopo, aggiungere una proprietà (di tipo HostViewModel in questo caso) alla classe della pagina MainPage .

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

namespace DataBindingInDepth
{
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        HostViewModel ViewModel{ get; };
    }
}

// MainPage.h
// Include a header, and add this field:
...
#include "HostViewModel.h"
...
    DataBindingInDepth::HostViewModel ViewModel();

private:
    DataBindingInDepth::HostViewModel m_viewModel{ nullptr };
...

// MainPage.cpp
// Implement like this:
...
MainPage::MainPage()
{
    InitializeComponent();

}

DataBindingInDepth::HostViewModel MainPage::ViewModel()
{
    return m_viewModel;
}
...

A questo scopo, è ora possibile esaminare più in dettaglio il markup che dichiara l'oggetto di associazione. L'esempio seguente usa la stessa destinazione di associazione Button.Content usata nella sezione "Destinazione binding" precedente e mostra che è associata alla proprietà HostViewModel.NextButtonText .

<!-- MainPage.xaml -->
<Page x:Class="DataBindingInDepth.Mainpage" ... >
    <Button Content="{x:Bind Path=ViewModel.NextButtonText, Mode=OneWay}" ... />
</Page>

Si noti il valore specificato per Path. Questo valore viene interpretato nel contesto della pagina stessa e in questo caso il percorso inizia facendo riferimento alla proprietà ViewModel appena aggiunta alla pagina MainPage . Tale proprietà restituisce un'istanza di HostViewModel e quindi è possibile aggiungere un punto a tale oggetto per accedere alla proprietà HostViewModel.NextButtonText . Viene inoltre specificata la modalità per eseguire l'override del valore predefinito {x:Bind} una tantum.

La proprietà Path supporta un'ampia gamma di opzioni di sintassi per l'associazione a proprietà annidate, proprietà associate e indicizzatori di tipo integer e stringhe. Per altre info, vedi Sintassi property-path. L'associazione agli indicizzatori di stringhe offre l'effetto dell'associazione alle proprietà dinamiche senza dover implementare ICustomPropertyProvider. Per altre impostazioni, vedere estensione di markup {x:Bind}.

Per illustrare che la proprietà HostViewModel.NextButtonText è effettivamente osservabile, aggiungere un gestore eventi Click al pulsante e aggiornare il valore di HostViewModel.NextButtonText. Compilare, eseguire e fare clic sul pulsante per visualizzare il valore dell'aggiornamento del contenuto del pulsante.

// MainPage.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    this.ViewModel.NextButtonText = "Updated Next button text";
}
// MainPage.cpp
void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
    ViewModel().NextButtonText(L"Updated Next button text");
}

Annotazioni

Le modifiche apportate a TextBox.Text vengono inviate a un'origine associata bidirezionale quando textBox perde lo stato attivo e non dopo ogni sequenza di tasti utente.

DataTemplate e x:DataType

All'interno di un Oggetto DataTemplate (usato come modello di elemento, modello di contenuto o modello di intestazione), il valore di Path non viene interpretato nel contesto della pagina, ma nel contesto dell'oggetto dati basato su modelli. Quando si usa {x:Bind} in un modello di dati, in modo che i relativi binding possano essere convalidati (e generati codice efficiente) in fase di compilazione, DataTemplate deve dichiarare il tipo dell'oggetto dati usando x:DataType. L'esempio riportato di seguito può essere usato come ItemTemplate di un controllo elementi associato a una raccolta di oggetti SampleDataGroup .

<DataTemplate x:Key="SimpleItemTemplate" x:DataType="data:SampleDataGroup">
    <StackPanel Orientation="Vertical" Height="50">
      <TextBlock Text="{x:Bind Title}"/>
      <TextBlock Text="{x:Bind Description}"/>
    </StackPanel>
  </DataTemplate>

Oggetti tipizzati in modo debole nel percorso

Si consideri, ad esempio, un tipo denominato SampleDataGroup, che implementa una proprietà stringa denominata Title. Si dispone di una proprietà MainPage.SampleDataGroupAsObject, che è di tipo oggetto, ma che restituisce effettivamente un'istanza di SampleDataGroup. L'associazione <TextBlock Text="{x:Bind SampleDataGroupAsObject.Title}"/> genererà un errore di compilazione perché la proprietà Title non viene trovata nell'oggetto type. Il rimedio per questo consiste nell'aggiungere un cast alla sintassi Path, come illustrato di seguito: <TextBlock Text="{x:Bind ((data:SampleDataGroup)SampleDataGroupAsObject).Title}"/>. Ecco un altro esempio in cui Element viene dichiarato come oggetto, ma in realtà è textBlock: <TextBlock Text="{x:Bind Element.Text}"/>. E un cast risolve il problema: <TextBlock Text="{x:Bind ((TextBlock)Element).Text}"/>.

Se i dati vengono caricati in modo asincrono

Il codice per supportare {x:Bind} viene generato in fase di compilazione nelle classi parziali per le pagine. Questi file sono disponibili nella obj cartella, con nomi come (per C#) <view name>.g.cs. Il codice generato include un gestore per l'evento Loading della pagina e tale gestore chiama il metodo Initialize in una classe generata che rappresenta le associazioni della pagina. Inizializzare a sua volta chiama Update per iniziare a spostare i dati tra l'origine di associazione e la destinazione. Il caricamento viene generato poco prima del passaggio della prima misura della pagina o del controllo utente. Pertanto, se i dati vengono caricati in modo asincrono, potrebbe non essere pronto dal momento in cui viene chiamato Initialize . Dopo aver caricato i dati, è quindi possibile forzare l'inizializzazione delle associazioni una tantum chiamando this.Bindings.Update();. Se sono necessarie solo associazioni monouso per i dati caricati in modo asincrono, è molto più conveniente inizializzarle in questo modo rispetto a quelle di avere binding unidirezionale e ascoltare le modifiche. Se i dati non subiscono modifiche con granularità fine e, se è probabile che vengano aggiornati come parte di un'azione specifica, è possibile apportare le associazioni una sola volta e forzare un aggiornamento manuale in qualsiasi momento con una chiamata a Aggiorna.

Annotazioni

{x:Bind} non è adatto agli scenari con associazione tardiva, ad esempio l'esplorazione della struttura del dizionario di un oggetto JSON, né la digitazione di anatra. "Duck typing" è una forma debole di digitazione basata su corrispondenze lessicali sui nomi delle proprietà (come in , "se cammina, nuota e quack come un'anatra, allora è un'anatra"). Con la digitazione anatra, un binding alla proprietà Age sarebbe altrettanto soddisfatto di un oggetto Person o Wine (presupponendo che tali tipi avessero una proprietà Age ). Per questi scenari, usare l'estensione di markup {Binding} .

Oggetto binding dichiarato con {Binding}

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

// HostViewModel.idl
// Add this attribute:
[Windows.UI.Xaml.Data.Bindable]
runtimeclass HostViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
...

Importante

Se usi C++/WinRT, l'attributo BindableAttribute è disponibile se hai installato Windows SDK versione 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} .

{Binding} presuppone che, per impostazione predefinita, si stia eseguendo il binding al DataContext della pagina di markup. Il DataContext della pagina verrà quindi impostato come istanza della classe di origine dell'associazione (di tipo HostViewModel in questo caso). L'esempio seguente mostra il markup che dichiara l'oggetto di associazione. Usiamo la stessa destinazione di associazione Button.Content usata in precedenza nella sezione "Destinazione binding" e ci associamo alla proprietà HostViewModel.NextButtonText .

<Page xmlns:viewmodel="using:DataBindingInDepth" ... >
    <Page.DataContext>
        <viewmodel:HostViewModel x:Name="viewModelInDataContext"/>
    </Page.DataContext>
    ...
    <Button Content="{Binding Path=NextButtonText}" ... />
</Page>
// MainPage.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    this.viewModelInDataContext.NextButtonText = "Updated Next button text";
}
// MainPage.cpp
void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
    viewModelInDataContext().NextButtonText(L"Updated Next button text");
}

Si noti il valore specificato per Path. Questo valore viene interpretato nel contesto del DataContext della pagina, che in questo esempio è impostato su un'istanza di HostViewModel. Il percorso fa riferimento alla proprietà HostViewModel.NextButtonText . È possibile omettere la modalità perché l'impostazione predefinita {Binding} di unidirezionale funziona qui.

Il valore predefinito di DataContext per un elemento dell'interfaccia utente è il valore ereditato del relativo elemento padre. È naturalmente possibile eseguire l'override di tale impostazione predefinita impostando DataContext in modo esplicito, che a sua volta viene ereditato dagli elementi figlio per impostazione predefinita. L'impostazione di DataContext in modo esplicito su un elemento è utile quando si desidera avere più associazioni che usano la stessa origine.

Un oggetto binding ha una proprietà Source , che per impostazione predefinita corrisponde al DataContext dell'elemento dell'interfaccia utente in cui viene dichiarata l'associazione. È possibile eseguire l'override di questa impostazione predefinita impostando in modo esplicito Source, RelativeSource o ElementName nell'associazione (vedere {Binding} per informazioni dettagliate).

All'interno di un Oggetto DataTemplate, DataContext viene impostato automaticamente sull'oggetto dati basato su modelli. L'esempio riportato di seguito può essere usato come ItemTemplate di un controllo elementi associato a una raccolta di qualsiasi tipo con proprietà stringa denominate Title e Description.

<DataTemplate x:Key="SimpleItemTemplate">
    <StackPanel Orientation="Vertical" Height="50">
      <TextBlock Text="{Binding Title}"/>
      <TextBlock Text="{Binding Description"/>
    </StackPanel>
  </DataTemplate>

Annotazioni

Per impostazione predefinita, le modifiche apportate a TextBox.Text vengono inviate a un'origine associata bidirezionale quando textBox perde lo stato attivo. Per fare in modo che le modifiche vengano inviate dopo ogni sequenza di tasti utente, impostare UpdateSourceTrigger su PropertyChanged sull'associazione nel markup. È anche possibile controllare completamente quando le modifiche vengono inviate all'origine impostando UpdateSourceTrigger su Explicit. È quindi possibile gestire gli eventi nella casella di testo (in genere TextBox.TextChanged), chiamare GetBindingExpression nella destinazione per ottenere un oggetto BindingExpression e infine chiamare BindingExpression.UpdateSource per aggiornare l'origine dati a livello di codice.

La proprietà Path supporta un'ampia gamma di opzioni di sintassi per l'associazione a proprietà annidate, proprietà associate e indicizzatori di tipo integer e stringhe. Per altre info, vedi Sintassi property-path. L'associazione agli indicizzatori di stringhe offre l'effetto dell'associazione alle proprietà dinamiche senza dover implementare ICustomPropertyProvider. La proprietà ElementName è utile per l'associazione da elemento a elemento. La proprietà RelativeSource include diversi usi, uno dei quali è un'alternativa più potente all'associazione di modelli all'interno di un ControlTemplate. Per altre impostazioni, vedere Estensione di markup {Binding} e classe Binding .

Cosa accade se l'origine e la destinazione non sono dello stesso tipo?

Se vuoi controllare la visibilità di un elemento dell'interfaccia utente in base al valore di una proprietà booleana o se vuoi eseguire il rendering di un elemento dell'interfaccia utente con un colore che rappresenta una funzione dell'intervallo o della tendenza di un valore numerico oppure se vuoi visualizzare un valore di data e/o ora in una proprietà dell'elemento dell'interfaccia utente che prevede una stringa, sarà quindi necessario convertire i valori da un tipo a un altro. Ci saranno casi in cui la soluzione corretta consiste nell'esporre un'altra proprietà del tipo corretto dalla classe di origine dell'associazione e mantenere la logica di conversione incapsulata e testabile. Ma questo non è flessibile né scalabile quando si hanno numeri elevati, o combinazioni di grandi dimensioni, di proprietà di origine e di destinazione. In questo caso sono disponibili due opzioni:

  • Se si usa {x:Bind} è possibile eseguire il binding direttamente a una funzione per eseguire tale conversione
  • In alternativa, è possibile specificare un convertitore di valori che è un oggetto progettato per eseguire la conversione

Convertitori di valori

Ecco un convertitore di valori, adatto per un binding una tantum o unidirezionale, che converte un valore DateTime in un valore stringa contenente il mese. La classe implementa IValueConverter.

public class DateToStringConverter : IValueConverter
{
    // Define the Convert method to convert a DateTime value to 
    // a month string.
    public object Convert(object value, Type targetType, 
        object parameter, string language)
    {
        // value is the data from the source object.
        DateTime thisdate = (DateTime)value;
        int monthnum = thisdate.Month;
        string month;
        switch (monthnum)
        {
            case 1:
                month = "January";
                break;
            case 2:
                month = "February";
                break;
            default:
                month = "Month not found";
                break;
        }
        // Return the value to pass to the target.
        return month;
    }

    // ConvertBack is not implemented for a OneWay binding.
    public object ConvertBack(object value, Type targetType, 
        object parameter, string language)
    {
        throw new NotImplementedException();
    }
}
// See the "Formatting or converting data values for display" section in the "Data binding overview" topic.

Ecco come utilizzare il convertitore di valori nel markup dell'oggetto di associazione.

<UserControl.Resources>
  <local:DateToStringConverter x:Key="Converter1"/>
</UserControl.Resources>
...
<TextBlock Grid.Column="0" 
  Text="{x:Bind ViewModel.Month, Converter={StaticResource Converter1}}"/>
<TextBlock Grid.Column="0" 
  Text="{Binding Month, Converter={StaticResource Converter1}}"/>

Il motore di associazione chiama i metodi Convert e ConvertBack se il parametro Converter è definito per l'associazione. Quando i dati vengono passati dall'origine, il motore di associazione chiama Convert e passa i dati restituiti alla destinazione. Quando i dati vengono passati dalla destinazione (per un'associazione bidirezionale), il motore di associazione chiama ConvertBack e passa i dati restituiti all'origine.

Il convertitore include anche parametri facoltativi: ConverterLanguage, che consente di specificare la lingua da usare nella conversione e ConverterParameter, che consente di passare un parametro per la logica di conversione. Per un esempio che usa un parametro del convertitore, vedere IValueConverter.

Annotazioni

Se si verifica un errore nella conversione, non generare un'eccezione. Restituisce invece DependencyProperty.UnsetValue, che interromperà il trasferimento dei dati.

Per visualizzare un valore predefinito da utilizzare ogni volta che l'origine dell'associazione non può essere risolta, impostare la proprietà FallbackValue sull'oggetto binding nel markup. Ciò è utile per gestire gli errori di conversione e formattazione. È anche utile eseguire l'associazione alle proprietà di origine che potrebbero non esistere in tutti gli oggetti in una raccolta associata di tipi eterogenei.

Se si associa un controllo di testo a un valore che non è una stringa, il motore di data binding convertirà il valore in una stringa. Se il valore è un tipo riferimento, il motore di data binding recupererà il valore stringa chiamando ICustomPropertyProvider.GetStringRepresentation o IStringable.ToString , se disponibile, e chiamerà altrimenti Object.ToString. Si noti, tuttavia, che il motore di associazione ignorerà qualsiasi implementazione di ToString che nasconde l'implementazione della classe base. Le implementazioni della sottoclasse devono eseguire l'override del metodo ToString della classe base. Analogamente, nei linguaggi nativi, tutti gli oggetti gestiti sembrano implementare ICustomPropertyProvider e IStringable. Tuttavia, tutte le chiamate a GetStringRepresentation e IStringable.ToString vengono instradate a Object.ToString o a un override di tale metodo e non a una nuova implementazione ToString che nasconde l'implementazione della classe base.

Annotazioni

A partire da Windows 10, versione 1607, il framework XAML fornisce un convertitore booleano predefinito per Visibility. Il convertitore esegue il mapping true al valore di enumerazione Visible e false a Collapsed in modo da poter associare una proprietà Visibility a un valore booleano senza creare un convertitore. Per usare il convertitore integrato, la versione minima dell'SDK di destinazione dell'app deve essere 14393 o successiva. Non puoi usarla quando la tua app è destinata a versioni precedenti di Windows 10. Per altre informazioni sulle versioni di destinazione, vedi Codice adattivo della versione.

Associazione di funzioni in {x:Bind}

{x:Bind} consente al passaggio finale di un percorso di associazione di essere una funzione. Può essere usato per eseguire conversioni e per eseguire associazioni che dipendono da più di una proprietà. Vedere Funzioni in x:Bind

Associazione da elemento a elemento

Puoi associare la proprietà di un elemento XAML alla proprietà di un altro elemento XAML. Ecco un esempio dell'aspetto nel markup.

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

Importante

Per il flusso di lavoro necessario per l'associazione da elemento a elemento tramite C++/WinRT, vedere Binding da elemento a elemento.

Dizionari risorse con {x:Bind}

L'estensione di markup {x:Bind} dipende dalla generazione del codice, quindi richiede un file code-behind contenente un costruttore che chiama InitializeComponent (per inizializzare il codice generato). È possibile riutilizzare il dizionario risorse creando un'istanza del relativo tipo (in modo che venga chiamato InitializeComponent ) anziché fare riferimento al nome del file. Ecco un esempio di operazioni da eseguire se si dispone di un dizionario risorse esistente e si vuole usare {x:Bind} in esso.

TemplatesResourceDictionary.xaml

<ResourceDictionary
    x:Class="ExampleNamespace.TemplatesResourceDictionary"
    .....
    xmlns:examplenamespace="using:ExampleNamespace">
    
    <DataTemplate x:Key="EmployeeTemplate" x:DataType="examplenamespace:IEmployee">
        <Grid>
            <TextBlock Text="{x:Bind Name}"/>
        </Grid>
    </DataTemplate>
</ResourceDictionary>

TemplatesResourceDictionary.xaml.cs

using Windows.UI.Xaml.Data;
 
namespace ExampleNamespace
{
    public partial class TemplatesResourceDictionary
    {
        public TemplatesResourceDictionary()
        {
            InitializeComponent();
        }
    }
}

MainPage.xaml

<Page x:Class="ExampleNamespace.MainPage"
    ....
    xmlns:examplenamespace="using:ExampleNamespace">

    <Page.Resources>
        <ResourceDictionary>
            .... 
            <ResourceDictionary.MergedDictionaries>
                <examplenamespace:TemplatesResourceDictionary/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Page.Resources>
</Page>

Combinazione di {x:Bind} e {Binding} in uno stile riutilizzabile

Mentre l'esempio precedente ha mostrato l'uso di {x:Bind} in DataTemplates, è anche possibile creare stili riutilizzabili che combinano sia le estensioni di markup {x:Bind} che {Binding}. Ciò è utile quando si desidera associare alcune proprietà ai valori noti in fase di compilazione usando {x:Bind} e altre proprietà ai valori DataContext di runtime usando {Binding}.

Di seguito è riportato un esempio che illustra come creare uno stile button riutilizzabile che usa entrambi gli approcci di associazione:

TemplatesResourceDictionary.xaml

<ResourceDictionary
    x:Class="ExampleNamespace.TemplatesResourceDictionary"
    .....
    xmlns:examplenamespace="using:ExampleNamespace">
    
    <!-- DataTemplate using x:Bind -->
    <DataTemplate x:Key="EmployeeTemplate" x:DataType="examplenamespace:IEmployee">
        <Grid>
            <TextBlock Text="{x:Bind Name}"/>
        </Grid>
    </DataTemplate>
    
    <!-- Style that mixes x:Bind and Binding -->
    <Style x:Key="CustomButtonStyle" TargetType="Button">
        <Setter Property="Background" Value="{Binding ButtonBackgroundBrush}"/>
        <Setter Property="Foreground" Value="{Binding ButtonForegroundBrush}"/>
        <Setter Property="FontSize" Value="16"/>
        <Setter Property="Margin" Value="4"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border x:Name="RootBorder"
                            Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            CornerRadius="4">
                        <StackPanel Orientation="Horizontal" 
                                    HorizontalAlignment="Center"
                                    VerticalAlignment="Center">
                            <!-- x:Bind to a static property or page-level property -->
                            <Ellipse Width="8" Height="8" 
                                     Fill="{x:Bind DefaultIndicatorBrush}" 
                                     Margin="0,0,8,0"/>
                            <!-- Binding to DataContext -->
                            <ContentPresenter x:Name="ContentPresenter"
                                              Content="{TemplateBinding Content}"
                                              Foreground="{TemplateBinding Foreground}"
                                              FontSize="{TemplateBinding FontSize}"/>
                        </StackPanel>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="PointerOver">
                                    <VisualState.Setters>
                                        <!-- Binding to DataContext for hover color -->
                                        <Setter Target="RootBorder.Background" 
                                                Value="{Binding ButtonHoverBrush}"/>
                                    </VisualState.Setters>
                                </VisualState>
                                <VisualState x:Name="Pressed">
                                    <VisualState.Setters>
                                        <!-- x:Bind to a compile-time known resource -->
                                        <Setter Target="RootBorder.Background" 
                                                Value="{x:Bind DefaultPressedBrush}"/>
                                    </VisualState.Setters>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

TemplatesResourceDictionary.xaml.cs

using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
 
namespace ExampleNamespace
{
    public partial class TemplatesResourceDictionary
    {
        public TemplatesResourceDictionary()
        {
            InitializeComponent();
        }
        
        // Properties for x:Bind - these are compile-time bound
        public SolidColorBrush DefaultIndicatorBrush { get; } = 
            new SolidColorBrush(Colors.Green);
            
        public SolidColorBrush DefaultPressedBrush { get; } = 
            new SolidColorBrush(Colors.DarkGray);
    }
}

Utilizzo in MainPage.xaml con un viewModel che fornisce valori di runtime:

<Page x:Class="ExampleNamespace.MainPage"
    ....
    xmlns:examplenamespace="using:ExampleNamespace">

    <Page.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <examplenamespace:TemplatesResourceDictionary/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Page.Resources>
    
    <Page.DataContext>
        <examplenamespace:ButtonThemeViewModel/>
    </Page.DataContext>

    <StackPanel Margin="20">
        <!-- This button uses the mixed binding style -->
        <Button Content="Save" Style="{StaticResource CustomButtonStyle}"/>
        <Button Content="Cancel" Style="{StaticResource CustomButtonStyle}"/>
    </StackPanel>
</Page>

ButtonThemeViewModel.cs (DataContext che fornisce valori di associazione di runtime):

using System.ComponentModel;
using Windows.UI;
using Windows.UI.Xaml.Media;

namespace ExampleNamespace
{
    public class ButtonThemeViewModel : INotifyPropertyChanged
    {
        private SolidColorBrush _buttonBackgroundBrush = new SolidColorBrush(Colors.LightBlue);
        private SolidColorBrush _buttonForegroundBrush = new SolidColorBrush(Colors.DarkBlue);
        private SolidColorBrush _buttonHoverBrush = new SolidColorBrush(Colors.LightCyan);

        public SolidColorBrush ButtonBackgroundBrush
        {
            get => _buttonBackgroundBrush;
            set
            {
                _buttonBackgroundBrush = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonBackgroundBrush)));
            }
        }

        public SolidColorBrush ButtonForegroundBrush
        {
            get => _buttonForegroundBrush;
            set
            {
                _buttonForegroundBrush = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonForegroundBrush)));
            }
        }

        public SolidColorBrush ButtonHoverBrush
        {
            get => _buttonHoverBrush;
            set
            {
                _buttonHoverBrush = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonHoverBrush)));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

In questo esempio:

  • {Binding} viene usato per le proprietà che dipendono da DataContext (ButtonBackgroundBrush, ButtonForegroundBrush, ButtonHoverBrush)
  • {x:Bind} viene usato per le proprietà note in fase di compilazione e che appartengono al ResourceDictionary stesso (DefaultIndicatorBrush, DefaultPressedBrush)
  • Lo stile è riutilizzabile e può essere applicato a qualsiasi pulsante
  • Il tema di runtime è possibile tramite DataContext, sfruttando al tempo stesso le prestazioni di {x:Bind} per gli elementi statici

Associazione di eventi e ICommand

{x:Bind} supporta una funzionalità denominata associazione di eventi. Con questa funzionalità, è possibile specificare il gestore per un evento usando un'associazione, che è un'opzione aggiuntiva sulla gestione degli eventi con un metodo nel file code-behind. Si supponga di avere una proprietà RootFrame nella classe MainPage .

public sealed partial class MainPage : Page
{
    ...
    public Frame RootFrame { get { return Window.Current.Content as Frame; } }
}

È quindi possibile associare l'evento Click di un pulsante a un metodo sull'oggetto Frame restituito dalla proprietà RootFrame in questo modo. Tieni presente che associamo anche la proprietà IsEnabled del pulsante a un altro membro dello stesso Frame.

<AppBarButton Icon="Forward" IsCompact="True"
IsEnabled="{x:Bind RootFrame.CanGoForward, Mode=OneWay}"
Click="{x:Bind RootFrame.GoForward}"/>

I metodi di overload non possono essere utilizzati per gestire un evento con questa tecnica. Inoltre, se il metodo che gestisce l'evento ha parametri, devono essere tutti assegnabili rispettivamente dai tipi di tutti i parametri dell'evento. In questo caso, Frame.GoForward non è sottoposto a overload e non ha parametri (ma sarebbe ancora valido anche se ha preso due parametri oggetto ). Frame.GoBack è tuttavia sottoposto a overload, quindi non è possibile usare questo metodo con questa tecnica.

La tecnica di associazione di eventi è simile all'implementazione e all'utilizzo dei comandi (un comando è una proprietà che restituisce un oggetto che implementa l'interfaccia ICommand ). Sia {x:Bind} che {Binding} funzionano con i comandi. In modo che non sia necessario implementare più volte il modello di comando, è possibile usare la classe helper DelegateCommand disponibile nell'esempio QuizGame (nella cartella "Common").

Associazione a una raccolta di cartelle o file

È possibile usare le API nello spazio dei nomi Windows.Storage per recuperare i dati di file e cartelle. Tuttavia, i vari metodi GetFilesAsync, GetFoldersAsync e GetItemsAsync non restituiscono valori adatti per l'associazione ai controlli elenco. È invece necessario eseguire l'associazione ai valori restituiti dei metodi GetVirtualizedFilesVector, GetVirtualizedFoldersVector e GetVirtualizedItemsVector della classe FileInformationFactory . L'esempio di codice seguente dell'esempio StorageDataSource e GetVirtualizedFilesVector illustra il modello di utilizzo tipico. Ricordarsi di dichiarare la funzionalità picturesLibrary nel manifesto del pacchetto dell'app e verificare che nella cartella della raccolta immagini siano presenti immagini.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    var library = Windows.Storage.KnownFolders.PicturesLibrary;
    var queryOptions = new Windows.Storage.Search.QueryOptions();
    queryOptions.FolderDepth = Windows.Storage.Search.FolderDepth.Deep;
    queryOptions.IndexerOption = Windows.Storage.Search.IndexerOption.UseIndexerWhenAvailable;

    var fileQuery = library.CreateFileQueryWithOptions(queryOptions);

    var fif = new Windows.Storage.BulkAccess.FileInformationFactory(
        fileQuery,
        Windows.Storage.FileProperties.ThumbnailMode.PicturesView,
        190,
        Windows.Storage.FileProperties.ThumbnailOptions.UseCurrentScale,
        false
        );

    var dataSource = fif.GetVirtualizedFilesVector();
    this.PicturesListView.ItemsSource = dataSource;
}

In genere si userà questo approccio per creare una visualizzazione di sola lettura delle informazioni su file e cartelle. È possibile creare associazioni bidirezionali alle proprietà del file e della cartella, ad esempio per consentire agli utenti di valutare un brano in una visualizzazione musicale. Tuttavia, tutte le modifiche non vengono mantenute finché non si chiama il metodo SavePropertiesAsync appropriato, ad esempio MusicProperties.SavePropertiesAsync. È consigliabile eseguire il commit delle modifiche quando l'elemento perde lo stato attivo perché attiva una reimpostazione della selezione.

Si noti che l'associazione bidirezionale che usa questa tecnica funziona solo con posizioni indicizzate, ad esempio Musica. È possibile determinare se una posizione viene indicizzata chiamando il metodo FolderInformation.GetIndexedStateAsync .

Si noti anche che un vettore virtualizzato può restituire null per alcuni elementi prima di popolarne il valore. Ad esempio, è necessario verificare la presenza di null prima di usare il valore SelectedItem di un controllo elenco associato a un vettore virtualizzato oppure utilizzare SelectedIndex .

Associazione a dati raggruppati in base a una chiave

Se si accetta una raccolta semplice di elementi ,ad esempio libri rappresentati da una classe BookSku , e si raggruppano gli elementi usando una proprietà comune come chiave (ad esempio la proprietà BookSku.AuthorName ), il risultato viene chiamato dati raggruppati. Quando si raggruppano i dati, non è più una raccolta flat. I dati raggruppati sono una raccolta di oggetti gruppo, in cui ogni oggetto gruppo ha

  • una chiave e
  • raccolta di elementi la cui proprietà corrisponde a tale chiave.

Per riprendere l'esempio dei libri, il risultato del raggruppamento dei libri in base al nome dell'autore restituisce una raccolta di gruppi di nomi dell'autore in cui ogni gruppo ha

  • una chiave, che è un nome autore e
  • raccolta di BookSkula cui proprietà AuthorName corrisponde alla chiave del gruppo.

In generale, per visualizzare una raccolta, si associa ItemsSource di un controllo elementi (ad esempio ListView o GridView) direttamente a una proprietà che restituisce un insieme. Se si tratta di una raccolta semplice di elementi, non è necessario eseguire alcuna operazione speciale. Tuttavia, se si tratta di una raccolta di oggetti gruppo (così com'è quando si esegue l'associazione a dati raggruppati), sono necessari i servizi di un oggetto intermedio denominato CollectionViewSource che si trova tra il controllo elementi e l'origine dell'associazione. Si associa CollectionViewSource alla proprietà che restituisce dati raggruppati e si associa il controllo items a CollectionViewSource. Un ulteriore valore aggiunto di collectionViewSource è che tiene traccia dell'elemento corrente, in modo da poter mantenere sincronizzati più di un controllo elementi associandoli tutti allo stesso CollectionViewSource. È anche possibile accedere all'elemento corrente a livello di codice tramite la proprietà ICollectionView.CurrentItem dell'oggetto restituito dalla proprietà CollectionViewSource.View .

Per attivare la funzionalità di raggruppamento di un oggetto CollectionViewSource, impostare IsSourceGrouped su true. La necessità di impostare anche la proprietà ItemsPath dipende esattamente dalla modalità di creazione degli oggetti gruppo. Esistono due modi per creare un oggetto gruppo: il modello "is-a-group" e il modello "has-a-group". Nel modello "is-a-group", l'oggetto gruppo deriva da un tipo di raccolta (ad esempio , List<T>), quindi l'oggetto gruppo è in realtà il gruppo di elementi. Con questo modello non è necessario impostare ItemsPath. Nel modello "has-a-group", l'oggetto gruppo ha una o più proprietà di un tipo di raccolta (ad esempio List<T>), quindi il gruppo "ha un" gruppo di elementi sotto forma di proprietà (o diversi gruppi di elementi sotto forma di diverse proprietà). Con questo modello è necessario impostare ItemsPath sul nome della proprietà che contiene il gruppo di elementi.

L'esempio seguente illustra il modello "has-a-group". La classe page ha una proprietà denominata ViewModel, che restituisce un'istanza del modello di visualizzazione. CollectionViewSource viene associato alla proprietà Authors del modello di visualizzazione (Authors è la raccolta di oggetti gruppo) e specifica anche che è la proprietà Author.BookSkus che contiene gli elementi raggruppati. Infine, GridView è associato a CollectionViewSource e ha lo stile di gruppo definito in modo che possa eseguire il rendering degli elementi in gruppi.

<Page.Resources>
    <CollectionViewSource
    x:Name="AuthorHasACollectionOfBookSku"
    Source="{x:Bind ViewModel.Authors}"
    IsSourceGrouped="true"
    ItemsPath="BookSkus"/>
</Page.Resources>
...
<GridView
ItemsSource="{x:Bind AuthorHasACollectionOfBookSku}" ...>
    <GridView.GroupStyle>
        <GroupStyle
            HeaderTemplate="{StaticResource AuthorGroupHeaderTemplateWide}" ... />
    </GridView.GroupStyle>
</GridView>

È possibile implementare il modello "is-a-group" in uno dei due modi seguenti. Un modo consiste nel creare una classe di gruppo personalizzata. Derivare la classe da List<T> (dove T è il tipo degli elementi). Ad esempio: public class Author : List<BookSku>. Il secondo modo consiste nell'usare un'espressione LINQ per creare dinamicamente oggetti gruppo (e una classe di gruppo) da come i valori delle proprietà degli elementi BookSku . Questo approccio, mantenendo solo un elenco semplice di elementi e raggruppandoli in tempo reale, è tipico di un'app che accede ai dati da un servizio cloud. Si ottiene la flessibilità di raggruppare i libri per autore o per genere (ad esempio) senza bisogno di classi di gruppo speciali, ad esempio Author e Genre.

L'esempio seguente illustra il modello "is-a-group" usando LINQ. Questa volta si raggruppano libri per genere, visualizzati con il nome del genere nelle intestazioni di gruppo. Ciò è indicato dal percorso della proprietà "Key" in riferimento al valore della chiave di gruppo.

using System.Linq;
...
private IOrderedEnumerable<IGrouping<string, BookSku>> genres;

public IOrderedEnumerable<IGrouping<string, BookSku>> Genres
{
    get
    {
        if (this.genres == null)
        {
            this.genres = from book in this.bookSkus
                          group book by book.genre into grp
                          orderby grp.Key
                          select grp;
        }
        return this.genres;
    }
}

Tenere presente che quando si usa {x:Bind} con i modelli di dati è necessario indicare il tipo a cui è associato impostando un valore x:DataType . Se il tipo è generico, non è possibile esprimerlo nel markup, quindi è necessario usare {Binding} nel modello di intestazione dello stile di gruppo.

    <Grid.Resources>
        <CollectionViewSource x:Name="GenreIsACollectionOfBookSku"
        Source="{x:Bind Genres}"
        IsSourceGrouped="true"/>
    </Grid.Resources>
    <GridView ItemsSource="{x:Bind GenreIsACollectionOfBookSku}">
        <GridView.ItemTemplate x:DataType="local:BookTemplate">
            <DataTemplate>
                <TextBlock Text="{x:Bind Title}"/>
            </DataTemplate>
        </GridView.ItemTemplate>
        <GridView.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Key}"/>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </GridView.GroupStyle>
    </GridView>

Un controllo SemanticZoom è un ottimo modo per consentire agli utenti di visualizzare e esplorare i dati raggruppati. L'app di esempio Bookstore2 illustra come usare SemanticZoom. In tale app è possibile visualizzare un elenco di libri raggruppati per autore (visualizzazione ingrandita) o per visualizzare un jump list di autori (visualizzazione ingrandita). La jump list offre una navigazione molto più rapida rispetto allo scorrimento nell'elenco dei libri. Le visualizzazioni ingrandite e ingrandite sono in realtà controlli ListView o GridView associati allo stesso CollectionViewSource.

Illustrazione di semanticzoom

Quando si esegue il binding a dati gerarchici, ad esempio sottocategorie all'interno delle categorie, è possibile scegliere di visualizzare i livelli gerarchici nell'interfaccia utente con una serie di controlli elementi. Una selezione in un controllo elementi determina il contenuto dei controlli elementi successivi. È possibile mantenere sincronizzati gli elenchi associando ogni elenco al proprio CollectionViewSource e associando le istanze CollectionViewSource in una catena. Si tratta di una visualizzazione master/dettagli (o elenco/dettagli). Per altre info, vedi Come eseguire il binding ai dati gerarchici e creare una visualizzazione master/dettagli.

Diagnosi e debug dei problemi di data binding

Il markup di associazione contiene i nomi delle proprietà (e, per C#, a volte campi e metodi). Pertanto, quando si rinomina una proprietà, sarà necessario modificare anche qualsiasi associazione che vi fa riferimento. Dimenticando di eseguire questa operazione, si verifica un tipico esempio di bug di data binding e l'app non verrà compilata o non verrà eseguita correttamente.

Gli oggetti di associazione creati da {x:Bind} e {Binding} sono in gran parte equivalenti a livello funzionale. Ma {x:Bind} contiene informazioni sul tipo per l'origine dell'associazione e genera il codice sorgente in fase di compilazione. Con {x:Bind} si ottiene lo stesso tipo di rilevamento dei problemi che si ottiene con il resto del codice. Ciò include la convalida in fase di compilazione delle espressioni di binding e il debug impostando punti di interruzione nel codice sorgente generato come classe parziale per la pagina. Queste classi sono disponibili nei file nella obj cartella, con nomi come (per C#) <view name>.g.cs). Se si verifica un problema con un'associazione, attivare Interrompi eccezioni non gestite nel debugger di Microsoft Visual Studio. Il debugger interromperà l'esecuzione a quel punto ed è quindi possibile eseguire il debug di ciò che è andato storto. Il codice generato da {x:Bind} segue lo stesso modello per ogni parte del grafico dei nodi di origine dell'associazione ed è possibile usare le informazioni nella finestra Stack di chiamate per determinare la sequenza di chiamate che hanno causato il problema.

{Binding} non dispone di informazioni sul tipo per l'origine dell'associazione. Tuttavia, quando si esegue l'app con il debugger collegato, eventuali errori di associazione vengono visualizzati nella finestra Output in Visual Studio.

Creazione di associazioni nel codice

Nota Questa sezione si applica solo a {Binding}, perché non è possibile creare associazioni {x:Bind} nel codice. Tuttavia, alcuni degli stessi vantaggi di {x:Bind} possono essere ottenuti con DependencyObject.RegisterPropertyChangedCallback, che consente di eseguire la registrazione per le notifiche di modifica in qualsiasi proprietà di dipendenza.

Puoi anche connettere gli elementi dell'interfaccia utente ai dati usando codice procedurale anziché XAML. A tale scopo, creare un nuovo oggetto Binding , impostare le proprietà appropriate, quindi chiamare FrameworkElement.SetBinding o BindingOperations.SetBinding. La creazione di associazioni a livello di codice è utile quando si desidera scegliere i valori delle proprietà di associazione in fase di esecuzione o condividere un'unica associazione tra più controlli. Si noti, tuttavia, che non è possibile modificare i valori delle proprietà di associazione dopo aver chiamato SetBinding.

Nell'esempio seguente viene illustrato come implementare un'associazione nel codice.

<TextBox x:Name="MyTextBox" Text="Text"/>
// Create an instance of the MyColors class 
// that implements INotifyPropertyChanged.
MyColors textcolor = new MyColors();

// Brush1 is set to be a SolidColorBrush with the value Red.
textcolor.Brush1 = new SolidColorBrush(Colors.Red);

// Set the DataContext of the TextBox MyTextBox.
MyTextBox.DataContext = textcolor;

// Create the binding and associate it with the text box.
Binding binding = new Binding() { Path = new PropertyPath("Brush1") };
MyTextBox.SetBinding(TextBox.ForegroundProperty, binding);
' Create an instance of the MyColors class 
' that implements INotifyPropertyChanged. 
Dim textcolor As New MyColors()

' Brush1 is set to be a SolidColorBrush with the value Red. 
textcolor.Brush1 = New SolidColorBrush(Colors.Red)

' Set the DataContext of the TextBox MyTextBox. 
MyTextBox.DataContext = textcolor

' Create the binding and associate it with the text box.
Dim binding As New Binding() With {.Path = New PropertyPath("Brush1")}
MyTextBox.SetBinding(TextBox.ForegroundProperty, binding)

Confronto tra funzionalità {x:Bind} e {Binding}

Caratteristica / Funzionalità {x:Bind} e {Binding} Note
Path è la proprietà predefinita {x:Bind a.b.c}
-
{Binding a.b.c}
Proprietà Path {x:Bind Path=a.b.c}
-
{Binding Path=a.b.c}
In x:Bind il percorso è rooted nella pagina per impostazione predefinita, non datacontext.
Indexer {x:Bind Groups[2].Title}
-
{Binding Groups[2].Title}
Associa all'elemento specificato nell'insieme. Sono supportati solo gli indici basati su numeri interi.
Proprietà associate {x:Bind Button22.(Grid.Row)}
-
{Binding Button22.(Grid.Row)}
Le proprietà associate vengono specificate usando parentesi. Se la proprietà non viene dichiarata in uno spazio dei nomi XAML, anteponerla a uno spazio dei nomi xml, che deve essere mappata a uno spazio dei nomi del codice all'inizio del documento.
Colata {x:Bind groups[0].(data:SampleDataGroup.Title)}
-
Non necessario per {Binding}.
I cast vengono specificati usando parentesi. Se la proprietà non viene dichiarata in uno spazio dei nomi XAML, anteponerla a uno spazio dei nomi xml, che deve essere mappata a uno spazio dei nomi del codice all'inizio del documento.
Convertitore {x:Bind IsShown, Converter={StaticResource BoolToVisibility}}
-
{Binding IsShown, Converter={StaticResource BoolToVisibility}}
I convertitori devono essere dichiarati nella radice di Page/ResourceDictionary o in App.xaml.
ConverterParameter, ConverterLanguage {x:Bind IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr}
-
{Binding IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr}
I convertitori devono essere dichiarati nella radice di Page/ResourceDictionary o in App.xaml.
TargetNullValue {x:Bind Name, TargetNullValue=0}
-
{Binding Name, TargetNullValue=0}
Utilizzato quando la foglia dell'espressione di associazione è Null. Usare virgolette singole per un valore stringa.
FallbackValue {x:Bind Name, FallbackValue='empty'}
-
{Binding Name, FallbackValue='empty'}
Utilizzato quando una parte del percorso per l'associazione (ad eccezione della foglia) è Null.
ElementName {x:Bind slider1.Value}
-
{Binding Value, ElementName=slider1}
Con {x:Bind} si sta associando a un campo; Il percorso è rooted nella pagina per impostazione predefinita, quindi è possibile accedere a qualsiasi elemento denominato tramite il relativo campo.
RelativeSource: Self <Rectangle x:Name="rect1" Width="200" Height="{x:Bind rect1.Width}" ... />
-
<Rectangle Width="200" Height="{Binding Width, RelativeSource={RelativeSource Self}}" ... />
Con {x:Bind}, denominare l'elemento e usarne il nome in Path.
RelativeSource: TemplatedParent Non necessario per {x:Bind}
-
{Binding <path>, RelativeSource={RelativeSource TemplatedParent}}
Con {x:Bind} TargetType in ControlTemplate indica l'associazione all'elemento padre del modello. Per {Binding} l'associazione di modelli regolari può essere usata nei modelli di controllo per la maggior parte degli usi. Usare Tuttavia TemplatedParent dove è necessario usare un convertitore o un'associazione bidirezionale.<
Fonte Non necessario per {x:Bind}
-
<ListView ItemsSource="{Binding Orders, Source={StaticResource MyData}}"/>
Per {x:Bind} è possibile usare direttamente l'elemento denominato, usare una proprietà o un percorso statico.
Mode {x:Bind Name, Mode=OneWay}
-
{Binding Name, Mode=TwoWay}
La modalità può essere OneTime, OneWay o TwoWay. Il valore predefinito di {x:Bind} è OneTime; L'impostazione predefinita di {Binding} è OneWay.
UpdateSourceTrigger {x:Bind Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
-
{Binding UpdateSourceTrigger=PropertyChanged}
UpdateSourceTrigger può essere Default, LostFocus o PropertyChanged. {x:Bind} non supporta UpdateSourceTrigger=Explicit. {x:Bind} usa il comportamento PropertyChanged per tutti i casi tranne TextBox.Text, in cui usa il comportamento LostFocus.