Condividi tramite


Data binding di Windows approfondito

Questo articolo descrive le funzionalità di data binding WinUI usando le API nello spazio dei nomi Microsoft.UI.Xaml.Data.

Annotazioni

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

API importanti

Introduzione

Il data binding è una tecnica che consente all'interfaccia utente dell'app di visualizzare e sincronizzare i dati in modo efficiente. Separando i problemi dei dati dalle problematiche dell'interfaccia utente, semplifica la progettazione delle app, migliora la leggibilità e migliora la gestibilità.

È 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. Questa modalità di associazione viene chiamata 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 in genere si dichiarano entrambi nel markup dell'interfaccia utente. È possibile scegliere di usare l'estensione di markup {x:Bind} o l'estensione di markup {Binding}. Puoi anche usare una combinazione dei due nella stessa app, anche nello stesso elemento dell'interfaccia utente. {x:Bind} è stata una novità della piattaforma UWP 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 UWP che illustrano {x:Bind}

App di esempio UWP che illustrano {Binding}

Ogni associazione implica questi pezzi

  • Un'origine di associazione. Questa sorgente fornisce i dati per l'associazione. Può essere un'istanza di qualsiasi classe con membri i cui valori si desidera visualizzare nell'interfaccia utente.
  • Destinazione di associazione. Questa destinazione è un DependencyProperty di un FrameworkElement nell'interfaccia utente che visualizza i dati.
  • Oggetto di associazione. Questo oggetto 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 viene esaminata più in dettaglio l'origine di associazione, la destinazione di associazione e l'oggetto di associazione. Le sezioni si collegano insieme all'esempio di associazione del contenuto di un pulsante a una proprietà stringa denominata , che appartiene a una classe denominata NextButtonTextHostViewModel.

Origine dell'associazione

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

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

    public string NextButtonText { get; set; }
}

L'implementazione di HostViewModele la relativa proprietà NextButtonTextfunzionano solo per l'associazione monouso. Ma le associazioni unidirezionali e bidirezionali sono estremamente comuni. In questi tipi di binding, l'interfaccia utente viene aggiornata automaticamente in risposta alle modifiche nei valori dei dati dell'origine di associazione. Per il corretto funzionamento di questi tipi di binding, è necessario rendere l'origine dell'associazione osservabile all'oggetto binding. Pertanto, nell'esempio, se si vuole eseguire l'associazione unidirezionale o bidirezionale alla NextButtonText proprietà, tutte le modifiche apportate in fase di esecuzione al valore di tale proprietà devono essere 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. Un FrameworkElement è un'origine di binding valida sin 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 approccio prevede l'implementazione di un singolo evento denominato PropertyChanged. Un esempio di utilizzo HostViewModel è illustrato nel codice seguente.

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

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

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

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

    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        // Raise the PropertyChanged event, passing the name of the property whose value has changed.
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Ora la NextButtonText proprietà è 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 più volte, se si usa C#, è possibile derivare dalla BindableBase classe di base disponibile nell'esempio QuizGame (nella cartella "Common"). Ecco un esempio dell'aspetto.

public class HostViewModel : BindableBase
{
    private string nextButtonText;

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

    public string NextButtonText
    {
        get { return nextButtonText; }
        set { SetProperty(ref nextButtonText, value); }
    }
}

La generazione dell'evento PropertyChanged con un argomento String.Empty o null indica che tutte le proprietà non indicizzate dell'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.

È possibile considerare un'origine di associazione come un singolo oggetto le cui proprietà contengono dati o come raccolta di oggetti. Nel codice C# è possibile associare una sola volta a un oggetto che implementa List<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), eseguire invece un binding unidirezionale a ObservableCollection<T>. Per eseguire l'associazione alle classi di raccolta personalizzate, usare le indicazioni riportate nella tabella seguente.

Scenario C# (CLR) C++/WinRT
Eseguire l'associazione a un oggetto . Può essere qualsiasi oggetto. Può essere qualsiasi oggetto.
Ottenere notifiche di modifica delle proprietà da un oggetto associato. L'oggetto deve implementare INotifyPropertyChanged. L'oggetto deve implementare INotifyPropertyChanged.
Eseguire l'associazione a una raccolta. Elenco<T> IVector di IInspectable o IBindableObservableVector. Vedi Controlli elementi XAML, binding a una raccolta e raccolte C++/WinRTcon C++/WinRT.
Ottenere notifiche di modifica della raccolta da una raccolta associata. ObservableCollection<T> IObservableVector di IInspectable. Ad esempio, winrt::single_threaded_observable_vector<T>.
Implementare una raccolta che supporta l'associazione. Estendere List<T> o implementare IList, IList<Object>, IEnumerable o IEnumerable<Object>. Il collegamento a tipi generici IList<T> e IEnumerable<T> non è supportato. Implementare IVector di IInspectable. Vedi Controlli elementi XAML, binding a una raccolta e raccolte C++/WinRTcon C++/WinRT.
Implementare una raccolta che supporta le notifiche di modifica della raccolta. Estendere ObservableCollection<T> o implementare (non generico) IList e INotifyCollectionChanged. Implementare IObservableVector di IInspectable o IBindableObservableVector.
Implementare una raccolta che supporta il caricamento incrementale. Estendere ObservableCollection<T> o implementare (non generico) IList e INotifyCollectionChanged. Implementare anche ISupportIncrementalLoading. Implementare IObservableVector di IInspectable o IBindableObservableVector. Implementare inoltre 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. Il valore è impostato su un'estensione di markup che dichiara l'oggetto binding. Il primo esempio mostra {x:Bind}e il secondo esempio mostra {Binding}. La dichiarazione di associazioni di dati nel markup è il caso comune perché è pratica, leggibile e compatibile con strumenti. 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 C++/WinRT, devi 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 con Windows App SDK. Senza tale attributo, è necessario implementare le interfacce ICustomPropertyProvider e ICustomProperty per poter usare l'estensione di markup {Binding} .

Oggetto binding dichiarato con {x:Bind}

Prima di creare il markup {x:Bind} , è necessario esporre la classe di origine di binding dalla classe che rappresenta la pagina di markup. Aggiungi una proprietà (di tipo HostViewModel in questo caso) alla classe finestra MainWindow.

namespace DataBindingInDepth
{
    public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
            ViewModel = new HostViewModel();
        }
    
        public HostViewModel ViewModel { get; set; }
    }
}

Dopo aver aggiunto la proprietà, è possibile esaminare più in dettaglio il markup che dichiara l'oggetto di associazione. Nell'esempio seguente viene usata la stessa Button.Content destinazione di associazione visualizzata in precedenza nella sezione "Destinazione di binding". Mostra la destinazione di associazione che è vincolata alla proprietà HostViewModel.NextButtonText.

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

Si noti il valore specificato per Path. La finestra interpreta questo valore nel proprio contesto. In questo caso, il percorso inizia facendo riferimento alla ViewModel proprietà appena aggiunta alla MainWindow pagina. Tale proprietà restituisce un'istanza di HostViewModel, quindi puoi utilizzare la notazione a punto su quell'oggetto per accedere alla proprietà HostViewModel.NextButtonText. Si utilizza Mode per modificare il valore predefinito {x:Bind} di esecuzione 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 HostViewModel.NextButtonText proprietà è osservabile, aggiungere un Click gestore eventi al pulsante e aggiornare il valore di HostViewModel.NextButtonText. Compilare, eseguire e fare clic sul pulsante per visualizzare il valore dell'aggiornamento del Content pulsante.

// MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    ViewModel.NextButtonText = "Updated Next button text";
}

Annotazioni

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

DataTemplate e x:DataType

All'interno di un DataTemplate (indipendentemente dal fatto che venga usato come modello di elemento, modello di contenuto o modello di intestazione), il valore di Path non viene interpretato nel contesto della finestra. Funziona invece nel contesto dell'oggetto dati che si sta templattando. Quando si usa {x:Bind} in un modello di dati, è possibile convalidarne le associazioni in fase di compilazione e generare codice efficiente per tali associazioni. A tale scopo, DataTemplate deve dichiarare il tipo dell'oggetto dati usando x:DataType. L'esempio seguente può essere utilizzato come oggetto ItemTemplate di un controllo di elementi associato a una raccolta di SampleDataGroup oggetti.

<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 supponga di avere un tipo denominato SampleDataGroup che implementa una proprietà stringa denominata Title. È anche disponibile una proprietà MainWindow.SampleDataGroupAsObject di tipo object, ma restituisce effettivamente un'istanza di SampleDataGroup. L'associazione <TextBlock Text="{x:Bind SampleDataGroupAsObject.Title}"/> genera un errore di compilazione perché la Title proprietà non viene trovata nel tipo object. Per correggere questo errore, aggiungere un cast alla sintassi di Path come segue: <TextBlock Text="{x:Bind ((data:SampleDataGroup)SampleDataGroupAsObject).Title}"/>. Ecco un altro esempio in cui Element viene dichiarato come object ma è in realtà un TextBlock: <TextBlock Text="{x:Bind Element.Text}"/>. Un cast risolve il problema: <TextBlock Text="{x:Bind ((TextBlock)Element).Text}"/>.

Se i dati vengono caricati in modo asincrono

Le classi parziali per le finestre generano codice da supportare {x:Bind} in fase di compilazione. È possibile trovare questi file nella obj cartella, con nomi come (per C#) <view name>.g.cs. Il codice generato include un gestore per l'evento Loading della finestra. Tale gestore chiama il Initialize metodo su una classe generata che rappresenta le associazioni della finestra. Initialize chiama Update per avviare lo spostamento dei dati tra l'origine di associazione e la destinazione. Loading viene generato poco prima del passaggio della prima misura della finestra o del controllo utente. Se i dati vengono caricati in modo asincrono, potrebbe non essere pronto al momento Initialize della chiamata. Dopo aver caricato i dati, è possibile forzare l'inizializzazione di associazioni monouso chiamando this.Bindings.Update();. Se sono necessarie solo associazioni monouso per i dati caricati asincronamente, è molto più conveniente inizializzarle in questo modo che avere associazioni unidirezionali e ascoltare le modifiche. Se i dati non subiscono modifiche con granularità fine ed è probabile che vengano aggiornati come parte di un'azione specifica, è possibile effettuare le associazioni una sola volta e forzare un aggiornamento manuale in qualsiasi momento con una chiamata a Update.

Annotazioni

{x:Bind} non è adatto agli scenari legati a late-binding, ad esempio l'esplorazione della struttura del dizionario di un oggetto JSON, né al duck typing. "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'associazione alla Age proprietà sarebbe ugualmente soddisfatta di un Person oggetto o Wine (presupponendo che tali tipi avessero una Age proprietà). Per questi scenari, usare l'estensione di {Binding} markup.

Oggetto di binding dichiarato tramite {Binding}

Se usi C++/WinRT, aggiungi l'attributo BindableAttribute a qualsiasi classe di runtime a cui vuoi eseguire l'associazione quando usi l'estensione di markup {Binding} . Per usare {x:Bind}, non è necessario tale attributo.

// HostViewModel.idl
// Add this attribute:
[Microsoft.UI.Xaml.Data.Bindable]
runtimeclass HostViewModel : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
{
    HostViewModel();
    String NextButtonText;
}

Importante

Se usi C++/WinRT, l'attributo BindableAttribute è disponibile con Windows App SDK. Senza tale attributo, è necessario implementare le interfacce ICustomPropertyProvider e ICustomProperty per poter usare l'estensione di markup {Binding} .

Per impostazione predefinita, {Binding} presuppone che si stia eseguendo il binding al DataContext della finestra di markup. Quindi, imposta l'oggetto DataContext della tua finestra come un'istanza della tua classe di origine dell'associazione (di tipo HostViewModel in questo caso). Nell'esempio seguente viene illustrato il markup che dichiara l'oggetto di associazione. Usa la stessa Button.Content destinazione di binding usata nella sezione "Destinazione di binding" precedente e associa alla proprietà HostViewModel.NextButtonText.

<Window xmlns:viewmodel="using:DataBindingInDepth" ... >
    <Window.DataContext>
        <viewmodel:HostViewModel x:Name="viewModelInDataContext"/>
    </Window.DataContext>
    ...
    <Button Content="{Binding Path=NextButtonText}" ... />
</Window>
// MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    viewModelInDataContext.NextButtonText = "Updated Next button text";
}

Si noti il valore specificato per Path. DataContext della finestra interpreta questo valore, che in questo esempio è impostato su un'istanza di HostViewModel. Il percorso fa riferimento alla HostViewModel.NextButtonText proprietà . Puoi omettere Mode, perché l'impostazione predefinita {Binding} monodirezionale funziona qui.

Il valore predefinito di DataContext per un elemento dell'interfaccia utente è il valore ereditato del relativo elemento padre. È 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 DataContext esplicita di un elemento è utile quando si desidera avere più associazioni che usano la stessa origine.

Un oggetto binding ha una Source proprietà, 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 Source, RelativeSourceo ElementName in modo esplicito 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 seguente può essere usato come oggetto 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 su UpdateSourceTriggerPropertyChanged sull'associazione nel markup. È anche possibile assumere completamente il controllo 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, è quindi necessario convertire i valori da un tipo a un altro. Esistono casi in cui la soluzione corretta consiste nell'esporre un'altra proprietà del tipo corretto dalla classe di origine del binding e mantenere la logica di conversione incapsulata e testabile. Tuttavia, questa soluzione non è flessibile o scalabile quando si hanno numeri elevati o grandi combinazioni di proprietà di origine e di destinazione. In tal 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 string valore 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();
    }
}

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 di associazione non può essere risolta, impostare la FallbackValue proprietà sull'oggetto di associazione 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 ToString implementazione che nasconde l'implementazione della classe base. Le implementazioni della sottoclasse devono invece eseguire l'override del metodo della classe ToString 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 o a Object.ToString un override di tale metodo e non a una nuova ToString implementazione che nasconde l'implementazione della classe base.

Annotazioni

Windows Community Toolkit fornisce un oggetto BoolToVisibilityConverter. Il convertitore esegue il mapping true al valore di Visible enumerazione e false a Collapsed in modo da poter associare una Visibility proprietà a un valore booleano senza creare un convertitore. Per usare il convertitore, il progetto deve aggiungere il pacchetto NuGet CommunityToolkit.WinUI.Converters .

Associazione di funzioni in {x:Bind}

{x:Bind} consente al passaggio finale di un percorso di associazione di essere una funzione. Utilizzare questa funzionalità per eseguire conversioni o per creare associazioni che dipendono da più proprietà. Per altre informazioni, 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 di come appare l'associazione nel markup.

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

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). Per riutilizzare il dizionario di risorse, istanziare il suo tipo (così che InitializeComponent venga chiamato) anziché riferirsi al nome del file. Di seguito è riportato un esempio di operazioni da eseguire se si dispone di un dizionario risorse esistente e si vuole usarlo {x:Bind} .

<!-- 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 Microsoft.UI.Xaml.Data;
 
namespace ExampleNamespace
{
    public partial class TemplatesResourceDictionary
    {
        public TemplatesResourceDictionary()
        {
            InitializeComponent();
        }
    }
}
<!-- MainWindow.xaml -->
<Window x:Class="ExampleNamespace.MainWindow"
    ....
    xmlns:examplenamespace="using:ExampleNamespace">

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

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

Nell'esempio precedente è stato illustrato come usare {x:Bind} in DataTemplates. È anche possibile creare stili riutilizzabili che combinano estensioni di {x:Bind} markup e {Binding} . Questa combinazione è utile quando si desidera associare alcune proprietà ai valori noti in fase di compilazione usando {x:Bind} e altre proprietà per eseguire il runtime dei valori DataContext usando {Binding}.

L'esempio seguente illustra come creare uno stile Button riutilizzabile che usa entrambi gli approcci di associazione:

TemplatesResourceDictionary.xaml

<!-- 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

// TemplatesResourceDictionary.xaml.cs
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using Microsoft.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 MainWindow.xaml con un viewModel che fornisce valori di runtime:

<!-- MainWindow.xaml -->
<Window x:Class="ExampleNamespace.MainWindow"
    ....
    xmlns:examplenamespace="using:ExampleNamespace">

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

    <Grid>
        <Grid.DataContext>
            <examplenamespace:ButtonThemeViewModel/>
        </Grid.DataContext>
        
        <StackPanel Margin="20">
            <!-- These buttons use the mixed binding style -->
            <Button Content="Save" Style="{StaticResource CustomButtonStyle}"/>
            <Button Content="Cancel" Style="{StaticResource CustomButtonStyle}"/>
        </StackPanel>
    </Grid>
</Window>

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

using System.ComponentModel;
using Microsoft.UI;
using Microsoft.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 ed è possibile applicarlo 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. Questa funzionalità è un'opzione aggiuntiva per la gestione degli eventi, oltre alla gestione degli eventi con un metodo nel file code-behind. Si supponga di avere un ListViewDoubleTapped gestore eventi nella MainWindow classe .

public sealed partial class MainWindow : Window
{
    ...
    public void ListViewDoubleTapped()
    {
        // Handle double-tapped logic
    }
}

È possibile associare un evento DoubleTapped di ListView a un metodo in MainWindow in questo modo.

<ListView DoubleTapped="{x:Bind ListViewDoubleTapped}" />

Non è possibile usare metodi sovraccaricati per gestire un evento con questa tecnica. Inoltre, se il metodo che gestisce l'evento ha parametri, tutti devono essere assegnati rispettivamente dai tipi di tutti i parametri dell'evento. In questo caso, ListViewDoubleTapped non è sottoposto a overload e non ha parametri (ma sarebbe ancora valido anche se ha preso due object parametri).

La tecnica di associazione di eventi è simile all'implementazione e al consumo di 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 DelegateCommand classe helper disponibile nell'esempio UWP QuizGame (nella cartella "Common").

Associazione a una raccolta di cartelle o file

Puoi usare le API nello spazio dei nomi Windows.Storage per recuperare i dati di cartelle e file nelle app di Windows App SDK in pacchetto. Tuttavia, i vari GetFilesAsyncmetodi , GetFoldersAsynce 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 UWP 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 usa 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 appropriato SavePropertiesAsync , ad esempio MusicProperties.SavePropertiesAsync. È consigliabile applicare le modifiche quando l'elemento perde il focus perché questa azione causa un ripristino 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 cercare 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 flat di elementi (libri, ad esempio, rappresentati da una BookSku classe) e si raggruppano gli elementi usando una proprietà comune come chiave (ad esempio la BookSku.AuthorName proprietà ), 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 comporta una raccolta di gruppi di nomi dell'autore in cui ogni gruppo ha:

  • una chiave, che è un nome autore e
  • insieme degli oggetti la BookSku cui AuthorName proprietà 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 di associazione. L'oggetto viene CollectionViewSource associato 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 group 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.

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

<Window.Resources>
    <CollectionViewSource
    x:Name="AuthorHasACollectionOfBookSku"
    Source="{x:Bind ViewModel.Authors}"
    IsSourceGrouped="true"
    ItemsPath="BookSkus"/>
</Window.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.

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

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

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

Tenere presente che quando si usa {x:Bind} con i modelli di dati, è necessario indicare il tipo a cui è associato impostando un x:DataType valore. 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 UWP 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 ingrandita e ingrandita sono effettivamente ListView o GridView controlli associati allo stesso CollectionViewSourceoggetto .

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 CollectionViewSource istanze in una catena. Questa configurazione è denominata visualizzazione principale/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à, è necessario modificare anche qualsiasi associazione che vi faccia riferimento. Se si dimentica di farlo, si crea un bug di data binding e l'app non viene compilata o non viene 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. Tale rilevamento include la convalida in fase di compilazione delle espressioni di associazione e il debug impostando punti di interruzione nel codice sorgente generato come classe parziale per la pagina. È possibile trovare queste classi nei file nella obj cartella, con nomi come (per C#) <view name>.g.cs). Se si verifica un problema con un'associazione automatica, attivare Interrompere in caso di eccezioni non gestite nel debugger di Microsoft Visual Studio. Il debugger interrompe 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 esegui l'app con il debugger collegato, eventuali errori di associazione vengono visualizzati nelle finestre Output e Errori di binding XAML in Visual Studio. Per altre informazioni sul debug degli errori di associazione in Visual Studio, vedere Diagnostica del data binding XAML.

Creazione di associazioni nel codice

Annotazioni

Questa sezione si applica solo a {Binding}, perché non è possibile creare associazioni {x:Bind} nel codice. Tuttavia, è possibile ottenere alcuni degli stessi vantaggi di {x:Bind} 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. Tuttavia, 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.
var 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.
var binding = new Binding { 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}
x:Bind In Pathè rooted nella finestra 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 è dichiarata in uno spazio dei nomi XAML, anteporrla 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 è dichiarata in uno spazio dei nomi XAML, anteporrla 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}}
Dichiara i convertitori nella radice di Window, Control, 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}
Dichiara i convertitori nella radice di Window, Control, 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} viene effettuato l'associazione a un campo; Path è radicato nella finestra per impostazione predefinita, in modo da poter accedere a qualsiasi elemento denominato tramite il relativo campo.
RelativeSource: Sè Stesso <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 su 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. TemplatedParent Usare tuttavia 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} poter usare direttamente l'elemento denominato, usare una proprietà o un percorso statico.
Mode {x:Bind Name, Mode=OneWay}
-
{Binding Name, Mode=TwoWay}
Mode può essere OneTime, OneWayo TwoWay. {x:Bind} il valore predefinito è OneTime. {Binding} Il valore predefinito è OneWay.
UpdateSourceTrigger {x:Bind Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
-
{Binding UpdateSourceTrigger=PropertyChanged}
UpdateSourceTrigger può essere Default, LostFocuso PropertyChanged. {x:Bind} non supporta UpdateSourceTrigger=Explicit. {x:Bind} usa il PropertyChanged comportamento per tutti i casi tranne TextBox.Text, in cui usa LostFocus il comportamento.

Vedere anche