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.

Screenshot of the Photo lab main page.

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.

Screenshot of the Photo lab detail page.

Prerequisiti

Parte 0: Scaricare il codice di avvio da GitHub

Per questa esercitazione, inizierai con una versione semplificata dell'esempio PhotoLab.

  1. Passare alla pagina di GitHub relativa all'esempio: https://github.com/Microsoft/Windows-appsample-photo-lab.

  2. Quindi dovrai clonare o scaricare l'esempio. Seleziona il pulsante Clone or download (Clona o scarica). Viene visualizzato un sottomenu. The Clone or download menu on the PhotoLab sample's GitHub page

    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.

  3. 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

  1. Aprire la cartella xaml-basics-starting-points\data-binding e avviare il file PhotoLab.sln in Visual Studio.

  2. 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.

    Running app with placeholder images and text

  3. 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 da ImageGridView per selezionare questo modello per la visualizzazione di oggetti dati.

  4. 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 classe ImageFileInfo (dove local: indica lo spazio dei nomi locale definito in una dichiarazione xmlns presente nella parte superiore del file).

    x:DataType è obbligatorio quando si usano espressioni x:Bind in un modello di dati, come descritto di seguito.

  5. In DataTemplate, trovare l'elemento Image denominato ItemImage e sostituire Source 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 qualsiasi x: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'espressione x:Bind. Provare nel codice appena incollato: posizionare il cursore immediatamente dopo x:Bind e premere la barra spaziatrice per visualizzare un elenco delle proprietà a cui è possibile eseguire il binding.

  6. 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.

Running app with real images and text instead of placeholders

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.

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

  1. In MainPage.xaml.cs trovare il metodo GetItemsAsync e rimuovere il codice che imposta ItemsSource.

    Prima:

    ImageGridView.ItemsSource = Images;
    

    Dopo:

    // Replaced with XAML binding:
    // ImageGridView.ItemsSource = Images;
    
  2. In MainPage.xaml trovare l'elemento GridView denominato ImageGridView e aggiungere un attributo ItemsSource. Per il valore, usare un'espressione x: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 tipo ObservableCollection<ImageFileInfo>, quindi i singoli elementi visualizzati in GridView sono di tipo ImageFileInfo. Corrisponde al valore x: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

  1. 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 con x: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.

  2. 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 denominato ImageGridView_DefaultItemTemplate e sostituire i valori **Height** e Width del controllo Grid 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.

Running app with zoom slider showing

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

  1. In MainPage.xaml.cs modifica la firma della classe MainPage in modo che implementi l'interfaccia INotifyPropertyChanged.

    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 evento PropertyChanged (aggiunto in seguito) di cui i binding possono rimanere in ascolto per aggiornare l'interfaccia utente.

  2. Aggiungere un evento PropertyChanged alla classe MainPage.

    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.

  3. Aggiungere una proprietà ItemSize e generare l'evento PropertyChanged 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 evento PropertyChanged potenzialmente non necessario.

    L'evento vero e proprio viene generato dal metodo Invoke. Il punto interrogativo consente di verificare se l'evento PropertyChanged è 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 chiave this) 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.

  4. In MainPage.xaml, trovare l'oggetto DataTemplate denominato ImageGridView_DefaultItemTemplate e sostituire i valori Height e Width del controllo Grid 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 di Value con ItemSize e ZoomSlider con page. Eseguire questa operazione sia per Height che per Width.

    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

  1. 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;
        }
    }
    
  2. In MainPage.xaml spostarsi nella parte superiore del file e aggiungere un binding di evento SizeChanged all'elemento Page.

    Prima:

    <Page x:Name="page"
    

    Dopo:

    <Page x:Name="page"
          SizeChanged="{x:Bind DetermineItemSize}"
    
  3. Trovare il controllo Slider denominato ZoomSlider (nella sezione Page.Resources) e aggiungere un binding di eventi ValueChanged.

    Prima:

    <Slider x:Name="ZoomSlider"
    

    Dopo:

    <Slider x:Name="ZoomSlider"
            ValueChanged="{x:Bind DetermineItemSize}"
    
  4. Trovare l'oggetto ToggleSwitch denominato FitScreenToggle e aggiungere un binding di eventi Toggled.

    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.

Running app with fit-to-screen enabled

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

  1. In MainPage.xaml trovare l'elemento GridView denominato ImageGridView. Per rendere gli elementi selezionabili, impostare IsItemClickEnabled su True e aggiungere un gestore eventi ItemClick.

    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.

  2. 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.

  3. (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

  1. In DetailPage.xaml trovare l'elemento TextBlock denominato TitleTextBlock e il controllo RatingControl successivo e aggiornare le relative espressioni x: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}"
                            ... >
    
  2. 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.

Effect sliders with default label values

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

  1. Trovare il TextBlock dopo il dispositivo di scorrimento Exposure e sostituire il valore Text 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 .NET ToString, 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'espressione x:Bind di chiamare ToString con il nuovo valore e quindi di aggiornare l'interfaccia utente con il risultato.

  2. 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.

Effect sliders with working labels

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: