Condividi tramite


Panoramica del data binding di Windows

Il data binding nelle app WinUI consente di connettere in modo efficiente i controlli alle origini dati. Informazioni su come associare un controllo a un singolo elemento o a una raccolta di elementi, il rendering degli elementi di controllo, implementare visualizzazioni dettagli e formattare i dati per la visualizzazione. Per altri dettagli, vedere Data binding in modo approfondito.

Prerequisiti

Questo argomento presuppone che tu sappia come creare un'app WinUI di base con Windows App SDK. Per istruzioni sulla creazione della prima app WinUI, vedi Creare un'app WinUI.

Creare il progetto

Creare un nuovo progetto C# WinUI Blank App, confezionato. Denominarlo "Avvio rapido".

Eseguire l'associazione a un singolo elemento

Ogni associazione è costituita da un target di associazione e un'origine di associazione. In genere, la destinazione è una proprietà di un controllo o di un altro elemento dell'interfaccia utente e l'origine è una proprietà di un'istanza della classe (un modello di dati o un modello di visualizzazione). In questo esempio viene illustrato come associare un controllo a un singolo elemento. La destinazione è la proprietà Text di un TextBlock. L'origine è un'istanza di una classe semplice denominata Recording che rappresenta una registrazione audio. Esaminiamo prima la classe.

Aggiungere una nuova classe al progetto e denominare la classe Recording.

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

Esporre quindi la classe di origine dell'associazione dalla classe che rappresenta la finestra di markup. Aggiungere una proprietà di tipo RecordingViewModel a MainWindow.xaml.cs.

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

L'ultima parte consiste nell'associare una TextBlock alla proprietà ViewModel.DefaultRecording.OneLineSummary.

<Window x:Class="Quickstart.MainWindow" ... >
    <Grid>
        <TextBlock Text="{x:Bind ViewModel.DefaultRecording.OneLineSummary}"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"/>
    </Grid>
</Window>

Ecco il risultato.

Screenshot di un'app WinUI che mostra un TextBlock collegato a un singolo elemento.

Eseguire l'associazione a una raccolta di elementi

Uno scenario comune consiste nell'associare una raccolta di oggetti aziendali. In C# usare la classe T ObservableCollection<> generica per il data binding. Implementa l'interfaccia INotifyCollectionChanged , che fornisce una notifica di modifica ai binding quando gli elementi vengono aggiunti o rimossi. Tuttavia, a causa di un bug noto della modalità di rilascio winUI con .NET 8 e versioni successive, potrebbe essere necessario usare un elenco<T> in alcuni scenari, soprattutto se la raccolta è statica e non cambia dopo l'inizializzazione. Se l'interfaccia utente deve essere aggiornata quando la raccolta cambia in fase di esecuzione, usare ObservableCollection<T>. Se è sufficiente visualizzare solo un set fisso di elementi, List<T> è sufficiente. Inoltre, se si desidera che i controlli associati vengano aggiornati con le modifiche alle proprietà degli oggetti nell'insieme, tali oggetti devono implementare INotifyPropertyChanged. Per altre informazioni, vedere Approfondimento sul Data binding.

Nota

Usando List<T>, è possibile che non si ricevano notifiche di modifica per i cambiamenti nella raccolta. Se è necessario rispondere alle modifiche, è consigliabile usare ObservableCollection<T>. In questo esempio non è necessario rispondere alle modifiche alla raccolta, quindi List<T> è sufficiente.

Nell'esempio seguente viene associato un controllo ListView a una raccolta di Recording oggetti . Aggiungere prima di tutto la raccolta al modello di visualizzazione. Aggiungere questi nuovi membri alla RecordingViewModel classe .

public class RecordingViewModel
{
    ...
    private List<Recording> recordings = new();
    public List<Recording> Recordings{ get{ return recordings; } }
    public RecordingViewModel()
    {
        recordings.Add(new Recording(){ ArtistName = "Johann Sebastian Bach",
            CompositionName = "Mass in B minor", ReleaseDateTime = new DateTime(1748, 7, 8) });
        recordings.Add(new Recording(){ ArtistName = "Ludwig van Beethoven",
            CompositionName = "Third Symphony", ReleaseDateTime = new DateTime(1805, 2, 11) });
        recordings.Add(new Recording(){ ArtistName = "George Frideric Handel",
            CompositionName = "Serse", ReleaseDateTime = new DateTime(1737, 12, 3) });
    }
}

Associare quindi un controllo ListView alla ViewModel.Recordings proprietà .

<Window x:Class="Quickstart.MainWindow" ... >
    <Grid>
        <ListView ItemsSource="{x:Bind ViewModel.Recordings}"
                  HorizontalAlignment="Center"
                  VerticalAlignment="Center"/>
    </Grid>
</Window>

Non hai ancora fornito un modello di dati per la classe Recording, quindi la cosa migliore che il framework dell'interfaccia utente può fare è chiamare ToString per ogni elemento in ListView. L'implementazione predefinita di ToString restituisce il nome del tipo.

Collegamento di una vista elenco 1

Per risolvere questo problema, è possibile eseguire l'override di ToString per restituire il valore di OneLineSummaryoppure fornire un modello di dati. L'opzione modello di dati è una soluzione più comune e flessibile. È possibile specificare un modello di dati utilizzando la proprietà ContentTemplate di un controllo contenuto o la proprietà ItemTemplate di un controllo elementi. Ecco due modi per progettare un modello di dati per Recording insieme a un'illustrazione del risultato.

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

Collegamento di una vista elenco 2

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

Collegamento di una vista elenco 3

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

Aggiungere una visualizzazione dettagli

È possibile scegliere di visualizzare tutti i dettagli degli oggetti Recording in elementi ListView. Ma questo approccio occupa un sacco di spazio. È invece possibile visualizzare solo la quantità sufficiente di dati nell'elemento per identificarlo. Quando l'utente effettua una selezione, puoi visualizzare tutti i dettagli dell'elemento selezionato in una parte separata dell'interfaccia utente nota come visualizzazione dei dettagli. Questa disposizione è nota anche come visualizzazione principale/dettagli o visualizzazione elenco/dettagli.

È possibile implementare questa disposizione in due modi. È possibile associare la visualizzazione dei dettagli alla proprietà SelectedItem di ListView. In alternativa, è possibile usare CollectionViewSource. In questo caso, si associano sia ListView che la visualizzazione dettagli a CollectionViewSource. Questo approccio si occupa dell'elemento attualmente selezionato. Entrambe le tecniche sono illustrate nelle sezioni seguenti e forniscono entrambi gli stessi risultati (illustrati nella figura).

Nota

Finora in questo argomento è stata usata solo l'estensione di markup {x:Bind}. Tuttavia, entrambe le tecniche illustrate nelle sezioni seguenti richiedono l'estensione di markup {Binding} più flessibile (ma meno efficiente).

Prima di tutto, ecco la tecnica SelectedItem. Per un'applicazione C#, l'unica modifica necessaria è il markup.

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

Per la tecnica di CollectionViewSource, aggiungere prima un CollectionViewSource come risorsa del Griddi primo livello.

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

Nota

La classe Window in WinUI non ha una proprietà Resources. È possibile aggiungere il CollectionViewSource all'elemento Grid di primo livello (o ad altro elemento dell'interfaccia utente padre, ad esempio StackPanel). Se stai lavorando all'interno di un Page, puoi aggiungere il CollectionViewSource al Page.Resources.

Modificare quindi i collegamenti in ListView (che non devono più essere denominati) e nella visualizzazione dei dettagli per usare CollectionViewSource. Associando la visualizzazione dei dettagli direttamente all'oggetto CollectionViewSource, si implica che si vuole eseguire l'associazione all'elemento corrente nelle associazioni in cui il percorso non può essere trovato nella raccolta stessa. Non è necessario specificare la proprietà CurrentItem come percorso per l'associazione, anche se è possibile farlo in caso di ambiguità.

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

Ed ecco il risultato identico in ogni caso.

Associare una visualizzazione elenco 4

Formattare o convertire i valori dei dati per la visualizzazione

Il rendering precedente presenta un problema. La ReleaseDateTime proprietà non è solo una data. Si tratta di un valore DateTime. Quindi, viene visualizzato con maggiore precisione di quanto sia necessario. Una soluzione consiste nell'aggiungere una proprietà stringa alla classe Recording che restituisce l'equivalente di ReleaseDateTime.ToString("d"). La denominazione di tale proprietà ReleaseDate indica che restituisce una data e non una data e ora. Denominandolo ReleaseDateAsString indica inoltre che restituisce una stringa.

Una soluzione più flessibile consiste nell'usare un convertitore di valori. Ecco un esempio di come creare un convertitore di valori personalizzato. Aggiungere il codice seguente al file sorgente Recording.cs.

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

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

    // No need to implement converting back on a one-way binding
    public object ConvertBack(object value, Type targetType,
        object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

È ora possibile aggiungere un'istanza di StringFormatter come risorsa e usarla nell'associazione di TextBlock che visualizza la ReleaseDateTime proprietà .

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

Come si può notare, per la flessibilità di formattazione, il markup passa una stringa di formato nel convertitore tramite il parametro del convertitore. Nell'esempio di codice illustrato in questo argomento, il convertitore di valori C# usa tale parametro.

Ecco il risultato.

visualizzare una data con formattazione personalizzata

Differenze tra binding e x:Bind

Quando si usa il data binding nelle app WinUI, è possibile che si verifichino due meccanismi di associazione principali: Binding e x:Bind. Anche se entrambi servono allo scopo di connettere gli elementi dell'interfaccia utente alle origini dati, presentano differenze distinte:

  • x:Bind: offre un controllo in fase di compilazione, migliori prestazioni ed è fortemente tipizzato. È ideale per gli scenari in cui si conosce la struttura dei dati in fase di compilazione.
  • Binding: fornisce la valutazione di runtime ed è più flessibile per gli scenari dinamici, ad esempio quando la struttura dei dati non è nota in fase di compilazione.

Scenari non supportati da x:Bind

Sebbene x:Bind sia potente, non è possibile usarlo in determinati scenari:

  • Strutture di dati dinamiche: se la struttura dei dati non è nota in fase di compilazione, non è possibile usare x:Bind.
  • Associazione da elemento a elemento: x:Bind non supporta l'associazione diretta tra due elementi dell'interfaccia utente.
  • L'associazione a un oggetto DataContext: x:Bind non eredita automaticamente l'oggetto DataContext di un elemento padre.
  • Binding bidirezionali con Mode=TwoWay: Sebbene supportato, x:Bind richiede l'implementazione esplicita di INotifyPropertyChanged per qualsiasi proprietà che si desidera venga aggiornata dall'interfaccia utente quando l'origine cambia, sia che si utilizzi un binding unidirezionale o bidirezionale. La differenza principale con le associazioni bidirezionali è che anche le modifiche passano dall'interfaccia utente all'origine.

Per esempi pratici e una comprensione più approfondita di quando usarli, vedere gli argomenti seguenti: