Panoramica del data binding in WPF
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. Gli elementi possono essere associati ai dati di un'ampia gamma 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.
La funzionalità di data binding in WPF offre diversi vantaggi rispetto ai modelli tradizionali, tra cui il supporto intrinseco per il data binding da un'ampia gamma di proprietà, la rappresentazione flessibile dell'interfaccia utente dei dati e la separazione pulita della logica di business dall'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 viene ampliato per includere l'associazione di un'ampia gamma di proprietà a un'ampia gamma 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.
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 proprietà quali Description, StartPrice, StartDate, Category, SpecialFeatures e così via.
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 data TextBoxdi 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.
Concetti di base del data binding
Indipendentemente dall'elemento di associazione e dalla natura dell'origine dati, ogni binding segue sempre il 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 si desidera associare il contenuto di un
TextBox
oggetto allaEmployee.Name
proprietà , l'oggetto di destinazione èTextBox
, la proprietà di destinazione è la Text proprietà , il valore da utilizzare è Name e l'oggetto di origine è l'oggetto Employee .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 le proprietà di dipendenza e tutti i UIElement tipi derivano da
DependencyObject
.Anche se non illustrato nella figura, è necessario notare che l'oggetto di origine dell'associazione non è limitato all'essere un oggetto .NET personalizzato. Il data binding WPF supporta i dati sotto forma di oggetti .NET e XML. 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 ricordare che quando si stabilisce un'associazione, si sta associando una destinazione di associazione a un'origine di associazione. Ad esempio, se vengono visualizzati alcuni dati XML sottostanti in un ListBox oggetto utilizzando il data binding, si sta associando 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.
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 delle proprietà di destinazione, l'uso della modalità di associazione 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 OneWay , la maggior parte delle proprietà è binding, ma alcune proprietà di dipendenza (in genere proprietà di controlli modificabili dall'utente, ad TextBox.Text esempio e CheckBox.IsChecked) per TwoWay impostazione predefinita. Per determinare in modo programmatico se una proprietà di dipendenza usa il binding unidirezionale o bidirezionale per impostazione predefinita, è possibile ottenere i metadati della proprietà con DependencyProperty.GetMetadata e quindi controllare il valore booleano della proprietà FrameworkPropertyMetadata.BindsTwoWayByDefault.
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.
Non illustrato nella figura è OneTime l'associazione, che fa sì che la proprietà di origine inizializzi la proprietà di destinazione, ma non propaga 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 associazione che offre prestazioni migliori nei casi in cui il valore di OneWay 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 modifica delle proprietà per un esempio di implementazione 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.
Tuttavia, il valore di origine viene aggiornato durante la modifica del testo o dopo aver completato la modifica del testo e 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, mentre la proprietà TextBox.Text
ha il valore predefinito LostFocus. PropertyChanged
indica che gli aggiornamenti di origine vengono in genere eseguiti ogni volta che la proprietà di destinazione cambia. Le modifiche istantanee sono appropriate per CheckBox e 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.
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 (aggiorna i valori di origine solo quando l'utente fa clic sul pulsante di invio). |
Per un esempio, vedere Procedura: Controllare quando il testo TextBox aggiorna l'origine.
Creazione di 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.
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 associazione ed esempi di come configurare un'associazione nel codice, vedere Cenni preliminari sulle 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 origine del binding, questa non viene creata.
Esistono diversi modi per specificare l'oggetto origine del binding. L'uso della DataContext proprietà su un elemento padre è utile quando si associano 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 ElementName proprietà è utile quando si associa ad altri elementi dell'app, ad esempio quando 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 Procedura: Specificare l'origine di associazione.
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 ai dati XML, utilizzare la Binding.XPath proprietà 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, è applicabile a 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 si esegue un binding 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. La logica personalizzata può essere sotto forma di convertitore personalizzato se la conversione del tipo predefinito non esiste. Per informazioni sui convertitori, vedere Conversione dei dati.
Binding e BindingExpression
Prima di accedere ad altre funzionalità e utilizzi del data binding, è utile introdurre la BindingExpression classe . 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:
Conversione dati
Nella sezione Creazione di un'associazione il pulsante è rosso perché la relativa Background proprietà è associata a una proprietà stringa con il valore "Red". Questo valore di stringa funziona perché nel tipo Brush è presente un convertitore di tipi per convertirlo in Brush.
L'aggiunta di queste informazioni alla figura nella sezione Creazione di un'associazione è simile alla seguente.
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 devono necessariamente modificare il valore di testo di una proprietà quanto piuttosto altri valori, 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.
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 al quale 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.
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 CollectionView classi anziché CollectionViewSource, si userà la Filter proprietà per specificare un callback. Per un esempio, vedere Procedura: Filtrare i dati in una visualizzazione.
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 Procedura: Raggruppare gli elementi di un controllo ListView che implementa una GridView.
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 lo spostamento viene spostato un puntatore dell'elemento che consente di recuperare l'oggetto presente in un percorso specifico nella raccolta. Per un esempio, vedere Spostarsi tra gli oggetti in un insieme di dati CollectionView.
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 CollectionViewSource oggetto come in questo esempio, è necessario impostarne la IsSynchronizedWithCurrentItem proprietà su true
per il funzionamento.
Per altri esempi, vedere Associare a una raccolta e visualizzare informazioni in base alla selezione e Usare il modello master-dettagli con dati gerarchici.
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'uso dei modelli di dati, l'interfaccia utente dell'app nella sezione What is data binding sarà simile alla 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 su come visualizzare la raccolta dati, ListBox visualizza la rappresentazione di stringa di ogni oggetto nella raccolta sottostante e ContentControl visualizza la rappresentazione di 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 DataTemplate precedente vengono usati ad esempio oggetti DataTrigger 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.
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 un esempio di 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 TextBox"Start Date" contenuto in Add Product Listing 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 TextBox StartDateEntryForm e StartPriceEntryForm 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 RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Con l'oggetto personalizzato ErrorTemplate e ToolTip, StartDateEntryForm TextBox ha un aspetto simile al seguente quando si verifica un errore di convalida.
Se le Binding regole di convalida associate ma non si specifica un ErrorTemplate oggetto nel controllo associato, verrà usato un valore predefinito ErrorTemplate per notificare agli 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 StartPriceEntryForm TextBox è 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 associazione crea un ValidationError oggetto e lo aggiunge alla Validation.Errors raccolta 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.
Quando Validation.Errors non è vuoto, la Validation.HasError proprietà associata 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 all'associazione è associato un ExceptionValidationRule oggetto o se la ValidatesOnExceptions proprietà è impostata su true
e viene generata un'eccezione quando il motore di associazione imposta l'origine, il motore di associazione verifica se è presente un oggetto UpdateSourceExceptionFilter. È possibile usare il UpdateSourceExceptionFilter callback per fornire un gestore personalizzato per la gestione delle eccezioni. Se un oggetto UpdateSourceExceptionFilter non viene specificato in Binding, il motore di associazione crea un ValidationError oggetto con l'eccezione e lo aggiunge alla Validation.Errors raccolta 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