Introduzione al data binding

Completato

Tech logo of U W P and W P F.

In questa lezione si apprenderà come creare un'app che visualizza l'ora corrente. La lezione introduce i concetti fondamentali del data binding, ottenendo i dati dal codice e visualizzandoli nell'interfaccia utente dell'app ed eseguendo l'aggiornamento per aggiornare la visualizzazione dell'orologio nell'interfaccia utente. Questa lezione definisce così le basi per le attività di data binding più complesse delle lezioni successive. È ora di iniziare.

Tech logo of U W P and W P F. W P F appears dimmed.

1. Creare il progetto

Se non è già in esecuzione, aprire Visual Studio. Creare un nuovo progetto Windows universale in C# usando il modello App vuota (Windows universale). Assegnare al progetto il nome DatabindingSample. Si tratta del progetto che verrà usato per tutto il modulo relativo a interfaccia utente e dati.

Screenshot of the Visual Studio Create a new project dialog box.

Quando fa clic su OK, Visual Studio chiede di immettere le versioni di Windows minima e di destinazione. Si tratta semplicemente di un progetto di esercizio e non si prevede di distribuirlo in computer che eseguono una versione precedente di Windows. Pertanto, è possibile selezionare la versione piú recente di Windowa sia come versione minima sia come versione di destinazione e quindi fare clic su OK

2. Aggiungere il controllo TextBlock per la visualizzazione dell'orologio

Dopo avere inizializzato e caricato completamente il progetto, aprire MainPage.xaml facendo doppio clic su di esso in Esplora soluzioni.

Suggerimento

Se c'è poco spazio sullo schermo, usare l'elenco a discesa nell'angolo in alto a sinistra per simulare una risoluzione dello schermo inferiore per l'editor. Per questo modulo, è consigliabile usare una risoluzione equivalente a Desktop 13,3" (1280x720) Scala 100%, ma è possibile scegliere quella preferita.

Aggiungere la riga seguente tra i tag di apertura e chiusura dell'elemento Grid.

<TextBlock HorizontalAlignment="Right" 
           Margin="10" 
           Text="{x:Bind CurrentTime}" />

Viene creato un nuovo TextBlock nella parte superiore destra della finestra, con un margine di 10 unità dal bordo. Successivamente si affronta il Text della TextBlock.

La parte Text={x:Bind CurrentTime} è la prima che si incontra con il data binding. x:Bind è un'estensione di markup XAML che viene compilata nel codice C# insieme al resto dell'app. In questo caso, connette la proprietà Text di TextBlock alla proprietà CurrentTime. Se si prova a compilare ora il progetto, verrà ricevuto il messaggio di errore seguente:

Errore xamlCompiler WMC1110: percorso di binding 'CurrentTime' non valido: impossibile trovare la proprietà 'CurrentTime' nel tipo 'MainPage'

Ciò indica che nel compilatore manca una proprietà CurrentTime di MainPage. Una volta creata la proprietà, il relativo contenuto viene visualizzato da TextBlock nell'angolo in alto a destra.

Nota

La piattaforma UWP supporta anche un metodo di data binding precedente, simile al seguente: Text={Bind CurrentTime}. Questo metodo precedente funziona in modo diverso rispetto a {x:Bind}. In particolare, non segnala errori in fase di compilazione nel caso di un errore di digitazione. In questo modulo viene analizzata esclusivamente la nuova modalità di binding {x:Bind}, che include il controllo degli errori in fase di compilazione. Tuttavia, poiché {x:Bind} è più recente di {Bind}, vengono aggiunte nuove funzionalità e per questo motivo si è scelto di usare la versione di Windows più recente per la creazione del progetto.

3. Creare la CurrentTime proprietà

Aprire MainPage.xaml.cs e aggiungere la definizione di proprietà seguente alla classe MainPage.

public string CurrentTime => DateTime.Now.ToLongTimeString();

Se non si ha familiarità con la sintassi precedente, si tratta di un membro con corpo di espressione. È stato introdotto in C# 6.0 e rappresenta la sintassi abbreviata per quanto segue:

public string CurrentTime 
{
    get { return DateTime.Now.ToLongTimeString(); }
}

4. Eseguire l'app

Se si avvia l'app ora (usando il tasto F5 o il comando di menu Debug/Avvia debug), l'app viene compilata ed eseguita. E la cosa migliore è che sembra funzionare. L'ora corrente viene visualizzata nell'angolo in alto a destra.

Screenshot of the running app with the clock.

Tuttavia, qualcosa non è corretto perché l'orologio non viene aggiornato. È bloccato sull'ora di avvio dell'app. In che modo l'app può sapere quando aggiornare il valore in TextBlock? È necessario indicare al runtime UWP di eseguire l'aggiornamento una volta al secondo.

5. Specificare le modalità di associazione

I binding {x:Bind} sono ottimizzati per le prestazioni. Ciò significa che non eseguono nessuna operazione che non sia stata richiesta in modo esplicito dallo sviluppatore. Quindi, per impostazione predefinita, un binding {x:Bind} valuta l'origine del binding (in questo caso la proprietà CurrentTime) solo una volta. Questo tipo di associazione viene chiamato OneTime binding. Se si vuole che il framework UWP continui ad aggiornare l'interfaccia utente, è necessario specificare in modo esplicito un'altra modalità di binding: OneWay o TwoWay.

La modalità di binding TwoWay indica un binding bidirezionale tra il codice C# (la logica) e l'interfaccia utente. Questo tipo di associazione sarà utile più avanti quando si associa ai controlli che l'utente può modificare. Ma per un oggetto TextBlock, il binding OneWay è più appropriato, perché le modifiche dei dati avranno origine solo nel codice e mai nell'interfaccia utente.

Per specificare una modalità di binding OneWay per TextBlock, modificare {x:Bind CurrentTime} in {x:Bind CurrentTime, Mode=OneWay}. L'intero tag TextBlock all'interno di Grid avrà ora un aspetto simile a questo markup.

<TextBlock HorizontalAlignment="Right" 
           Margin="10" 
           Text="{x:Bind CurrentTime, Mode=OneWay}" />

Il binding indica al runtime UWP di creare l'infrastruttura necessaria per tenere traccia delle modifiche alla proprietà CurrentTime e riflettere tali modifiche nell'oggetto Text di TextBlock. Questa infrastruttura aggiuntiva usa una piccola quantità di memoria e cicli di CPU e per questo motivo non è l'opzione predefinita.

Se si esegue l'app ora, l'orologio continuerà a non aggiornarsi. È necessario inviare una notifica al sistema per segnalare che la proprietà CurrentTime è stata modificata.

6. Implementare l'interfaccia INotifyPropertyChanged

Questa notifica viene gestita tramite l'interfaccia INotifyPropertyChanged. Si tratta di un'interfaccia semplice con un singolo evento.

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

Qualsiasi classe contenente una semplice proprietà C# come origine del data binding deve implementare l'interfaccia INotifyPropertyChanged. La classe deve inoltre attivare l'evento PropertyChanged quando l'interfaccia utente deve essere aggiornata. Procedere aggiungendo l'interfaccia alla dichiarazione della classe MainPage.

public sealed partial class MainPage : Page, INotifyPropertyChanged

È necessario anche implementare l'interfaccia aggiungendo l'evento PropertyChanged alla classe.

public event PropertyChangedEventHandler PropertyChanged;

7. Richiamare l'evento PropertyChanged ogni secondo

Non resta che richiamare l'evento PropertyChanged ogni volta che si vuole aggiornare l'orologio, ovvero ogni secondo. Per iniziare, si dichiara un DispatcherTimer oggetto nella classe MainPage.

private DispatcherTimer _timer;

Configurarlo quindi nel costruttore (dopo la chiamata di InitializeComponent) in modo che venga attivato ogni secondo.

_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };

_timer.Tick += (sender, o) =>
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));

_timer.Start();

La prima riga qui sopra crea il timer con un intervallo di 1 secondo e l'ultima riga lo avvia. Si esaminerà ora cosa succede quando il timer si attiva (nella seconda riga).

PropertyChanged?.Invoke è una sintassi abbreviata per verificare se un evento è Null e chiamarlo se non lo è. Come per la maggior parte degli eventi, il primo argomento corrisponde al mittente (this). Il secondo argomento dell'evento PropertyChanged è un oggetto PropertyChangedEventArgs appena creato, che contiene un costruttore che accetta una stringa come nome della proprietà. Quindi i sottoscrittori dell'evento PropertyChanged, in questo caso il sistema della piattaforma UWP, ricevono il nome della proprietà aggiornata e possono agire di conseguenza.

Suggerimento

Non usare valori letterali stringa (ad esempio "CurrentTime") per il nome della proprietà. L'uso della stringa stessa è soggetto a errori di digitazione, che possono comportare problemi difficili da individuare in fase di debug quando l'interfaccia utente non viene aggiornata. Anche un'innocente ridenominazione della proprietà può causare errori se le costanti stringa non vengono aggiornate. La procedura consigliata è di usare sempre l'espressione nameof, che è immune da errori di digitazione e si adatta a eventuali ridenominazioni.

L'intero file MainPage.xaml.cs dovrebbe avere l'aspetto seguente:

namespace DatabindingSample
{
    public sealed partial class MainPage : Page, INotifyPropertyChanged
    {
        private DispatcherTimer _timer;

        public MainPage()
        {
            this.InitializeComponent();
            _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };

            _timer.Tick += (sender, o) =>
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));

            _timer.Start();
        }

        public string CurrentTime => DateTime.Now.ToLongTimeString();
        public event PropertyChangedEventHandler PropertyChanged;
    }
}

8. Eseguire l'app

Se si esegue l'app ora, l'orologio si aggiorna. Il primo data binding è stato creato.

9. Riepilogo

Si è appreso come usare {x:Bind} per creare un modo rapido e automatico per passare dati dal codice all'interfaccia utente dell'applicazione per la piattaforma UWP. Questa tecnica viene controllata in fase di compilazione. Si è anche acquisito familiarità con l'interfaccia INotifyPropertyChanged. Questa interfaccia consente all'applicazione di inviare una notifica al framework della piattaforma UWP quando una proprietà con data binding è cambiata ed è necessario aggiornare l'interfaccia utente.

Tech logo of U W P and W P F. U W P appears dimmed.

1. Creare il progetto

Se non è già in esecuzione, aprire Visual Studio. Creare un nuovo progetto WPF C# usando il modello Applicazione WPF. Assegnare al progetto il nome DatabindingSampleWPF e quindi selezionare OK. Si tratta del progetto che verrà usato per tutto il modulo relativo a interfaccia utente e dati.

Screenshot of the Visual Studio Create a new WPF project dialog box.

2. Creare la classe Clock

Poiché l'attività riguarda la visualizzazione dell'ora corrente, è sensato creare prima di tutto una classe Clock. Fare clic con il pulsante destro del mouse sul progetto DatabindingSampleWPF in Esplora soluzioni, scegliere Aggiungi/Classe e immettere Clock come nome della classe.

Copiare il codice seguente nel file appena creato:

using System;

namespace DatabindingSampleWPF
{
    public class Clock
    {
        public string CurrentTime => DateTime.Now.ToLongTimeString();
    }
}

Se non si ha familiarità con la sintassi precedente per la proprietà CurrentTime, si tratta di un membro con corpo di espressione. È stato introdotto in C# 6.0 e rappresenta la sintassi abbreviata per quanto segue:

public string CurrentTime 
{
    get { return DateTime.Now.ToLongTimeString(); }
}

Come si può notare, tutto ciò che la classe Clock contiene in questo momento è una semplice proprietà string che restituisce l'ora corrente nel formato ora estesa. Il passaggio successivo consiste nella visualizzazione dell'ora all'interno dell'app stessa.

3. Aggiungere il controllo TextBlock per la visualizzazione dell'orologio

Se è MainWindow.xaml aperto in Visual Studio, selezionarne la scheda. In caso contrario, è possibile aprirlo facendo doppio clic su di esso nel Esplora soluzioni.

Aggiungere la riga seguente tra i tag di apertura e chiusura dell'elemento Grid.

<TextBlock HorizontalAlignment="Right" 
           VerticalAlignment="Top"
           Margin="10" 
           Text="{Binding CurrentTime}">
    <TextBlock.DataContext>
        <local:Clock/>
    </TextBlock.DataContext>
</TextBlock>

Questo markup creerà un nuovo TextBlock nella parte superiore destra della finestra, con un margine di 10 unità dal bordo.

La parte Text="{Binding CurrentTime}" è la prima che si incontra con il data binding. {Binding} è un'estensione di markup XAML. In questo caso, connette la proprietà Text di TextBlock alla proprietà CurrentTime, ma non è chiaro a quale oggetto appartiene la proprietà CurrentTime in questione.

Un'istanza dell'oggetto a cui il data binding fa riferimento viene creata nell'oggetto DataContext del controllo TextBlock. Pertanto, il codice XAML sopra riportato non solo consente di creare un controllo TextBlock, ma crea anche un'istanza di un oggetto Clock. Inoltre, il codice associa la proprietà Text di TextBlock alla proprietà CurrentTime dell'oggetto Clock creato. La proprietà CurrentTime è detta origine, mentre la proprietà Text è detta destinazione del binding.

4. Eseguire l'app

Se si avvia l'app ora (usando il tasto F5 o il comando di menu Debug/Avvia debug), l'app viene compilata ed eseguita. E la cosa migliore è che sembra funzionare. L'ora corrente viene visualizzata nell'angolo in alto a destra.

Screenshot of the running app with the clock.

Tuttavia, qualcosa non è corretto perché l'orologio non viene aggiornato. È bloccato sull'ora di avvio dell'app. In che modo l'app può sapere quando aggiornare il valore in TextBlock? È necessario indicare al runtime della piattaforma UWP di eseguire l'aggiornamento una volta al secondo.

In altre parole, è necessario inviare una notifica al sistema per segnalare che la proprietà CurrentTime è stata modificata.

5. Implementare l'interfaccia INotifyPropertyChanged

Questa notifica viene gestita tramite l'interfaccia INotifyPropertyChanged. Si tratta di un'interfaccia semplice con un singolo evento.

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

Qualsiasi classe contenente una semplice proprietà C# come origine del data binding deve implementare l'interfaccia INotifyPropertyChanged. La classe deve inoltre attivare l'evento PropertyChanged quando l'interfaccia utente deve essere aggiornata. Procedere aggiungendo l'interfaccia alla dichiarazione della classe Clock.

using System.ComponentModel;

public class Clock : INotifyPropertyChanged
{

È necessario anche implementare l'interfaccia aggiungendo l'evento PropertyChanged alla classe.

public event PropertyChangedEventHandler? PropertyChanged;

6. Richiamare l'evento PropertyChanged ogni secondo

Non resta che richiamare l'evento PropertyChanged ogni volta che si vuole aggiornare l'orologio, ovvero ogni secondo. Per iniziare, si aggiungerà lo spazio dei nomi System.Windows.Threading alle istruzioni using e si dichiarerà un oggetto DispatcherTimer nella classe Clock.

private DispatcherTimer _timer;

Configurarlo quindi nel costruttore, in modo che venga attivato ogni secondo.

public Clock()
{
    _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };

    _timer.Tick += (sender, o) => PropertyChanged?.Invoke(this,
            new PropertyChangedEventArgs(nameof(CurrentTime)));

    _timer.Start();
}

La prima riga nel costruttore crea il timer con un intervallo di 1 secondo e l'ultima riga lo avvia. Si esaminerà ora cosa succede quando il timer si attiva (nella seconda riga).

PropertyChanged?.Invoke è una sintassi abbreviata per verificare se un evento è Null e chiamarlo se non lo è. Come per la maggior parte degli eventi, il primo argomento corrisponde al mittente (this). Il secondo argomento dell'evento PropertyChanged è un oggetto PropertyChangedEventArgs appena creato, che contiene un costruttore che accetta una stringa come nome della proprietà. Quindi i sottoscrittori dell'evento PropertyChanged, in questo caso il sistema WPF, ricevono il nome della proprietà aggiornata e possono agire di conseguenza.

Suggerimento

Non usare valori letterali stringa (ad esempio "CurrentTime") per il nome della proprietà. L'uso della stringa stessa è soggetto a errori di digitazione, che possono comportare problemi difficili da individuare in fase di debug quando l'interfaccia utente non viene aggiornata. Anche un'innocente ridenominazione della proprietà può causare errori se le costanti stringa non vengono aggiornate. La procedura consigliata è di usare sempre l'espressione nameof, che è immune da errori di digitazione e si adatta a eventuali operazioni di ridenominazione.

L’intero Clock.cs dovrebbe avere un aspetto simile al seguente:

namespace DatabindingSampleWPF
{
    using System;
    using System.ComponentModel;
    using System.Windows.Threading;

    public class Clock : INotifyPropertyChanged
    {
        private DispatcherTimer _timer;

        public string CurrentTime => DateTime.Now.ToLongTimeString();

        public event PropertyChangedEventHandler PropertyChanged;

        public Clock()
        {
            // setup _timer to refresh CurrentTime
            _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
            _timer.Tick += (sender, o) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));
            _timer.Start();
        }
    }
}

7. Eseguire l'app

Se si esegue l'app ora, l'orologio si aggiorna. Il primo data binding è stato creato.

8. Riepilogo

Si è appreso come usare {Binding} per creare un modo rapido e automatico per passare dati dal codice all'interfaccia utente dell'applicazione WPF. Si è anche acquisito familiarità con l'interfaccia INotifyPropertyChanged. Questa interfaccia consente all'applicazione di inviare una notifica al framework della piattaforma WPF quando una proprietà con data binding è cambiata ed è necessario aggiornare l'interfaccia utente.