Introduzione al data binding
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.
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.
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.
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.
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.
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.
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.