Panoramica del data binding (WPF .NET)
Il data binding (o associazione dati) in Windows Presentation Foundation (WPF) offre un modo semplice e coerente per presentare e interagire con i dati nelle app. È possibile associare gli elementi a dati provenienti da tipi diversi di origini dati sotto forma di oggetti .NET e XML. Qualsiasi oggetto ContentControl, ad esempio Button, e qualsiasi oggetto ItemsControl, ad esempio ListBox e ListView, include una funzionalità predefinita per abilitare lo stile flessibile di singoli elementi dati o raccolte di elementi di dati. In cima ai dati è possibile generare visualizzazioni di ordinamento, filtro e raggruppamento.
Il data binding in WPF presenta diversi vantaggi rispetto ai modelli tradizionali, tra cui un'ampia gamma di proprietà che supportano implicitamente questa funzionalità, una rappresentazione dei dati mediante interfaccia utente flessibile e una netta separazione tra logica di business e interfaccia utente.
Questo articolo illustra prima di tutto i concetti fondamentali del data binding di WPF e successivamente passa ad analizzare l'utilizzo della classe Binding e di altre funzionalità di data binding.
Che cos'è il data binding?
Il data binding è il processo tramite cui viene stabilita una connessione tra l'interfaccia utente dell'app e i dati che visualizza. Se l'associazione è impostata correttamente e i dati forniscono le notifiche appropriate, quando il valore dei dati cambia la modifica si riflette automaticamente negli elementi associati ai dati. Il data binding prevede anche che, se una rappresentazione esterna dei dati in un elemento viene modificata, i dati sottostanti possono essere automaticamente aggiornati in modo da riflettere la modifica. Se ad esempio l'utente modifica il valore di un elemento TextBox
, il valore dei dati sottostanti viene automaticamente aggiornato per riflettere tale modifica.
Un utilizzo tipico del data binding consiste nell'inserire dati di configurazione locali o del server in moduli o in altri controlli dell'interfaccia utente. In WPF questo concetto si estende per includere il binding di un'ampia gamma di proprietà a tipi diversi di origini dati. In WPF le proprietà di dipendenza degli elementi possono essere associate a oggetti .NET (inclusi gli oggetti ADO.NET o gli oggetti associati a servizi e proprietà Web) e a dati XML.
Concetti di base del data binding
Indipendentemente dall'elemento che si intende associare e dalla natura dell'origine dati, ogni binding si basa sempre sul modello illustrato nella figura seguente.
Come illustrato nella figura, il data binding funge essenzialmente da ponte tra la destinazione e l'origine del binding. La figura dimostra i concetti fondamentali seguenti relativi al data binding in WPF:
In genere, ogni binding è composto da quattro componenti:
- Un oggetto di destinazione del binding.
- Una proprietà di destinazione.
- Origine dell'associazione.
- Il percorso del valore nell'origine di binding da usare.
Ad esempio, se il contenuto di un oggetto
TextBox
viene associato alla proprietàEmployee.Name
, il binding verrà configurato come nella tabella seguente:Impostazione Valore Destinazione TextBox Proprietà di destinazione Text Oggetto di origine Employee
Percorso del valore dell'oggetto di origine Name
La proprietà di destinazione deve essere una proprietà di dipendenza.
Le proprietà UIElement sono prevalentemente proprietà di dipendenza e la maggior parte delle proprietà di dipendenza, ad eccezione di quelle di sola lettura, supporta il data binding per impostazione predefinita. Solo i tipi derivati da DependencyObject possono definire proprietà di dipendenza. Tutti i tipi di UIElement derivano da
DependencyObject
.Le origini di binding non sono limitate a oggetti .NET personalizzati.
Sebbene non sia illustrato nella figura, è importante notare che l'oggetto di origine del binding non è necessariamente un oggetto .NET personalizzato. Il data binding in WPF supporta i dati sotto forma di oggetti .NET, XML e anche oggetti elemento XAML. Per fornire alcuni esempi, l'origine del binding può essere un oggetto UIElement, qualsiasi oggetto elenco, un oggetto ADO.NET o servizi Web oppure un XmlNode che contiene i dati XML. Per altre informazioni, vedere Panoramica delle origini del binding.
È importante tenere presente che, quando si stabilisce un binding, si associa una destinazione del binding a un'origine del binding. Ad esempio, se si visualizzano alcuni dati XML sottostanti in un oggetto ListBox usando il data binding, si associa ListBox
ai dati XML.
Per stabilire un binding, si usa l'oggetto Binding. Il resto di questo articolo descrive molti concetti associati a Binding
, oltre ad alcune proprietà e modalità di utilizzo dell'oggetto.
Contesto dei dati
Quando il data binding viene dichiarato su elementi XAML, viene risolto esaminando la relativa proprietà DataContext immediata. Il contesto dei dati è in genere l'oggetto di origine del binding per la valutazione del percorso del valore di origine del binding. È possibile sostituire questo comportamento nel binding e impostare un valore specifico dell'oggetto di origine del binding. Se la proprietà DataContext
per l'oggetto che ospita il binding non è impostata, viene selezionata la proprietà DataContext
dell'elemento padre e così via fino alla radice dell'albero degli oggetti XAML. In breve, il contesto dei dati usato per risolvere il binding viene ereditato dall'elemento padre, a meno che non sia impostato in modo esplicito sull'oggetto.
I binding possono essere configurati per essere risolti con un oggetto specifico, invece che tramite il contesto dei dati. Un oggetto di origine viene specificato direttamente quando, ad esempio, si associa il colore primo piano di un oggetto al colore di sfondo di un altro. Il contesto dei dati non è necessario, perché il binding viene risolto direttamente tra questi due oggetti. Viceversa, per i binding non associati a oggetti di origine specifici viene usata la risoluzione tramite contesto dei dati.
Quando la proprietà DataContext
cambia, tutti i binding che potrebbero essere interessati dal contesto dei dati vengono rivalutati.
Direzione del flusso di dati
Come indicato dalla freccia nella figura precedente, il flusso di dati di un binding può andare dalla destinazione all'origine del binding (ad esempio, il valore di origine cambia quando un utente modifica il valore di un oggetto TextBox
) e/o dall'origine alla destinazione del binding (ad esempio, il contenuto di TextBox
viene aggiornato con le modifiche apportate nell'origine del binding) se l'origine del binding fornisce le notifiche appropriate.
È possibile fare in modo che l'app consenta agli utenti di cambiare i dati e di propagarli all'oggetto di origine. oppure è possibile fare in modo che gli utenti non possano aggiornare i dati di origine. È possibile controllare il flusso di dati impostando Binding.Mode.
Questa figura illustra i diversi tipi di flusso di dati:
Il binding OneWay fa sì che le modifiche apportate alla proprietà di origine comportino l'aggiornamento automatico della proprietà di destinazione. Le modifiche apportate alla proprietà di destinazione non vengono tuttavia propagate alla proprietà di origine. Questo tipo di binding è appropriato se il controllo da associare è implicitamente di sola lettura. È ad esempio possibile che venga effettuato un binding a un'origine come un controllo Stock Ticker oppure che la proprietà di destinazione non abbia un'interfaccia di controllo tramite la quale apportare modifiche, come nel caso di un colore di sfondo associato ai dati di una tabella. Se non è necessario monitorare le modifiche della proprietà di destinazione, l'uso della modalità di binding OneWay consente di evitare il sovraccarico della modalità di binding TwoWay.
Il binding TwoWay fa sì che le modifiche apportate alla proprietà di origine o alla proprietà di destinazione comportino l'aggiornamento automatico dell'altra. Questo tipo di binding è appropriato per i moduli modificabili o per altri scenari dell'interfaccia utente completamente interattivi. Per impostazione predefinita, la maggior parte delle proprietà usa il binding OneWay, ma le proprietà di dipendenza (in genere quelle di controlli modificabili dall'utente come TextBox.Text e CheckBox.IsChecked) usano il binding TwoWay.
Un modo programmatico per determinare se una proprietà di dipendenza associa unidirezionale o bidirezionale per impostazione predefinita consiste nel ottenere i metadati della proprietà con DependencyProperty.GetMetadata. Il tipo restituito di questo metodo è PropertyMetadata, che non contiene metadati relativi all'associazione. Tuttavia, se questo tipo può essere sottoposto a cast all'oggetto derivato FrameworkPropertyMetadata, è possibile controllare il valore booleano della FrameworkPropertyMetadata.BindsTwoWayByDefault proprietà. Nell'esempio di codice seguente viene illustrato come ottenere i metadati per la TextBox.Text proprietà :
public static void PrintMetadata() { // Get the metadata for the property PropertyMetadata metadata = TextBox.TextProperty.GetMetadata(typeof(TextBox)); // Check if metadata type is FrameworkPropertyMetadata if (metadata is FrameworkPropertyMetadata frameworkMetadata) { System.Diagnostics.Debug.WriteLine($"TextBox.Text property metadata:"); System.Diagnostics.Debug.WriteLine($" BindsTwoWayByDefault: {frameworkMetadata.BindsTwoWayByDefault}"); System.Diagnostics.Debug.WriteLine($" IsDataBindingAllowed: {frameworkMetadata.IsDataBindingAllowed}"); System.Diagnostics.Debug.WriteLine($" AffectsArrange: {frameworkMetadata.AffectsArrange}"); System.Diagnostics.Debug.WriteLine($" AffectsMeasure: {frameworkMetadata.AffectsMeasure}"); System.Diagnostics.Debug.WriteLine($" AffectsRender: {frameworkMetadata.AffectsRender}"); System.Diagnostics.Debug.WriteLine($" Inherits: {frameworkMetadata.Inherits}"); } /* Displays: * * TextBox.Text property metadata: * BindsTwoWayByDefault: True * IsDataBindingAllowed: True * AffectsArrange: False * AffectsMeasure: False * AffectsRender: False * Inherits: False */ }
Public Shared Sub PrintMetadata() Dim metadata As PropertyMetadata = TextBox.TextProperty.GetMetadata(GetType(TextBox)) Dim frameworkMetadata As FrameworkPropertyMetadata = TryCast(metadata, FrameworkPropertyMetadata) If frameworkMetadata IsNot Nothing Then System.Diagnostics.Debug.WriteLine($"TextBox.Text property metadata:") System.Diagnostics.Debug.WriteLine($" BindsTwoWayByDefault: {frameworkMetadata.BindsTwoWayByDefault}") System.Diagnostics.Debug.WriteLine($" IsDataBindingAllowed: {frameworkMetadata.IsDataBindingAllowed}") System.Diagnostics.Debug.WriteLine($" AffectsArrange: {frameworkMetadata.AffectsArrange}") System.Diagnostics.Debug.WriteLine($" AffectsMeasure: {frameworkMetadata.AffectsMeasure}") System.Diagnostics.Debug.WriteLine($" AffectsRender: {frameworkMetadata.AffectsRender}") System.Diagnostics.Debug.WriteLine($" Inherits: {frameworkMetadata.Inherits}") ' Displays: ' ' TextBox.Text property metadata: ' BindsTwoWayByDefault: True ' IsDataBindingAllowed: True ' AffectsArrange: False ' AffectsMeasure: False ' AffectsRender: False ' Inherits: False End If End Sub
OneWayToSource è l'inverso del binding OneWay; aggiorna la proprietà di origine quando la proprietà di destinazione cambia. Si può usare, ad esempio, nel caso in cui si debba semplicemente rivalutare il valore di origine dall'interfaccia utente.
Un altro tipo di binding non illustrato nella figura è OneTime, che fa sì che la proprietà di origine inizializzi la proprietà di destinazione ma senza propagare le modifiche successive. Se il contesto dei dati o l'oggetto al suo interno cambia, la modifica non si riflette nella proprietà di destinazione. Questo tipo di binding è appropriato per i dati per cui è opportuno usare uno snapshot dello stato corrente o che sono realmente statici. Questo tipo di binding è utile anche se si vuole inizializzare la proprietà di destinazione con un valore ricavato da una proprietà di origine e il contesto dei dati non è noto in anticipo. Questa modalità è essenzialmente una forma più semplice di binding OneWay che offre prestazioni migliori nei casi in cui il valore di origine non cambia.
Per rilevare le modifiche dell'origine (nel caso di binding OneWay e TwoWay), è necessario implementare un meccanismo di notifica appropriato per le modifiche delle proprietà, ad esempio INotifyPropertyChanged. Vedere Procedura: Implementare la notifica di modifiche alle proprietà (.NET Framework) per un esempio di implementazione di INotifyPropertyChanged.
La pagina della proprietà Binding.Mode fornisce altre informazioni sulle modalità di binding e un esempio che illustra come specificare la direzione di un binding.
Eventi che attivano gli aggiornamenti dell'origine
I binding TwoWay o OneWayToSource restano in ascolto delle modifiche apportate nella proprietà di destinazione e le propagano all'origine, ovvero aggiornano l'origine. Può accadere ad esempio che si modifichi il testo di un oggetto TextBox per modificare il valore di origine sottostante.
Il valore di origine viene tuttavia aggiornato durante o dopo aver apportato modifiche al testo e quando il controllo perde lo stato attivo? La proprietà Binding.UpdateSourceTrigger determina cosa attiva l'aggiornamento dell'origine. I punti delle frecce verso destra nella figura seguente illustrano il ruolo della proprietà Binding.UpdateSourceTrigger.
Se il valore di UpdateSourceTrigger
è UpdateSourceTrigger.PropertyChanged, il valore a cui punta la freccia verso destra dei binding TwoWay o OneWayToSource viene aggiornato non appena la proprietà di destinazione cambia. Tuttavia, se il valore di UpdateSourceTrigger
è LostFocus, tale valore viene aggiornato con il nuovo valore solo quando la proprietà di destinazione perde lo stato attivo.
Analogamente alla proprietà Mode, le diverse proprietà di dipendenza hanno valori predefiniti di UpdateSourceTrigger diversi. Il valore predefinito per la maggior parte delle proprietà di dipendenza è PropertyChanged, che fa sì che il valore della proprietà di origine cambi istantaneamente quando il valore della proprietà di destinazione viene cambiato. Le modifiche istantanee sono appropriate per CheckBox e per altri controlli semplici. Nel caso dei campi di testo, tuttavia, l'esecuzione di un aggiornamento a ogni pressione di tasto può comportare un calo di prestazioni nonché negare all'utente la possibilità di tornare indietro e correggere eventuali errori di digitazione prima di confermare il nuovo valore. Ad esempio, per la proprietà TextBox.Text
il valore predefinito di UpdateSourceTrigger
è LostFocus, per cui il valore di origine cambia solo quando il controllo perde lo stato attivo, non quando la proprietà TextBox.Text
cambia. Per informazioni su come trovare il valore predefinito di una proprietà di dipendenza, vedere la pagina della proprietà UpdateSourceTrigger.
La tabella seguente fornisce uno scenario di esempio per ogni valore di UpdateSourceTrigger usando TextBox come esempio.
Valore di UpdateSourceTrigger | Quando il valore di origine viene aggiornato | Scenario di esempio per TextBox |
---|---|---|
LostFocus (valore predefinito di TextBox.Text) |
Quando il controllo TextBox perde lo stato attivo. | Un controllo TextBox associato a una logica di convalida (vedere la sezione Convalida dei dati più avanti). |
PropertyChanged |
Quando si digita nel controllo TextBox. | Controlli TextBox nella finestra di una chat room. |
Explicit |
Quando l'app chiama UpdateSource. | Controlli TextBox in un modulo modificabile (i valori di origine vengono aggiornati solo quando l'utente preme il pulsante di invio). |
Per un esempio, vedere Procedura: Controllare il momento in cui l'origine viene aggiornata dal testo di TextBox (.NET Framework).
Esempio di data binding
Per un esempio di data binding, esaminare l'interfaccia utente dell'app seguente dalla demo di data binding, che visualizza un elenco di elementi dell'asta.
L'app dimostra le funzionalità di data binding seguenti:
Il contenuto di ListBox è associato a una raccolta di oggetti AuctionItem. Un oggetto AuctionItem ha diverse proprietà, tra cui Description, StartPrice, StartDate, Category e SpecialFeatures.
I dati (oggetti AuctionItem) visualizzati nel controllo
ListBox
sono basati su modelli, per cui per ogni articolo vengono visualizzati la descrizione e il prezzo corrente. Il modello viene creato usando un oggetto DataTemplate. L'aspetto di ogni articolo dipende inoltre dal valore di SpecialFeatures dell'oggetto AuctionItem visualizzato. Se il valore di SpecialFeatures dell'oggetto AuctionItem è Color, l'articolo avrà un bordo blu. Se il valore è Highlight, l'articolo sarà dotato di un bordo arancione e di una stella. La sezione Modelli di dati include informazioni sull'applicazione di modelli ai dati.L'utente può raggruppare, filtrare o ordinare i dati usando l'oggetto
CheckBoxes
fornito. Nell'immagine precedente sono selezionate l'opzione Raggruppa per categoria e Ordina per categoria e dataCheckBoxes
. I dati sono raggruppati in base alla categoria del prodotto e i nomi delle categorie seguono l'ordine alfabetico. Benché non risulti evidente dalla figura, gli articoli sono anche ordinati in base alla data di inizio all'interno di ogni categoria. L'ordinamento viene eseguito usando una visualizzazione di raccolta. La sezione Binding alle raccolte descrive le visualizzazioni di raccolta.Quando l'utente seleziona un elemento, l'oggetto ContentControl visualizza i relativi dettagli. L'esperienza in questo caso è detta scenario master-dettagli. La sezione Scenario master-dettagli include informazioni su questo tipo di binding.
Il tipo della proprietà StartDate è DateTime, che restituisce una data con l'ora fino al millisecondo. In questa app è stato usato un convertitore personalizzato per visualizzare una stringa di data più breve. La sezione Conversione dei dati include informazioni sui convertitori.
Quando l'utente fa clic sul pulsante Add Product, viene visualizzato il modulo seguente.
L'utente può modificare i campi del modulo, visualizzare in anteprima l'inserzione del prodotto usando i riquadri di anteprima rapida e dettagliata e quindi selezionare Submit
per aggiungere la nuova inserzione. Le impostazioni di raggruppamento, filtro e ordinamento esistenti verranno applicate alla nuova voce. In questo caso specifico, l'articolo immesso nella figura precedente verrà visualizzato come secondo articolo della categoria Computer.
Non illustrato in questa immagine è la logica di convalida fornita nella dataTextBoxdi inizio . Se l'utente immette una data non valida, ovvero una data in un formato non valido o passata, riceverà una notifica tramite ToolTip e accanto a TextBox verrà visualizzato un punto esclamativo rosso. La sezione Convalida dei dati descrive come creare la logica di convalida.
Prima di approfondire le diverse funzionalità di data binding delineate finora, verranno trattati i concetti fondamentali necessari a comprendere il data binding in WPF.
Creare un'associazione
Per riepilogare alcuni dei concetti illustrati nelle sezioni precedenti, si stabilisce un binding usando l'oggetto Binding e ogni binding è costituito in genere da quattro componenti: una destinazione di binding, una proprietà di destinazione, un'origine di binding e il percorso del valore di origine da usare. Questa sezione illustra come impostare un binding.
Le origini di binding sono associate all'oggetto DataContext attivo per l'elemento. Gli elementi ereditano automaticamente il relativo oggetto DataContext
se non ne è stato definito uno in modo esplicito.
Si consideri l'esempio seguente, in cui l'oggetto origine del binding è una classe denominata MyData definita nello spazio dei nomi SDKSample. A scopo dimostrativo, la classe MyData include una proprietà di tipo stringa denominata ColorName il cui valore è impostato su "Red". L'esempio genera quindi un pulsante con uno sfondo rosso.
<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:SDKSample">
<DockPanel.Resources>
<c:MyData x:Key="myDataSource"/>
</DockPanel.Resources>
<DockPanel.DataContext>
<Binding Source="{StaticResource myDataSource}"/>
</DockPanel.DataContext>
<Button Background="{Binding Path=ColorName}"
Width="150" Height="30">
I am bound to be RED!
</Button>
</DockPanel>
Per altre informazioni sulla sintassi della dichiarazione di binding e per esempi su come impostare un binding nel codice, vedere Panoramica delle dichiarazioni di binding.
Se si applica questo esempio al diagramma di base, la figura risultante sarà simile alla seguente. Questa figura descrive un binding OneWay perché la proprietà Background supporta il binding OneWay per impostazione predefinita.
Ci si potrebbe chiedere perché questo binding funziona anche se la proprietà ColorName è di tipo stringa mentre la proprietà Background è di tipo Brush. Questo binding usa la conversione di tipi predefinita, descritta nella sezione Conversione dei dati.
Specifica dell'origine del binding
Si noti che nell'esempio precedente l'origine del binding viene specificata impostando la proprietà DockPanel.DataContext. L'oggetto Button eredita quindi il valore di DataContext da DockPanel, ovvero il relativo elemento padre. Come già detto, l'oggetto origine del binding è uno dei quattro componenti necessari di un binding. Se quindi non si specifica l'oggetto di origine del binding, questo non viene creato.
Esistono diversi modi per specificare l'oggetto origine del binding. L'uso della proprietà DataContext in un elemento padre è utile in caso di binding di più proprietà alla stessa origine. In alcuni casi, tuttavia, è preferibile specificare l'origine del binding in singole dichiarazioni di binding. Nel caso dell'esempio precedente, invece di usare la proprietà DataContext, è possibile specificare l'origine del binding impostando la proprietà Binding.Source direttamente nella dichiarazione di binding del pulsante, come nell'esempio seguente.
<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:SDKSample">
<DockPanel.Resources>
<c:MyData x:Key="myDataSource"/>
</DockPanel.Resources>
<Button Background="{Binding Source={StaticResource myDataSource}, Path=ColorName}"
Width="150" Height="30">
I am bound to be RED!
</Button>
</DockPanel>
Invece di impostare direttamente la proprietà DataContext su un elemento, ereditando il valore di DataContext da un predecessore (il pulsante nel primo esempio), e di specificare esplicitamente l'origine del binding impostando la proprietà Binding.Source sul binding (il pulsante dell'ultimo esempio), per specificare l'origine del binding è anche possibile usare la proprietà Binding.ElementName o la proprietà Binding.RelativeSource. La proprietà ElementName è utile in caso di binding ad altri elementi nell'app, ad esempio se si usa un dispositivo di scorrimento per regolare la larghezza di un pulsante. La proprietà RelativeSource è utile quando in binding viene specificato in un oggetto ControlTemplate o Style. Per altre informazioni, vedere Panoramica delle origini del binding.
Specifica del percorso del valore
Se l'origine del binding è un oggetto, si usa la proprietà Binding.Path per specificare il valore da usare per il binding. Se si esegue il binding a dati XML, si usa la proprietà Binding.XPath per specificare il valore. In alcuni casi è possibile usare la proprietà Path anche per i dati XML. Per accedere, ad esempio, alla proprietà Name di un XmlNode restituito (in seguito a una query XPath), è consigliabile usare la proprietà Path in aggiunta alla proprietà XPath.
Per altre informazioni, vedere le proprietà Path e XPath.
Si noti che, sebbene sia stato sottolineato che il percorso Path del valore da usare è uno dei quattro componenti necessari di un binding, negli scenari di binding a un intero oggetto il valore da usare corrisponde all'oggetto di origine del binding. In questi casi, è possibile non specificare un oggetto Path. Si consideri l'esempio seguente.
<ListBox ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="true"/>
L'esempio precedente usa la sintassi di binding vuota: {Binding}. In questo caso, ListBox eredita DataContext da un elemento DockPanel padre, non mostrato nell'esempio. Quando il percorso non viene specificato, per impostazione predefinita il binding viene eseguito all'intero oggetto. In altri termini, in questo esempio il percorso è stato tralasciato poiché la proprietà ItemsSource viene associata all'intero oggetto. Per informazioni più dettagliate, vedere la sezione Binding alle raccolte.
Oltre al binding a una raccolta, questo scenario è utile anche in caso di binding a un intero oggetto anziché a una singola proprietà di un oggetto. Si consideri ad esempio un oggetto di origine di tipo String da associare semplicemente alla stringa stessa. Un altro scenario comune riguarda il binding di un elemento a un oggetto con numerose proprietà.
Affinché i dati siano significativi per la proprietà di destinazione associata, può essere necessario applicare logica personalizzata. Tale logica potrebbe essere un convertitore personalizzato se la conversione dei tipi predefinita non esiste. Per informazioni sui convertitori, vedere Conversione dei dati.
Binding e BindingExpression
Prima di approfondire altre funzionalità e modelli di utilizzo del data binding, è utile presentare la classe BindingExpression. Come si è visto nelle sezioni precedenti, Binding è la classe di alto livello per la dichiarazione di un binding. Include diverse proprietà che consentono di specificare le caratteristiche di un binding. Una classe correlata, BindingExpression, è l'oggetto sottostante che mantiene la connessione tra origine e destinazione. Un binding contiene tutte le informazioni condivisibili tra diverse espressioni di binding. BindingExpression è un'espressione di istanza che non può essere condivisa e contiene tutte le informazioni sull'istanza di Binding.
Si consideri l'esempio seguente, dove myDataObject
è un'istanza della classe MyData
, myBinding
è l'oggetto Binding di origine e MyData
è una classe definita che contiene una proprietà di tipo stringa denominata ColorName
. In questo esempio il contenuto di testo di myText
, un'istanza di TextBlock, viene associato a ColorName
.
// Make a new source
var myDataObject = new MyData();
var myBinding = new Binding("ColorName")
{
Source = myDataObject
};
// Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding);
' Make a New source
Dim myDataObject As New MyData
Dim myBinding As New Binding("ColorName")
myBinding.Source = myDataObject
' Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding)
È possibile usare lo stesso oggetto myBinding per creare altri binding. È ad esempio possibile usare l'oggetto myBinding per associare il contenuto di testo di una casella di controllo a ColorName. In questo scenario saranno presenti due istanze di BindingExpression che condividono l'oggetto myBinding.
Un oggetto BindingExpression viene restituito chiamando GetBindingExpression su un oggetto associato a dati. Gli articoli seguenti illustrano alcuni utilizzi della classe BindingExpression:
- Ottenere l'oggetto di binding da una proprietà di destinazione associata (.NET Framework)
- Controllare il momento in cui l'origine viene aggiornata dal testo di TextBox (.NET Framework)
Conversione dati
Nella sezione Creare un binding il pulsante è rosso perché la relativa proprietà Background è associata a una proprietà di tipo stringa con il valore "Red". Questo valore di stringa funziona perché nel tipo Brush è presente un convertitore di tipi per convertirlo in Brush.
Con l'aggiunta di queste informazioni, la figura della sezione Creare un binding sarà come segue.
Cosa succede però se, invece di una proprietà di tipo stringa, l'origine del binding ha una proprietà Color di tipo Color? In tal caso, per fare in modo che il binding funzioni è necessario prima di tutto convertire il valore della proprietà Color in un valore accettabile dalla proprietà Background. È quindi necessario creare un convertitore personalizzato implementando l'interfaccia IValueConverter, come nell'esempio seguente.
[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Color color = (Color)value;
return new SolidColorBrush(color);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
<ValueConversion(GetType(Color), GetType(SolidColorBrush))>
Public Class ColorBrushConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
Dim color As Color = CType(value, Color)
Return New SolidColorBrush(color)
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
Return Nothing
End Function
End Class
Per altre informazioni, vedere IValueConverter.
Il convertitore personalizzato viene ora usato al posto della conversione predefinita e il diagramma si presenta come segue.
Come già detto, le conversioni predefinite possono essere disponibili o meno a seconda dei convertitori presenti nel tipo a cui si esegue il binding. Questo comportamento dipenderà dai convertitori di tipi disponibili nella destinazione. In caso di dubbio, creare un convertitore personalizzato.
D eseguito vengono riportati alcuni scenari tipici in cui è opportuno implementare un convertitore di dati:
I dati devono essere visualizzati in modo diverso, a seconda delle impostazioni cultura. È ad esempio possibile implementare un convertitore di valuta o un convertitore di data/ora del calendario in base alle convenzioni di una specifica cultura.
I dati usati non sono necessariamente destinati a cambiare il valore di testo di una proprietà, ma piuttosto a cambiare qualche altro valore, ad esempio l'origine di un'immagine oppure il colore o lo stile del testo visualizzato. I convertitori possono essere usati in questo caso per convertire il binding di una proprietà considerata poco appropriata, ad esempio il binding di un campo di testo alla proprietà Background della cella di una tabella.
Più controlli o più proprietà dei controlli sono associati agli stessi dati. In questo caso, il binding primario potrebbe semplicemente visualizzare il testo, mentre gli altri binding gestiscono problemi di visualizzazione specifici, usando comunque lo stesso binding come informazione di origine.
Una proprietà di destinazione include una raccolta di binding, definita MultiBinding. Per MultiBinding, si usa un oggetto IMultiValueConverter personalizzato per produrre un valore finale dai valori dei binding. È possibile ad esempio calcolare il colore dai valori rosso, blu e verde, ovvero valori che possono provenire da oggetti origine del binding identici o diversi. Vedere MultiBinding per esempi e informazioni.
Binding alle raccolte
Un oggetto di origine del binding può essere considerato come un singolo oggetto le cui proprietà contengono dati oppure come una raccolta di dati di oggetti polimorfici che spesso vengono raggruppati (ad esempio il risultato di una query su un database). Finora è stato illustrato solo il binding a singoli oggetti. Tuttavia, il binding a una raccolta dati è uno scenario comune. Ad esempio, uno scenario comune consiste nell'usare un oggetto ItemsControl, ad esempio ListBox, ListView o TreeView, per visualizzare una raccolta dati, come nell'app illustrata nella sezione Che cos'è il data binding.
Anche in questo caso è possibile applicare il diagramma di base. Se si associa un oggetto ItemsControl a una raccolta, il diagramma è simile al seguente.
Come illustrato in questo diagramma, per associare un oggetto ItemsControl a un oggetto raccolta, è necessario usare la proprietà ItemsControl.ItemsSource. È possibile paragonare ItemsSource
al contenuto di ItemsControl. Il binding è OneWay perché la proprietà ItemsSource
supporta il binding OneWay
per impostazione predefinita.
Come implementare le raccolte
È possibile eseguire l'enumerazione su qualsiasi raccolta che implementa l'interfaccia IEnumerable. Tuttavia, per configurare binding dinamici in modo che gli inserimenti o le eliminazioni di elementi nella raccolta comportino l'aggiornamento automatico dell'interfaccia utente, la raccolta deve implementare l'interfaccia INotifyCollectionChanged. Questa interfaccia espone un evento che deve essere generato a ogni modifica della raccolta sottostante.
WPF include la classe ObservableCollection<T>, un'implementazione predefinita di una raccolta dati che espone l'interfaccia INotifyCollectionChanged. Per supportare pienamente il trasferimento dei valori dei dati dagli oggetti di origine alle destinazioni, ogni oggetto della raccolta che supporta proprietà associabili deve implementare anche l'interfaccia INotifyPropertyChanged. Per altre informazioni, vedere Panoramica delle origini del binding.
Prima di implementare una raccolta personalizzata, è consigliabile usare ObservableCollection<T> o una delle classi di raccolta esistenti, ad esempio List<T>, Collection<T>, BindingList<T> e così via. Se si desidera implementare una raccolta personalizzata in uno scenario avanzato, è opportuno usare IList, che fornisce una raccolta non generica di oggetti accessibili singolarmente dall'indice, garantendo quindi prestazioni ottimali.
Viste raccolta
Dopo l'associazione di ItemsControl a una raccolta dati, è possibile ordinare, filtrare o raggruppare i dati. A tale scopo si usano le visualizzazioni di raccolta, che sono classi che implementano l'interfaccia ICollectionView.
Che cosa sono le visualizzazione di raccolta?
Una visualizzazione di raccolta rappresenta il livello superiore di una raccolta di origine di binding che consente di esplorare e visualizzare la raccolta di origine in base a query di ordinamento, filtro e raggruppamento, il tutto senza modificare la raccolta di origine sottostante. Una visualizzazione di raccolta mantiene inoltre un puntatore all'elemento corrente nella raccolta. Se la raccolta di origine implementa l'interfaccia INotifyCollectionChanged, le modifiche generate dall'evento CollectionChanged vengono propagate alle visualizzazioni.
Poiché le visualizzazioni non modificano le raccolte di origine sottostanti, ogni raccolta di origine può avere più visualizzazioni associate. Si consideri ad esempio una raccolta di oggetti Task. Grazie alle visualizzazioni è possibile visualizzare gli stessi dati in modi diversi. È possibile ad esempio visualizzare le attività ordinate in base alla priorità nella parte sinistra della pagina e, contemporaneamente nella parte destra, visualizzare le stesse attività raggruppate in base all'area.
Come creare una visualizzazione
Per creare e usare una visualizzazione, è possibile creare direttamente un'istanza dell'oggetto visualizzazione e usare tale istanza come origine del binding. Si consideri ad esempio l'app demo data binding illustrata nella sezione Informazioni sul data binding . L'app viene implementata in modo tale che ListBox non viene associato direttamente alla raccolta dati, ma alla relativa visualizzazione. L'esempio seguente viene estratto dall'app demo data binding. La classe CollectionViewSource è il proxy XAML di una classe che eredita da CollectionView. In questo esempio specifico l'oggetto Source della visualizzazione è associato alla raccolta AuctionItems (di tipo ObservableCollection<T>) dell'oggetto dell'app corrente.
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=AuctionItems}"
x:Key="listingDataView" />
</Window.Resources>
La risorsa listingDataView funge quindi da origine del binding per gli elementi dell'app, ad esempio ListBox.
<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8"
ItemsSource="{Binding Source={StaticResource listingDataView}}" />
Per creare un'altra visualizzazione per la stessa raccolta, è possibile creare una nuova istanza di CollectionViewSource e attribuirle un nome x:Key
diverso.
La tabella seguente illustra i tipi di dati della visualizzazione creati come visualizzazione di raccolta predefinita o in base a CollectionViewSource, a seconda del tipo di raccolta di origine.
Tipo di raccolta di origine | Tipo di visualizzazione di raccolta | Note |
---|---|---|
IEnumerable | Tipo interno basato su CollectionView | Non è possibile raggruppare gli elementi. |
IList | ListCollectionView | Il più veloce. |
IBindingList | BindingListCollectionView |
Utilizzo di una visualizzazione predefinita
Specificare una visualizzazione di raccolta come origine di binding rappresenta un modo per creare e usare una visualizzazione di raccolta. WPF crea inoltre una visualizzazione di raccolta predefinita per ogni raccolta usata come origine di binding. Se si esegue il binding direttamente a una raccolta, WPF esegue il binding alla relativa visualizzazione predefinita. Questa visualizzazione predefinita è condivisa da tutti i binding alla stessa raccolta, per cui una modifica apportata a una visualizzazione predefinita da un controllo associato o dal codice, ad esempio l'ordinamento o una modifica al puntatore dell'elemento corrente illustrati più avanti, si riflette in tutti gli altri binding alla stessa raccolta.
Per ottenere la visualizzazione predefinita, usare il metodo GetDefaultView. Per un esempio, vedere Ottenere la visualizzazione predefinita di una raccolta dati (.NET Framework).
Visualizzazioni di raccolta con ADO.NET DataTable
Per migliorare le prestazioni, le visualizzazioni di raccolta per gli oggetti ADO.NET DataTable o DataView delegano l'ordinamento e il filtro all'oggetto DataView, per cui tali operazioni vengono condivise tra tutte le visualizzazioni di raccolta dell'origine dati. Per consentire a ogni visualizzazione di raccolta di ordinare e filtrare gli elementi in modo indipendente, inizializzare ognuna con il relativo oggetto DataView.
Ordinamento
Come detto in precedenza, le visualizzazioni possono applicare un ordinamento a una raccolta. Poiché esistono nella raccolta sottostante, i dati possono avere o non avere un ordine intrinseco. La visualizzazione della raccolta consente di imporre un ordine o di modificare l'ordine predefinito in base ai criteri di confronto che si forniscono. Trattandosi di una visualizzazione dei dati basata su client, uno scenario comune prevede che l'utente possa ordinare le colonne di dati tabulari in base al valore a cui corrisponde la colonna. Grazie alle visualizzazioni, è possibile applicare questo ordinamento gestito dall'utente senza apportare alcuna modifica alla raccolta sottostante né dover ripetere la query nel contenuto della raccolta. Per un esempio, vedere Ordinare una colonna GridView quando si fa clic su un'intestazione (.NET Framework).
L'esempio seguente illustra la logica di ordinamento dell'oggetto CheckBox "Sort by category and date" dell'interfaccia utente dell'app inclusa nella sezione Che cos'è il data binding.
private void AddSortCheckBox_Checked(object sender, RoutedEventArgs e)
{
// Sort the items first by Category and then by StartDate
listingDataView.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending));
listingDataView.SortDescriptions.Add(new SortDescription("StartDate", ListSortDirection.Ascending));
}
Private Sub AddSortCheckBox_Checked(sender As Object, e As RoutedEventArgs)
' Sort the items first by Category And then by StartDate
listingDataView.SortDescriptions.Add(New SortDescription("Category", ListSortDirection.Ascending))
listingDataView.SortDescriptions.Add(New SortDescription("StartDate", ListSortDirection.Ascending))
End Sub
Filtri
Le visualizzazioni possono anche applicare un filtro a una raccolta, in modo che venga mostrato solo un certo sottoinsieme della raccolta completa. I dati possono essere filtrati in base a una condizione. Come succede, ad esempio, per l'app nella sezione Che cos'è il data binding, l'oggetto CheckBox "Show only bargains" contiene la logica che prevede l'esclusione degli articoli con un costo pari o superiore a 25 dollari. Il codice seguente viene eseguito per impostare ShowOnlyBargainsFilter come gestore dell'evento Filter quando viene selezionato tale oggetto CheckBox.
private void AddFilteringCheckBox_Checked(object sender, RoutedEventArgs e)
{
if (((CheckBox)sender).IsChecked == true)
listingDataView.Filter += ListingDataView_Filter;
else
listingDataView.Filter -= ListingDataView_Filter;
}
Private Sub AddFilteringCheckBox_Checked(sender As Object, e As RoutedEventArgs)
Dim checkBox = DirectCast(sender, CheckBox)
If checkBox.IsChecked = True Then
AddHandler listingDataView.Filter, AddressOf ListingDataView_Filter
Else
RemoveHandler listingDataView.Filter, AddressOf ListingDataView_Filter
End If
End Sub
L'implementazione del gestore dell'evento ShowOnlyBargainsFilter è la seguente.
private void ListingDataView_Filter(object sender, FilterEventArgs e)
{
// Start with everything excluded
e.Accepted = false;
// Only inlcude items with a price less than 25
if (e.Item is AuctionItem product && product.CurrentPrice < 25)
e.Accepted = true;
}
Private Sub ListingDataView_Filter(sender As Object, e As FilterEventArgs)
' Start with everything excluded
e.Accepted = False
Dim product As AuctionItem = TryCast(e.Item, AuctionItem)
If product IsNot Nothing Then
' Only include products with prices lower than 25
If product.CurrentPrice < 25 Then e.Accepted = True
End If
End Sub
Se si usa direttamente una delle classi CollectionView anziché CollectionViewSource, si userà la proprietà Filter per specificare un callback. Per un esempio, vedere Filtrare i dati in una visualizzazione (.NET Framework).
Raggruppamento
Ad eccezione della classe interna che visualizza una raccolta IEnumerable,tutte le visualizzazioni di raccolta supportano la funzionalità di raggruppamento, che consente all'utente di suddividere la raccolta contenuta nella visualizzazione in gruppi logici. Se l'utente fornisce un elenco di gruppi, questi saranno espliciti. Se invece i gruppi vengono generati in modo dinamico in base ai dati, si avranno gruppi impliciti.
L'esempio seguente illustra la logica dell'oggetto CheckBox "Group by category".
// This groups the items in the view by the property "Category"
var groupDescription = new PropertyGroupDescription();
groupDescription.PropertyName = "Category";
listingDataView.GroupDescriptions.Add(groupDescription);
' This groups the items in the view by the property "Category"
Dim groupDescription = New PropertyGroupDescription()
groupDescription.PropertyName = "Category"
listingDataView.GroupDescriptions.Add(groupDescription)
Per un altro esempio di raggruppamento, vedere Raggruppare gli elementi di un controllo ListView che implementa una GridView (.NET Framework).
Puntatori all'elemento corrente
Le visualizzazioni supportano anche la nozione di elemento corrente. In una visualizzazione di raccolta è possibile spostarsi da un oggetto all'altro. Durante l'esplorazione viene spostato un puntatore all'elemento che consente di recuperare l'oggetto presente in quella posizione specifica nella raccolta. Per un esempio, vedere Spostarsi tra gli oggetti nella visualizzazione di una raccolta dati (.NET Framework).
Poiché WPF esegue il binding a una raccolta solo tramite una visualizzazione, che può essere sia una visualizzazione specificata dall'utente che la visualizzazione predefinita della raccolta, tutti i binding alle raccolte contengono un puntatore dell'elemento corrente. Quando si esegue il binding a una visualizzazione, il carattere barra ("/") in un valore di Path
definisce l'elemento corrente della visualizzazione. Nell'esempio seguente il contesto di dati è una visualizzazione di raccolta. La prima riga viene associata alla raccolta. La seconda riga viene associata all'elemento corrente della raccolta. La terza riga viene associata alla proprietà Description
dell'elemento corrente nella raccolta.
<Button Content="{Binding }" />
<Button Content="{Binding Path=/}" />
<Button Content="{Binding Path=/Description}" />
La barra e la sintassi della proprietà possono inoltre essere sovrapposte per attraversare una gerarchia di raccolte. Nell'esempio seguente viene eseguito il binding all'elemento corrente di una raccolta denominata Offices
, che è una proprietà dell'elemento corrente della raccolta di origine.
<Button Content="{Binding /Offices/}" />
Il puntatore dell'elemento corrente può essere influenzato da un'operazione di ordinamento o filtro applicata alla raccolta. L'ordinamento mantiene il puntatore dell'elemento corrente sull'ultimo elemento selezionato, ma la visualizzazione di raccolta viene ristrutturata in base a tale elemento. (Anche se prima l'elemento selezionato si trovava all'inizio dell'elenco, ora potrebbe trovarsi al centro). L'elemento selezionato viene preservato se tale selezione rimane nella visualizzazione dopo l'applicazione del filtro. Il puntatore dell'elemento corrente viene altrimenti impostato sul primo elemento della visualizzazione di raccolta filtrata.
Scenario di binding master-dettagli
La nozione di elemento corrente è utile non solo per l'esplorazione degli elementi in una raccolta, bensì anche per lo scenario di binding master-dettagli. Si consideri nuovamente l'interfaccia utente dell'app presentata nella sezione Che cos'è il data binding. In tale app la selezione all'interno di ListBox determina il contenuto visualizzato in ContentControl. In altri termini, quando si seleziona un elemento ListBox, ContentControl mostra i relativi dettagli.
Per implementare lo scenario master-dettagli, occorre semplicemente avere due o più controlli associati alla stessa visualizzazione. L'esempio seguente della demo di data binding mostra il markup di ListBox e l'oggetto ContentControl visualizzato nell'interfaccia utente dell'app nella sezione What is data binding (Informazioni sul data binding ).
<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8"
ItemsSource="{Binding Source={StaticResource listingDataView}}" />
<ContentControl Name="Detail" Grid.Row="3" Grid.ColumnSpan="3"
Content="{Binding Source={StaticResource listingDataView}}"
ContentTemplate="{StaticResource detailsProductListingTemplate}"
Margin="9,0,0,0"/>
Si noti che entrambi i controlli sono associati alla stessa origine, vale a dire la risorsa statica listingDataView. Vedere a tale proposito la definizione di questa risorsa nella sezione Come creare una visualizzazione. Questa associazione funziona perché quando un oggetto ( ContentControl in questo caso) è associato a una visualizzazione raccolta, viene associato automaticamente all'oggetto CurrentItem della visualizzazione. Gli oggetti CollectionViewSource sincronizzano automaticamente valuta e selezione. Se il controllo elenco non è associato a un oggetto CollectionViewSource come in questo esempio, è necessario impostarne la proprietà IsSynchronizedWithCurrentItem su true
affinché funzioni.
Per altri esempi, vedere Eseguire il binding di una raccolta e visualizzare informazioni in base alla selezione effettuata (.NET Framework) e Usare il modello master-dettagli con dati gerarchici (.NET Framework).
Come si può notare, l'esempio precedente usa un modello. I dati non verrebbero infatti visualizzati come desiderato senza l'utilizzo dei modelli, per la precisione quello usato in modo esplicito da ContentControl e quello usato in modo implicito da ListBox. Le sezione seguente illustra i modelli di dati.
Applicazione di un modello ai dati
Senza l'utilizzo di modelli di dati, l'interfaccia utente dell'app illustrata nella sezione Che cos'è il data binding avrebbe un aspetto simile al seguente:
Come mostrato nell'esempio della sezione precedente, i controlli ListBox e ContentControl vengono entrambi associati all'intero oggetto Collection (o più specificatamente alla visualizzazione sull'oggetto Collection) di AuctionItem. Senza istruzioni specifiche per la visualizzazione della raccolta di dati, l'oggetto ListBox visualizza una rappresentazione in stringa di ogni oggetto della raccolta sottostante e l'oggetto ContentControl visualizza una rappresentazione in stringa dell'oggetto a cui è associato.
Per risolvere il problema, l'app definisce DataTemplates. Come illustrato nell'esempio nella sezione precedente, il controllo ContentControl usa in modo esplicito il modello di dati detailsProductListingTemplate. Il controllo ListBox usa in modo implicito il modello di dati seguente quando vengono visualizzati gli oggetti AuctionItem nella raccolta.
<DataTemplate DataType="{x:Type src:AuctionItem}">
<Border BorderThickness="1" BorderBrush="Gray"
Padding="7" Name="border" Margin="3" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="86"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Polygon Grid.Row="0" Grid.Column="0" Grid.RowSpan="4"
Fill="Yellow" Stroke="Black" StrokeThickness="1"
StrokeLineJoin="Round" Width="20" Height="20"
Stretch="Fill"
Points="9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7"
Visibility="Hidden" Name="star"/>
<TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,8,0"
Name="descriptionTitle"
Style="{StaticResource smallTitleStyle}">Description:</TextBlock>
<TextBlock Name="DescriptionDTDataType" Grid.Row="0" Grid.Column="2"
Text="{Binding Path=Description}"
Style="{StaticResource textStyleTextBlock}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Margin="0,0,8,0"
Name="currentPriceTitle"
Style="{StaticResource smallTitleStyle}">Current Price:</TextBlock>
<StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">
<TextBlock Text="$" Style="{StaticResource textStyleTextBlock}"/>
<TextBlock Name="CurrentPriceDTDataType"
Text="{Binding Path=CurrentPrice}"
Style="{StaticResource textStyleTextBlock}"/>
</StackPanel>
</Grid>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=SpecialFeatures}">
<DataTrigger.Value>
<src:SpecialFeatures>Color</src:SpecialFeatures>
</DataTrigger.Value>
<DataTrigger.Setters>
<Setter Property="BorderBrush" Value="DodgerBlue" TargetName="border" />
<Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
<Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
<Setter Property="BorderThickness" Value="3" TargetName="border" />
<Setter Property="Padding" Value="5" TargetName="border" />
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding Path=SpecialFeatures}">
<DataTrigger.Value>
<src:SpecialFeatures>Highlight</src:SpecialFeatures>
</DataTrigger.Value>
<Setter Property="BorderBrush" Value="Orange" TargetName="border" />
<Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
<Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
<Setter Property="Visibility" Value="Visible" TargetName="star" />
<Setter Property="BorderThickness" Value="3" TargetName="border" />
<Setter Property="Padding" Value="5" TargetName="border" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Con l'utilizzo di questi due oggetti DataTemplate, l'interfaccia utente risultante è quella illustrata nella sezione Che cos'è il data binding. Come si evince dallo screenshot, oltre a consentire di posizionare i dati all'interno dei controlli, gli oggetti DataTemplate permettono di conferire ai dati un aspetto visivo gradevole. Nell'oggetto DataTrigger precedente vengono usati ad esempio oggetti DataTemplate che consentono di visualizzare gli oggetti AuctionItem per cui il valore di SpecialFeatures è impostato su HighLight con un bordo arancione e una stella.
Per altre informazioni sui modelli di dati, vedere Panoramica dei modelli di dati (.NET Framework).
Convalida dei dati
La maggior parte delle app che accettano input dell'utente deve avere una logica di convalida per garantire che l'utente immetta le informazioni previste. I controlli di convalida possono basarsi su tipo, intervallo, formato o altri requisiti specifici dell'app. Questa sezione illustra il funzionamento della convalida dei dati in WPF.
Associazione di regole di convalida con un binding
Il modello di data binding in WPF consente di associare ValidationRules all'oggetto Binding. L'esempio seguente associa un oggetto TextBox a una proprietà denominata StartPrice
e aggiunge un oggetto ExceptionValidationRule alla proprietà Binding.ValidationRules.
<TextBox Name="StartPriceEntryForm" Grid.Row="2"
Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
<TextBox.Text>
<Binding Path="StartPrice" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Un oggetto ValidationRule controlla se il valore di una proprietà è valido. WPF include due tipi di oggetti predefiniti ValidationRule:
Un oggetto ExceptionValidationRule verifica la presenza di eccezioni generate durante l'aggiornamento della proprietà di origine del binding. Nell'esempio precedente
StartPrice
è di tipo intero. Quando l'utente immette un valore che non può essere convertito in un intero, viene generata un'eccezione e il binding viene contrassegnato come non valido. Una sintassi alternativa per impostare in modo esplicito ExceptionValidationRule consiste nell'impostare la proprietà ValidatesOnExceptions sutrue
nell'oggetto Binding o MultiBinding.Un oggetto DataErrorValidationRule verifica la presenza di errori generati da oggetti che implementano l'interfaccia IDataErrorInfo. Per altre informazioni sull'uso di questa regola di convalida, vedere DataErrorValidationRule. Una sintassi alternativa per impostare in modo esplicito DataErrorValidationRule consiste nell'impostare la proprietà ValidatesOnDataErrors su
true
nell'oggetto Binding o MultiBinding.
È anche possibile creare una regola di convalida personalizzata derivando dalla classe ValidationRule e implementando il metodo Validate. L'esempio seguente mostra la regola usata dall'oggetto "Start Date" contenuto in TextBox illustrato nella sezione Che cos'è il data binding.
public class FutureDateRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
// Test if date is valid
if (DateTime.TryParse(value.ToString(), out DateTime date))
{
// Date is not in the future, fail
if (DateTime.Now > date)
return new ValidationResult(false, "Please enter a date in the future.");
}
else
{
// Date is not a valid date, fail
return new ValidationResult(false, "Value is not a valid date.");
}
// Date is valid and in the future, pass
return ValidationResult.ValidResult;
}
}
Public Class FutureDateRule
Inherits ValidationRule
Public Overrides Function Validate(value As Object, cultureInfo As CultureInfo) As ValidationResult
Dim inputDate As Date
' Test if date is valid
If Date.TryParse(value.ToString, inputDate) Then
' Date is not in the future, fail
If Date.Now > inputDate Then
Return New ValidationResult(False, "Please enter a date in the future.")
End If
Else
' // Date Is Not a valid date, fail
Return New ValidationResult(False, "Value is not a valid date.")
End If
' Date is valid and in the future, pass
Return ValidationResult.ValidResult
End Function
End Class
StartDateEntryForm TextBox usa futureDateRule, come illustrato nell'esempio seguente.
<TextBox Name="StartDateEntryForm" Grid.Row="3"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
<TextBox.Text>
<Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged"
Converter="{StaticResource dateConverter}" >
<Binding.ValidationRules>
<src:FutureDateRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Poiché il valore di UpdateSourceTrigger è PropertyChanged, il motore di binding aggiorna il valore di origine a ogni sequenza di tasti, il che significa che controlla anche ogni regola nella raccolta ValidationRules. L'argomento verrà ulteriormente trattato nella sezione Processo di convalida.
Visualizzazione di un feedback
Se l'utente immette un valore non valido, è possibile fornire un feedback relativo all'errore nell'interfaccia utente dell'app. Un modo per fornire tale feedback consiste nell'impostare la proprietà associata Validation.ErrorTemplate su un oggetto ControlTemplate personalizzato. Come illustrato nella sottosezione precedente, StartDateEntryForm TextBox usa un ErrorTemplate oggetto denominato validationTemplate. L'esempio seguente illustra la definizione di validationTemplate.
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
L'elemento AdornedElementPlaceholder specifica dove deve essere posizionato il controllo decorato.
Inoltre, è anche possibile usare un oggetto ToolTip per visualizzare il messaggio di errore. Gli oggetti StartDateEntryForm e TextBox usano entrambi lo stile textStyleTextBox, che crea un oggetto ToolTip che visualizza il messaggio di errore. L'esempio seguente illustra la definizione di textStyleTextBox. La proprietà associata Validation.HasError è true
quando uno o più binding sulle proprietà dell'elemento associato sono in errore.
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333" />
<Setter Property="MaxLength" Value="40" />
<Setter Property="Width" Value="392" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={RelativeSource Self}}" />
</Trigger>
</Style.Triggers>
</Style>
Con l'oggetto personalizzato ErrorTemplate e ToolTip, StartDateEntryFormTextBox ha un aspetto simile al seguente quando si verifica un errore di convalida.
Se all'oggetto Binding sono associate regole di convalida ma non si specifica un oggetto ErrorTemplate nel controllo associato, verrà usato un oggetto ErrorTemplate predefinito per avvisare gli utenti quando si verifica un errore di convalida. L'oggetto ErrorTemplate predefinito è un modello del controllo che definisce un bordo rosso nel livello dello strumento decorativo. Con il valore predefinito ErrorTemplate e ToolTip, l'interfaccia utente di StartPriceEntryFormTextBox è simile alla seguente quando si verifica un errore di convalida.
Per un esempio di come fornire una logica di convalida per tutti i controlli in una finestra di dialogo, vedere la sezione Finestre di dialogo personalizzate contenuta in Panoramica delle finestre di dialogo.
Processo di convalida
La convalida avviene solitamente quando si trasferisce un valore di destinazione alla proprietà di origine del binding. Questo trasferimento si verifica nei binding TwoWay e OneWayToSource. Come già detto, gli eventi che generano un aggiornamento dell'origine dipendono dal valore della proprietà UpdateSourceTrigger, come descritto nella sezione Eventi che attivano gli aggiornamenti dell'origine.
Gli elementi seguenti descrivono il processo di convalida. Se in qualsiasi momento durante questo processo si verifica un errore di convalida o un altro tipo di errore, il processo viene interrotto:
Il motore di binding verifica se sono definiti oggetti ValidationRule personalizzati la cui proprietà ValidationStep è impostata su RawProposedValue per tale Binding, nel qual caso chiama il metodo Validate su ogni oggetto ValidationRule fino a quando per uno di essi non si verifica un errore o fino a quando non vengono completati tutti correttamente.
Il motore di binding chiama quindi il convertitore, se presente.
Se il convertitore riesce, il motore di binding verifica se sono definiti oggetti ValidationRule personalizzati la cui proprietà ValidationStep è impostata su ConvertedProposedValue per tale Binding, nel qual caso chiama il metodo Validate su ogni oggetto ValidationRule la cui proprietà ValidationStep è impostata su ConvertedProposedValuefino a quando per uno di essi non si verifica un errore o fino a quando non vengono completati tutti correttamente.
Il motore di binding imposta la proprietà di origine.
Il motore di binding verifica se sono definiti oggetti ValidationRule personalizzati la cui proprietà ValidationStep è impostata su UpdatedValue per tale Binding, nel qual caso chiama il metodo Validate su ogni oggetto ValidationRule la cui proprietà ValidationStep è impostata su UpdatedValue fino a quando per uno di essi non si verifica un errore o fino a quando non vengono completati tutti correttamente. Se un oggetto DataErrorValidationRule è associato a un binding e la relativa proprietà ValidationStep è impostata sul valore predefinito UpdatedValue, l'oggetto DataErrorValidationRule viene controllato a questo punto. Viene quindi controllato qualsiasi binding per cui ValidatesOnDataErrors è impostato su
true
.Il motore di binding verifica se sono definiti oggetti ValidationRule personalizzati la cui proprietà ValidationStep è impostata su CommittedValue per tale Binding, nel qual caso chiama il metodo Validate su ogni oggetto ValidationRule la cui proprietà ValidationStep è impostata su CommittedValue fino a quando per uno di essi non si verifica un errore o fino a quando non vengono completati tutti correttamente.
Se un oggetto ValidationRule non passa in qualsiasi momento durante questo processo, il motore di binding crea un oggetto ValidationError e lo aggiunge alla raccolta Validation.Errors dell'elemento associato. Prima che il motore di binding esegua gli oggetti ValidationRule in un determinato passaggio, rimuove tutti gli oggetti ValidationError aggiunti alla proprietà associata Validation.Errors dell'elemento associato durante tale passaggio. Ad esempio, se un oggetto ValidationRule la cui proprietà ValidationStep è impostata su UpdatedValue genera un errore, la volta successiva che si verifica il processo di convalida, il motore di binding rimuove tale ValidationError immediatamente prima di chiamare qualsiasi oggetto ValidationRule la cui proprietà ValidationStep è impostata su UpdatedValue.
Se Validation.Errors non è vuoto, la proprietà associata Validation.HasError dell'elemento è impostata su true
. Inoltre, se la proprietà NotifyOnValidationError dell'oggetto Binding è impostata su true
, il motore di binding genera l'evento associato Validation.Error sull'elemento.
Si noti inoltre che il trasferimento di un valore valido in una delle due direzioni (da destinazione a origine o da origine a destinazione) cancella la proprietà associata Validation.Errors.
Se al binding è associato un oggetto ExceptionValidationRule oppure se la proprietà ValidatesOnExceptions è impostata su true
e viene generata un'eccezione quando il motore di binding imposta l'origine, il motore di binding verifica se è presente un oggetto UpdateSourceExceptionFilter. È possibile usare il callback UpdateSourceExceptionFilter per fornire un gestore personalizzato per la gestione delle eccezioni. Se un oggetto UpdateSourceExceptionFilter non è specificato in Binding, il motore di binding crea un oggetto ValidationError con l'eccezione e lo aggiunge alla raccolta Validation.Errors dell'elemento associato.
Meccanismo di debug
È possibile impostare la proprietà associata PresentationTraceSources.TraceLevel su un oggetto correlato al binding per ricevere informazioni sullo stato di un binding specifico.
Vedi anche
.NET Desktop feedback