Esercitazione: Creare data binding
Supponi di aver progettato e implementato un'interfaccia utente dall'aspetto accattivante con immagini segnaposto, testo boilerplate "lorem ipsum" e controlli che ancora non eseguono alcuna azione. Successivamente, vuoi connetterla a dati reali e trasformarla da prototipo di progettazione ad app attiva a tutti gli effetti.
In questa esercitazione scoprirai come sostituire il testo boilerplate con data binding e come creare altri collegamenti diretti tra l'interfaccia utente e i dati. Apprenderai anche come formattare o convertire i dati per la visualizzazione, mantenendo sincronizzati l'interfaccia utente e i dati stessi. Al termine di questa esercitazione, sarai in grado di semplificare e organizzare meglio il codice XAML e C#, facilitandone la gestione e l'estensione.
Inizierai con una versione semplificata dell'esempio PhotoLab. Questa versione di avvio include il livello dati completo e i layout delle pagine XAML di base, ma tralascia numerose funzionalità per facilitarti l'analisi del codice. Questa esercitazione non è concepita per l'app completa ed è quindi opportuno usare la versione finale per vedere funzionalità come le animazioni personalizzate e i layout adattivi. Puoi trovare la versione finale nella cartella radice del repository Windows-appsample-photo-lab.
L'app di esempio PhotoLab è costituita da due pagine. La pagina principale mostra una visualizzazione della raccolta foto, insieme ad alcune informazioni su ogni file di immagine.
La pagina dei dettagli visualizza una singola foto dopo che è stata selezionata. Un menu di modifica a comparsa consente di modificare, rinominare e salvare la foto.
Prerequisiti
- Visual Studio 2019 o versione successiva: Scaricare Visual Studio 2019 (La Community Edition è gratuita.)
- Windows SDK (10.0.17763.0 o versione successiva): Scaricare l'ultimo Windows SDK (gratuito)
- Windows 10, versione 1809 o successive
Parte 0: Scaricare il codice di avvio da GitHub
Per questa esercitazione, inizierai con una versione semplificata dell'esempio PhotoLab.
Passare alla pagina di GitHub relativa all'esempio: https://github.com/Microsoft/Windows-appsample-photo-lab.
Quindi dovrai clonare o scaricare l'esempio. Seleziona il pulsante Clone or download (Clona o scarica). Viene visualizzato un sottomenu.
Se non hai familiarità con GitHub:
a. Seleziona Download ZIP (Scarica ZIP) e salva il file in locale. Viene scaricato un file ZIP che contiene tutti i file di progetto necessari.
b. Estrai il file. Usare Esplora file per selezionare il file con estensione zip appena scaricato, fare clic sul file con il pulsante destro del mouse e scegliere Estrai tutto.
c. Passa alla copia locale dell'esempio e vai alla directory
Windows-appsample-photo-lab-master\xaml-basics-starting-points\data-binding
.Se conosci già GitHub:
a. Clonare il ramo principale del repository in locale.
b. Passa alla directory
Windows-appsample-photo-lab\xaml-basics-starting-points\data-binding
.Fare doppio clic su
Photolab.sln
per aprire la soluzione in Visual Studio.
Parte 1: Sostituire i segnaposto
Qui è possibile creare binding una tantum nel codice XAML del modello di dati per visualizzare immagini reali e i relativi metadati anziché contenuto segnaposto.
I binding una tantum sono destinati a dati invariabili di sola lettura e di conseguenza hanno prestazioni elevate e sono di semplice creazione, consentendoti di visualizzare set di dati di grandi dimensioni nei controlli GridView
e ListView
.
Sostituire i segnaposto con binding una tantum
Aprire la cartella
xaml-basics-starting-points\data-binding
e avviare il filePhotoLab.sln
in Visual Studio.Verificare che la piattaforma della soluzione sia impostata su x86 o x64, non su Arm, e quindi eseguire l'app. Viene mostrato lo stato dell'app con i segnaposto dell'interfaccia utente, prima che i binding siano stati aggiunti.
Aprire MainPage.xaml e cercare un
DataTemplate
denominato ImageGridView_DefaultItemTemplate. Aggiornerai questo modello per l'uso di data binding.Prima:
<DataTemplate x:Key="ImageGridView_DefaultItemTemplate">
Il valore
x:Key
viene utilizzato daImageGridView
per selezionare questo modello per la visualizzazione di oggetti dati.Aggiungere un valore
x:DataType
al modello.Dopo:
<DataTemplate x:Key="ImageGridView_DefaultItemTemplate" x:DataType="local:ImageFileInfo">
x:DataType
indica il tipo a cui è destinato un modello. In questo caso, si tratta di un modello per la classeImageFileInfo
(dovelocal:
indica lo spazio dei nomi locale definito in una dichiarazione xmlns presente nella parte superiore del file).x:DataType
è obbligatorio quando si usano espressionix:Bind
in un modello di dati, come descritto di seguito.In
DataTemplate
, trovare l'elementoImage
denominatoItemImage
e sostituireSource
come illustrato.Prima:
<Image x:Name="ItemImage" Source="/Assets/StoreLogo.png" Stretch="Uniform" />
Dopo:
<Image x:Name="ItemImage" Source="{x:Bind ImageSource}" Stretch="Uniform" />
x:Name
identifica un elemento XAML in modo che tu possa farvi riferimento in un altro punto del codice XAML e nel code-behind.Le espressioni
x:Bind
forniscono un valore a una proprietà dell'interfaccia utente recuperando il valore da una proprietà data-object. Nei modelli la proprietà indicata è una proprietà di qualsiasix:DataType
su cui sia stato impostato il valore. In questo caso, quindi, l'origine dati è la proprietàImageFileInfo.ImageSource
.Nota
Il valore
x:Bind
consente anche all'editor di conoscere il tipo di dati, quindi è possibile usare IntelliSense anziché digitare il nome della proprietà in un'espressionex:Bind
. Provare nel codice appena incollato: posizionare il cursore immediatamente dopox:Bind
e premere la barra spaziatrice per visualizzare un elenco delle proprietà a cui è possibile eseguire il binding.Sostituisci i valori degli altri controlli dell'interfaccia utente nello stesso modo. Prova a eseguire questa operazione con IntelliSense invece che con Copia/Incolla.
Prima:
<TextBlock Text="Placeholder" ... /> <StackPanel ... > <TextBlock Text="PNG file" ... /> <TextBlock Text="50 x 50" ... /> </StackPanel> <muxc:RatingControl Value="3" ... />
Dopo:
<TextBlock Text="{x:Bind ImageTitle}" ... /> <StackPanel ... > <TextBlock Text="{x:Bind ImageFileType}" ... /> <TextBlock Text="{x:Bind ImageDimensions}" ... /> </StackPanel> <muxc:RatingControl Value="{x:Bind ImageRating}" ... />
Esegui l'app per vederne l'aspetto. Non è più presente alcun segnaposto. Questo è un buon inizio.
Nota
Se vuoi eseguire un ulteriore esperimento, prova ad aggiungere un nuovo elemento TextBlock al modello di dati e usa il trucchetto IntelliSense relativo a x:Bind per trovare una proprietà da visualizzare.
Parte 2: Usare il binding per connettere l'interfaccia utente della raccolta alle immagini
In questo caso, creerai binding una tantum nel codice XAML della pagina per connettere la visualizzazione Raccolta alla raccolta immagini, sostituendo il codice procedurale esistente che esegue tale operazione nel code-behind. Creerai inoltre un pulsante Delete per vedere come cambia la visualizzazione Raccolta quando dalla raccolta vengono rimosse una o più immagini. Allo stesso tempo, scoprirai come eseguire il binding di eventi a gestori eventi per una maggiore flessibilità rispetto a quella fornita dai gestori eventi tradizionali.
Tutti i binding considerati finora sono all'interno di modelli di dati e fanno riferimento a proprietà della classe indicata dal valore x:DataType
. Cosa accade al resto del codice XAML nella tua pagina?
Le espressioni x:Bind
all'esterno dei modelli di dati sono sempre associate alla pagina stessa. Ciò significa che è possibile fare riferimento a ciò che si inserisce nel code-behind o si dichiara nel codice XAML, ad esempio proprietà personalizzate e proprietà di altri controlli dell'interfaccia utente nella pagina (purché dispongano di un valore x:Name
).
Nell'esempio PhotoLab un uso di un binding simile a questo consiste nel connettere il controllo GridView
principale direttamente alla raccolta di immagini, invece di eseguire questa operazione nel code-behind. In seguito vedrai altri esempi.
Eseguire il binding del controllo GridView principale alla raccolta immagini
In MainPage.xaml.cs trovare il metodo
GetItemsAsync
e rimuovere il codice che impostaItemsSource
.Prima:
ImageGridView.ItemsSource = Images;
Dopo:
// Replaced with XAML binding: // ImageGridView.ItemsSource = Images;
In MainPage.xaml trovare l'elemento
GridView
denominatoImageGridView
e aggiungere un attributoItemsSource
. Per il valore, usare un'espressionex:Bind
che faccia riferimento alla proprietàImages
implementata nel code-behind.Prima:
<GridView x:Name="ImageGridView"
Dopo:
<GridView x:Name="ImageGridView" ItemsSource="{x:Bind Images}"
La proprietà
Images
è di tipoObservableCollection<ImageFileInfo>
, quindi i singoli elementi visualizzati inGridView
sono di tipoImageFileInfo
. Corrisponde al valorex:DataType
descritto nella Parte 1.
Tutti i binding esaminati finora sono una tantum e di sola lettura, comportamento predefinito per le espressioni x:Bind
normali. I dati vengono caricati solo in fase di inizializzazione, rendendo i binding ad alte prestazioni ideali per il supporto di più visualizzazioni complesse di set di dati di grandi dimensioni.
Persino il binding ItemsSource
appena aggiunto è un binding una tantum di sola lettura a un valore della proprietà invariabile, ma a questo punto è necessario considerare un'importante distinzione. Il valore invariabile della proprietà Images
è una singola istanza specifica di una raccolta, inizializzata una sola volta, come illustrato di seguito.
private ObservableCollection<ImageFileInfo> Images { get; }
= new ObservableCollection<ImageFileInfo>();
Il valore della proprietà Images
non cambia mai ma, poiché la proprietà è di tipo ObservableCollection<T>
, il contenuto della raccolta può variare e il binding noterà automaticamente le modifiche e aggiornerà l'interfaccia utente.
Per effettuare questa prova, verrà aggiunto temporaneamente un pulsante che consente di eliminare l'immagine attualmente selezionata. Questo pulsante non è nella versione finale perché la selezione di un'immagine ti indirizzerà a una pagina dei dettagli. Tuttavia, il comportamento di ObservableCollection<T>
continua a essere importante nell'esempio di PhotoLab finale perché XAML viene inizializzato nel costruttore di pagina (tramite la chiamata al metodo InitializeComponent
), ma la raccolta Images
viene popolata in seguito nel metodo GetItemsAsync
.
Aggiungere un pulsante di eliminazione
In MainPage.xaml trovare l'elemento
CommandBar
denominato MainCommandBar e aggiungere un nuovo pulsante prima del pulsante di zoom. I controlli di zoom ancora non funzionano. Verranno collegati nella prossima parte dell'esercitazione.<AppBarButton Icon="Delete" Label="Delete selected image" Click="{x:Bind DeleteSelectedImage}" />
Se si ha già familiarità con XAML, questo valore
Click
potrebbe sembrare insolito. Nelle versioni precedenti di XAML era necessario impostare questo valore su un metodo con una firma del gestore dell'evento specifico, includendo in genere i parametri per il mittente dell'evento e un oggetto di argomenti specifici dell'evento. È comunque possibile usare questa tecnica quando sono necessari gli argomenti dell'evento, ma conx:Bind
è possibile connettersi anche ad altri metodi. Ad esempio, se i dati dell'evento non sono necessari, puoi connetterti a metodi senza parametri, come in questo caso.In MainPage.xaml.cs aggiungere il metodo
DeleteSelectedImage
.private void DeleteSelectedImage() => Images.Remove(ImageGridView.SelectedItem as ImageFileInfo);
Questo metodo consente semplicemente di eliminare dalla raccolta
Images
l'immagine selezionata.
Ora esegui l'app e usa il pulsante per eliminare alcune immagini. Come si può vedere, l'interfaccia utente viene aggiornata automaticamente grazie al data binding e al tipo ObservableCollection<T>
.
Nota
Questo codice elimina l'istanza ImageFileInfo
dalla raccolta Images
solo nell'app in esecuzione. Non elimina il file di immagine dal computer.
Parte 3: Impostare il dispositivo di scorrimento dello zoom
In questa parte creerai binding unidirezionali da un controllo nel modello di dati al dispositivo di scorrimento dello zoom, che è esterno al modello. Si scoprirà anche che è possibile usare il data binding con molte proprietà del controllo, non solo quelle più ovvie come TextBlock.Text
e Image.Source
.
Eseguire il binding del modello di dati delle immagini al dispositivo di scorrimento dello zoom
Trovare l'oggetto
DataTemplate
denominatoImageGridView_DefaultItemTemplate
e sostituire i valori**Height**
eWidth
del controlloGrid
nella parte superiore del modello.Prima
<DataTemplate x:Key="ImageGridView_DefaultItemTemplate" x:DataType="local:ImageFileInfo"> <Grid Height="200" Width="200" Margin="{StaticResource LargeItemMargin}">
Dopo
<DataTemplate x:Key="ImageGridView_DefaultItemTemplate" x:DataType="local:ImageFileInfo"> <Grid Height="{Binding Value, ElementName=ZoomSlider}" Width="{Binding Value, ElementName=ZoomSlider}" Margin="{StaticResource LargeItemMargin}">
Si è notato che si tratta di espressioni Binding
e non di espressioni x:Bind
? Si tratta del modo tradizionale per eseguire i data binding ed è tendenzialmente obsoleto. x:Bind
fa quasi tutto ciò che fa Binding
, e altro ancora. Tuttavia, quando si usa x:Bind
in un modello di dati, viene eseguito il binding al tipo dichiarato nel valore x:DataType
. Quindi, in che modo esegui il binding di un elemento del modello a un elemento nel codice XAML della pagina o nel code-behind? È necessario usare un'espressione in stile Binding
precedente.
Le espressioni Binding
non riconoscono il valore x:DataType
, ma queste espressioni Binding
hanno valori ElementName
che funzionano quasi allo stesso modo. Pertanto, indicano al motore di binding che Binding Value è un binding alla proprietà Value
dell'elemento specificato nella pagina (ovvero l'elemento con tale valore x:Name
). Per eseguire il binding a una proprietà nel code-behind, l'aspetto deve essere simile a {Binding MyCodeBehindProperty, ElementName=page}
dove page
fa riferimento al valore x:Name
impostato nell'elemento Page
in XAML.
Nota
Per impostazione predefinita, le espressioni Binding
sono unidirezionali (one-way, ovvero aggiornano automaticamente l'interfaccia utente quando il valore della proprietà associata cambia).
Al contrario, l'impostazione predefinita per x:Bind
è la modalità una tantum (one-time), ovvero tutte le modifiche apportate alla proprietà associata vengono ignorate. Questo è il comportamento predefinito perché è l'opzione con le prestazioni più elevate e la maggior parte dei binding riguarda dati statici di sola lettura.
Ne consegue che, se si usa x:Bind
con proprietà i cui valori possono cambiare, occorre ricordare di aggiungere Mode=OneWay
o Mode=TwoWay
. Vedrai alcuni esempi nella sezione successiva.
Esegui l'app e usa il dispositivo di scorrimento per modificare le dimensioni del modello immagine. Come puoi vedere, l'effetto è molto potente senza che sia necessario usare una notevole quantità di codice.
Nota
Per effettuare una verifica, prova a eseguire il binding di altre proprietà dell'interfaccia utente alla proprietà Value
del dispositivo di scorrimento dello zoom o ad altri dispositivi di scorrimento aggiunti dopo quest'ultimo. Ad esempio, è possibile associare la proprietà FontSize
di TitleTextBlock
a un nuovo dispositivo di scorrimento con un valore predefinito pari a 24. Assicurati di impostare valori minimi e massimi ragionevoli.
Parte 4: Migliorare l'esperienza di zoom
In questa parte si aggiungerà al code-behind una proprietà ItemSize
personalizzata e si creeranno binding unidirezionali dal modello immagine alla nuova proprietà. Il valore ItemSize
verrà aggiornato dal dispositivo di scorrimento dello zoom e in base ad altri fattori, ad esempio l'interruttore Adatta allo schermo e le dimensioni della finestra, per offrire un'esperienza ancora più raffinata.
A differenza delle proprietà predefinite dei controlli, le proprietà personalizzate non aggiornano automaticamente l'interfaccia utente, anche con binding unidirezionali e bidirezionali. Tali proprietà funzionano correttamente con binding una tantum (one-time), ma se vuoi che le modifiche relative alle tue proprietà vengano effettivamente visualizzate nell'interfaccia utente, devi eseguire alcune operazioni.
Creare la proprietà ItemSize in modo che aggiorni l'interfaccia utente
In MainPage.xaml.cs modifica la firma della classe
MainPage
in modo che implementi l'interfacciaINotifyPropertyChanged
.Prima:
public sealed partial class MainPage : Page
Dopo:
public sealed partial class MainPage : Page, INotifyPropertyChanged
In questo modo, il sistema di binding viene informato che
MainPage
ha un eventoPropertyChanged
(aggiunto in seguito) di cui i binding possono rimanere in ascolto per aggiornare l'interfaccia utente.Aggiungere un evento
PropertyChanged
alla classeMainPage
.public event PropertyChangedEventHandler PropertyChanged;
Questo evento fornisce l'implementazione completa necessaria all'interfaccia
INotifyPropertyChanged
. Tuttavia, perché abbia effetto, è necessario generare l'evento in modo esplicito nelle proprietà personalizzate.Aggiungere una proprietà
ItemSize
e generare l'eventoPropertyChanged
nel relativo setter.public double ItemSize { get => _itemSize; set { if (_itemSize != value) { _itemSize = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ItemSize))); } } } private double _itemSize;
La proprietà
ItemSize
espone il valore di un campo privato_itemSize
. L'uso di un campo sottostante simile a questo consente alla proprietà di verificare se un nuovo valore è uguale al valore precedente prima di generare un eventoPropertyChanged
potenzialmente non necessario.L'evento vero e proprio viene generato dal metodo
Invoke
. Il punto interrogativo consente di verificare se l'eventoPropertyChanged
è null, ovvero se non è stato ancora aggiunto alcun gestore dell'evento. Ogni binding unidirezionale o bidirezionale aggiunge un gestore dell'evento in modo non visibile all'applicazione, ma se nessuno rimane in ascolto, in questo punto non vengono eseguite altre operazioni. Se peròPropertyChanged
non è null,Invoke
viene chiamato con un riferimento all'origine dell'evento (la pagina stessa, rappresentata dalla parola chiavethis
) e un oggetto event-args che indica il nome della proprietà. Con queste informazioni, i binding unidirezionali o bidirezionali alla proprietàItemSize
verranno informati delle eventuali modifiche in modo che possano aggiornare l'interfaccia utente associata.In MainPage.xaml, trovare l'oggetto
DataTemplate
denominatoImageGridView_DefaultItemTemplate
e sostituire i valoriHeight
eWidth
del controlloGrid
nella parte superiore del modello. Se è stato eseguito il binding tra controlli nella parte precedente di questa esercitazione, la sola modifica da apportare è la sostituzione diValue
conItemSize
eZoomSlider
conpage
. Eseguire questa operazione sia perHeight
che perWidth
.Prima
<DataTemplate x:Key="ImageGridView_DefaultItemTemplate" x:DataType="local:ImageFileInfo"> <Grid Height="{Binding Value, ElementName=ZoomSlider}" Width="{Binding Value, ElementName=ZoomSlider}" Margin="{StaticResource LargeItemMargin}">
Dopo
<DataTemplate x:Key="ImageGridView_DefaultItemTemplate" x:DataType="local:ImageFileInfo"> <Grid Height="{Binding ItemSize, ElementName=page}" Width="{Binding ItemSize, ElementName=page}" Margin="{StaticResource LargeItemMargin}">
Ora che l'interfaccia utente può rispondere alle modifiche relative a ItemSize
, occorre effettivamente apportare alcune modifiche. Come accennato in precedenza, il valore ItemSize
viene calcolato a partire dallo stato corrente di vari controlli dell'interfaccia utente, ma il calcolo deve essere eseguito ogni volta che lo stato di questi controlli cambia. A tale scopo, userai binding di evento in modo che alcune modifiche dell'interfaccia utente chiamino un metodo helper che aggiorna ItemSize
.
Aggiornare il valore della proprietà ItemSize
Aggiungere il metodo
DetermineItemSize
in MainPage.xaml.cs.private void DetermineItemSize() { if (FitScreenToggle != null && FitScreenToggle.IsOn == true && ImageGridView != null && ZoomSlider != null) { // The 'margins' value represents the total of the margins around the // image in the grid item. 8 from the ItemTemplate root grid + 8 from // the ItemContainerStyle * (Right + Left). If those values change, // this value needs to be updated to match. int margins = (int)this.Resources["LargeItemMarginValue"] * 4; double gridWidth = ImageGridView.ActualWidth - (int)this.Resources["DefaultWindowSidePaddingValue"]; double ItemWidth = ZoomSlider.Value + margins; // We need at least 1 column. int columns = (int)Math.Max(gridWidth / ItemWidth, 1); // Adjust the available grid width to account for margins around each item. double adjustedGridWidth = gridWidth - (columns * margins); ItemSize = (adjustedGridWidth / columns); } else { ItemSize = ZoomSlider.Value; } }
In MainPage.xaml spostarsi nella parte superiore del file e aggiungere un binding di evento
SizeChanged
all'elementoPage
.Prima:
<Page x:Name="page"
Dopo:
<Page x:Name="page" SizeChanged="{x:Bind DetermineItemSize}"
Trovare il controllo
Slider
denominatoZoomSlider
(nella sezionePage.Resources
) e aggiungere un binding di eventiValueChanged
.Prima:
<Slider x:Name="ZoomSlider"
Dopo:
<Slider x:Name="ZoomSlider" ValueChanged="{x:Bind DetermineItemSize}"
Trovare l'oggetto
ToggleSwitch
denominatoFitScreenToggle
e aggiungere un binding di eventiToggled
.Prima:
<ToggleSwitch x:Name="FitScreenToggle"
Dopo:
<ToggleSwitch x:Name="FitScreenToggle" Toggled="{x:Bind DetermineItemSize}"
Esegui l'app, quindi usa il dispositivo di scorrimento dello zoom e l'interruttore Adatta allo schermo per modificare le dimensioni del modello immagine. Come puoi vedere, le modifiche più recenti consentono di implementare un'esperienza di zoom o ridimensionamento più raffinata, mantenendo organizzato il codice.
Nota
Per una richiesta di verifica, provare ad aggiungere un oggetto TextBlock
dopo ZoomSlider
ed eseguire il binding della proprietà Text
alla proprietà ItemSize
. Poiché non si trova in un modello di dati, è possibile usare x:Bind
anziché Binding
come nei binding precedenti ItemSize
.
Parte 5: Abilitare le modifiche degli utenti
In questa sezione creerai binding bidirezionali per consentire agli utenti di aggiornare i valori, inclusi il titolo dell'immagine, la classificazione e diversi effetti visivi.
A tale scopo, si aggiornerà l'elemento DetailPage
esistente, che fornisce un visualizzatore di immagine singola, un controllo zoom e un'interfaccia utente di modifica.
Prima di tutto, occorre però collegare DetailPage
in modo che l'app si sposti su tale elemento quando l'utente fa clic su un'immagine nella visualizzazione Raccolta.
Collegare l'elemento DetailPage
In MainPage.xaml trovare l'elemento
GridView
denominatoImageGridView
. Per rendere gli elementi selezionabili, impostareIsItemClickEnabled
suTrue
e aggiungere un gestore eventiItemClick
.Suggerimento
Se apporti la modifica indicata di seguito digitando invece di eseguire le operazioni Copia/Incolla, verrà visualizzata una finestra popup di IntelliSense con il messaggio "<Nuovo gestore eventi>". Se premi il tasto TAB, come valore verrà inserito un nome di gestore di metodi predefinito e verrà nascosto automaticamente il metodo illustrato nel passaggio successivo. Puoi quindi premere F12 per passare al metodo nel code-behind.
Prima:
<GridView x:Name="ImageGridView">
Dopo:
<GridView x:Name="ImageGridView" IsItemClickEnabled="True" ItemClick="ImageGridView_ItemClick">
Nota
Qui viene usato un gestore eventi convenzionale anziché un'espressione x:Bind. Questo perché è necessario visualizzare i dati dell'evento, come indicato di seguito.
In MainPage.xaml.cs aggiungi il gestore eventi (o inseriscilo, se hai usato il suggerimento nell'ultimo passaggio).
private void ImageGridView_ItemClick(object sender, ItemClickEventArgs e) { this.Frame.Navigate(typeof(DetailPage), e.ClickedItem); }
Questo metodo consente semplicemente di spostarsi nella pagina dei dettagli passando l'elemento su cui è stato fatto clic, ovvero un oggetto
ImageFileInfo
usato da DetailPage.OnNavigatedTo per inizializzare la pagina. Non è necessario implementare tale metodo in questa esercitazione, ma puoi dare un'occhiata per vederne l'effetto.(Facoltativo) Elimina o imposta come commento eventuali controlli aggiunti nei punti di riproduzione precedenti che funzionano con l'immagine attualmente selezionata. Anche se non esegui l'eliminazione, non si verifica alcun problema, ma ora è molto più difficile selezionare un'immagine senza accedere alla pagina dei dettagli.
Ora che hai connesso le due pagine, esegui l'app ed esamina il risultato. Tutti gli elementi funzionano, tranne i controlli nel riquadro di modifica, che non rispondono quando tenti di modificare i valori.
Come puoi vedere, il titolo viene visualizzato nella relativa casella di testo e puoi digitare le modifiche. Per eseguire il commit delle modifiche, devi spostare lo stato attivo su un altro controllo, ma il titolo nell'angolo superiore sinistro dello schermo ancora non si aggiorna.
Tutti i controlli sono già associati tramite le espressioni x:Bind
normali descritte nella Parte 1. Se ben ricordi, ciò significa che si tratta di tutti binding una tantum, pertanto le modifiche ai valori non vengono registrate. Per ovviare a questo problema, è sufficiente trasformarli in binding bidirezionali.
Rendere interattivi i controlli di modifica
In DetailPage.xaml trovare l'elemento
TextBlock
denominato TitleTextBlock e il controllo RatingControl successivo e aggiornare le relative espressionix:Bind
in modo da includere Mode=TwoWay.Prima:
<TextBlock x:Name="TitleTextBlock" Text="{x:Bind item.ImageTitle}" ... > <muxc:RatingControl Value="{x:Bind item.ImageRating}" ... >
Dopo:
<TextBlock x:Name="TitleTextBlock" Text="{x:Bind item.ImageTitle, Mode=TwoWay}" ... > <muxc:RatingControl Value="{x:Bind item.ImageRating, Mode=TwoWay}" ... >
Esegui la stessa operazione per tutti i dispositivi di scorrimento degli effetti presenti dopo il controllo di classificazione.
<Slider Header="Exposure" ... Value="{x:Bind item.Exposure, Mode=TwoWay}" ... <Slider Header="Temperature" ... Value="{x:Bind item.Temperature, Mode=TwoWay}" ... <Slider Header="Tint" ... Value="{x:Bind item.Tint, Mode=TwoWay}" ... <Slider Header="Contrast" ... Value="{x:Bind item.Contrast, Mode=TwoWay}" ... <Slider Header="Saturation" ... Value="{x:Bind item.Saturation, Mode=TwoWay}" ... <Slider Header="Blur" ... Value="{x:Bind item.Blur, Mode=TwoWay}" ...
La modalità bidirezionale, come è prevedibile, indica che i dati si spostano in entrambe le direzioni ogni volta che sono presenti modifiche su un lato.
In modo analogo ai binding unidirezionali descritti in precedenza, questi binding bidirezionali ora aggiorneranno l'interfaccia utente tutte le volte che le proprietà associate cambiano, grazie all'implementazione di INotifyPropertyChanged
nella classe ImageFileInfo
. Con il binding bidirezionale, invece, i valori si sposteranno anche dall'interfaccia utente alle proprietà associate ogni volta che l'utente interagisce con il controllo. Nel codice XAML non è necessario intervenire ulteriormente.
Esegui l'app e prova i controlli di modifica. Come puoi vedere, quando apporti una modifica, quest'ultima ora influisce sui valori relativi all'immagine e le modifiche vengono mantenute quando torni alla pagina principale.
Parte 6: Formattare i valori tramite il binding di funzione
Rimane da affrontare un ultimo problema. Quando sposti i dispositivi di scorrimento degli effetti, le etichette accanto a essi ancora non cambiano.
La parte finale di questa esercitazione consiste nell'aggiungere binding che formattano i valori dei dispositivi di scorrimento per la visualizzazione.
Eseguire il binding delle etichette dei dispositivi di scorrimento degli effetti e formattare i valori per la visualizzazione
Trovare il
TextBlock
dopo il dispositivo di scorrimentoExposure
e sostituire il valoreText
con l'espressione di binding illustrata qui.Prima:
<Slider Header="Exposure" ... /> <TextBlock ... Text="0.00" />
Dopo:
<Slider Header="Exposure" ... /> <TextBlock ... Text="{x:Bind item.Exposure.ToString('N', culture), Mode=OneWay}" />
Si tratta di un binding di funzione perché esegui il binding al valore restituito di un metodo. Il metodo deve essere accessibile tramite il code-behind della pagina o il tipo
x:DataType
se si usa un modello di dati. In questo caso, si tratta del noto metodo .NETToString
, al quale si accede tramite la proprietà relativa all'elemento della pagina e quindi tramite la proprietàExposure
dell'elemento. Ciò illustra in che modo è possibile eseguire il binding a metodi e proprietà annidati in profondità in una catena di connessioni.Il binding di funzione è ideale per formattare i valori per la visualizzazione perché puoi passare altre origini di binding come argomenti del metodo e l'espressione di binding rimarrà in ascolto delle modifiche apportate a questi valori come previsto nella modalità unidirezionale. In questo esempio l'argomento culture è un riferimento a un campo invariabile implementato nel code-behind, ma potrebbe facilmente essere stato una proprietà che genera eventi
PropertyChanged
. In tal caso, ogni modifica al valore della proprietà indicherebbe all'espressionex:Bind
di chiamareToString
con il nuovo valore e quindi di aggiornare l'interfaccia utente con il risultato.Eseguire la stessa operazione per gli elementi
TextBlock
che definiscono le etichette degli altri dispositivi di scorrimento degli effetti.<Slider Header="Temperature" ... /> <TextBlock ... Text="{x:Bind item.Temperature.ToString('N', culture), Mode=OneWay}" /> <Slider Header="Tint" ... /> <TextBlock ... Text="{x:Bind item.Tint.ToString('N', culture), Mode=OneWay}" /> <Slider Header="Contrast" ... /> <TextBlock ... Text="{x:Bind item.Contrast.ToString('N', culture), Mode=OneWay}" /> <Slider Header="Saturation" ... /> <TextBlock ... Text="{x:Bind item.Saturation.ToString('N', culture), Mode=OneWay}" /> <Slider Header="Blur" ... /> <TextBlock ... Text="{x:Bind item.Blur.ToString('N', culture), Mode=OneWay}" />
Quando ora esegui l'app, tutto funziona, incluse le etichette dei dispositivi di scorrimento.
Conclusione
Questa esercitazione ha fornito un esempio di data binding e illustrato alcune funzionalità disponibili. Un avviso prima di concludere: non per tutti gli elementi è possibile eseguire il binding e in alcuni casi i valori a cui tenti di connetterti non sono compatibili con le proprietà per cui stai provando a eseguire il binding. Esiste una notevole flessibilità nelle operazioni di binding, ma non funzionano in ogni situazione.
Un esempio di un problema non risolto tramite binding è la presenza di un controllo che non dispone di proprietà adatte a cui eseguire il binding, come la funzionalità di zoom della pagina dei dettagli. Questo dispositivo di scorrimento dello zoom deve interagire con l'oggetto ScrollViewer
che visualizza l'immagine, ma ScrollViewer
può essere aggiornato solo tramite il relativo metodo ChangeView
. In questo caso, vengono usati i gestori eventi tradizionali per sincronizzare ScrollViewer
e il dispositivo di scorrimento dello zoom. Per informazioni dettagliate, vedere i metodi ZoomSlider_ValueChanged
e MainImageScroll_ViewChanged
in DetailPage
.
Il binding è tuttavia un modo potente e flessibile per semplificare il codice e mantenere la logica dell'interfaccia utente separata dalla logica dei dati. In questo modo risulta molto più semplice apportare modificare su un lato di tale separazione, riducendo contemporaneamente il rischio di introdurre bug sull'altro lato.
Un esempio di separazione dell'interfaccia utente e dei dati è rappresentato dalla proprietà ImageFileInfo.ImageTitle
. Questa proprietà e la proprietà ImageRating
sono leggermente diverse dalla proprietà ItemSize
creata nella Parte 4 perché il valore viene archiviato nei metadati del file (esposti tramite il tipo ImageProperties
) anziché in un campo. Inoltre, ImageTitle
restituisce il valore ImageName
(impostato sul nome del file) se non esiste alcun titolo nei metadati del file.
public string ImageTitle
{
get => String.IsNullOrEmpty(ImageProperties.Title) ? ImageName : ImageProperties.Title;
set
{
if (ImageProperties.Title != value)
{
ImageProperties.Title = value;
var ignoreResult = ImageProperties.SavePropertiesAsync();
OnPropertyChanged();
}
}
}
Come si può notare, il setter aggiorna la proprietà ImageProperties.Title
e quindi chiama SavePropertiesAsync
per scrivere il nuovo valore nel file. Questo è un metodo asincrono, ma non è possibile usare la parola chiave await
in una proprietà e non sarebbe comunque opportuno perché i getter e setter delle proprietà terminerebbero immediatamente. Al contrario, è opportuno chiamare il metodo e ignorare l'oggetto Task
che restituisce.
Approfondimenti
Ora che hai completato questo lab, disponi di informazioni sul binding sufficienti per risolvere un problema in modo autonomo.
Come avrai notato, se modifichi il livello di zoom nella pagina dei dettagli, tale livello viene reimpostato automaticamente quando torni indietro e poi selezioni nuovamente la stessa immagine. Vuoi scoprire come mantenere e ripristinare il livello di zoom per ogni immagine singolarmente? Buona fortuna!
In questa esercitazione sono presenti tutte le informazioni necessarie, ma se hai bisogno di altre indicazioni, è disponibile la documentazione sul data binding. Inizia da qui: