Condividi tramite


Esercitazione: Creare un visualizzatore di foto semplice destinato a più piattaforme

Dopo aver creato un visualizzatore di foto semplice iniziale WinUI 3, potresti chiederti come raggiungere più utenti senza dover riscrivere l'app. Questa esercitazione usa Uno Platform per espandere la portata dell'applicazione WinUI 3 C# esistente, consentendo il riutilizzo della logica di business e del livello dell'interfaccia utente in dispositivi mobili, Web e desktop nativi. Eseguendo solo modifiche minime all'app visualizzatore di foto semplice, è possibile eseguire una copia ad alta risoluzione dell'app convertita in queste piattaforme.

Screenshot dell'app UnoSimplePhoto destinata al desktop Web e WinUI.

Prerequisiti

  • Visual Studio 2022 17.4 o versione successiva

  • Configurare il computer di sviluppo (vedere Introduzione a WinUI)

  • carico di lavoro ASP.NET e sviluppo Web (per lo sviluppo WebAssembly)

    Screenshot del carico di lavoro sviluppo Web in Visual Studio.

  • Sviluppo di app multipiattaforma .NET installato (per lo sviluppo di app per iOS, Android, Mac Catalyst)

    Screenshot del carico di lavoro dotnet mobile in Visual Studio.

  • Sviluppo di desktop .NET installato (per lo sviluppo Gtk, Wpf e Linux Framebuffer)

    Screenshot del carico di lavoro dotnet desktop in Visual Studio.

Finalizzare l'ambiente

  1. Aprire un prompt della riga di comando, Terminale Windows se è installato oppure un prompt dei comandi o di Windows PowerShell dal menu Start.

  2. Installare o aggiornare lo strumento uno-check:

    • Utilizza il seguente comando:

      dotnet tool install -g uno.check
      
    • Per aggiornare lo strumento, se è già stata installata una versione precedente:

      dotnet tool update -g uno.check
      
  3. Eseguire lo strumento con il comando seguente:

    uno-check
    
  4. Seguire le istruzioni indicate dallo strumento. Poiché deve modificare il sistema, potrebbe essere richiesto di disporre di autorizzazioni elevate.

Installare i modelli di soluzione Uno Platform

Avviare Visual Studio, quindi fare clic su Continue without code. Fare clic su Extensions ->Manage Extensions dalla barra dei menu.

Screenshot della voce della barra dei menu di Visual Studio che legge le estensioni.

In Gestione estensioni espandere il nodo Online e cercare Uno, installare l'estensione Uno Platform o scaricarla e installarla da Visual Studio Marketplace, quindi riavviare Visual Studio.

Screenshot della finestra Gestisci estensioni in Visual Studio con estensione Uno Platform come risultato della ricerca.

Creare un'applicazione

Ora che siamo pronti per creare un'applicazione multipiattaforma, l'approccio che verrà adottato consiste nel creare una nuova applicazione Uno Platform. Il codice verrà copiato dal progetto SimplePhotos WinUI 3 dell'esercitazione precedente al progetto multipiattaforma. Ciò è possibile perché Uno Platform consente di riutilizzare il codebase esistente. Per le funzionalità dipendenti dalle API del sistema operativo fornite da ogni piattaforma, è possibile renderle facilmente funzionanti nel tempo. Questo approccio è particolarmente utile se hai un'applicazione esistente che vuoi convertire in altre piattaforme.

A breve, potrai sfruttare i vantaggi di questo approccio, in quanto puoi usare più piattaforme con una versione XAML familiare e il codebase già disponibile.

Aprire Visual Studio e creare un nuovo progetto tramite File>New>Project:

Screenshot della finestra di dialogo Crea un nuovo progetto.

Cercare Uno e selezionare il modello di progetto per l’app Uno Platform:

Screenshot della finestra di dialogo Crea un nuovo progetto con un'app Uno Platform come tipo di progetto selezionato.

Creare una nuova soluzione C# usando il tipo di app Uno Platform dalla pagina iniziale di Visual Studio. Per evitare conflitti con il codice dell'esercitazione precedente, a questa soluzione verrà assegnato un nome diverso, "UnoSimplePhotos". Specificare il nome del progetto, il nome della soluzione e la directory. In questo esempio il progetto multipiattaforma UnoSimplePhotos appartiene a una soluzione UnoSimplePhotos, che sarà disponibile in C:\Projects:

Screenshot della specifica dei dettagli del progetto per il nuovo progetto Uno Platform.

A questo punto devi scegliere un modello di base per rendere la raccolta dell’app semplice per foto multipiattaforma.

Il modello di app Uno Platform include due opzioni predefinite che consentono di iniziare rapidamente a usare una soluzione vuota o la configurazione predefinita che include riferimenti alle librerie Uno.Material e Uno.Toolkit. La configurazione predefinita include anche Uno.Extensions, usato per l'inserimento delle dipendenze, la configurazione, la navigazione e la registrazione. Inoltre, usa MVUX al posto di MVVM, rendendolo un ottimo punto di partenza per la creazione rapida di applicazioni reali.

Screenshot del modello di soluzione Uno per il tipo di avvio del progetto.

Per semplificare le operazioni, selezionare il set di impostazioni Vuoto. Fare quindi clic sul pulsante Crea. Attendere che i progetti vengano creati e che le relative dipendenze vengano ripristinate.

Un banner nella parte superiore dell'editor potrebbe richiedere di ricaricare i progetti, fare clic su Ricarica progetti:

Screenshot dell'offerta banner di Visual Studio per ricaricare i progetti per completare le modifiche.

In Esplora soluzioni dovrebbe essere visualizzata la struttura di file predefinita seguente:

Screenshot della struttura di file predefinita in Esplora soluzioni.

Aggiungere risorse immagini al progetto

L'app dovrà visualizzare alcune immagini. È possibile usare le stesse immagini dell'esercitazione precedente.

Nel progetto UnoSimplePhotos creare una nuova cartella denominata Assets e copiare i file di immagine JPG in una sottocartella Samples. La struttura della cartella di Assets dovrebbe risultare come segue:

Screenshot del riquadro Esplora soluzioni in Visual Studio con i nuovi file e le nuove cartelle aggiunti.

Per ulteriori informazioni sulla creazione della cartella Assets e sull'aggiunta di immagini, consultare la documentazione di Uno Platform relativa alle Risorse e alla visualizzazione delle immagini.

Preparazione dell’app

Dopo aver generato il punto di partenza funzionale dell'applicazione WinUI multipiattaforma, è possibile copiare il codice dal progetto desktop.

Copiare la visualizzazione

Poiché Uno Platform consente di usare la versione XAML già nota, è possibile copiare lo stesso codice creato nell'esercitazione precedente.

Tornare al progetto SimplePhotos dell'esercitazione precedente. InEsplora soluzioni trovare il file denominato MainWindow.xaml e aprirlo. Tieni presente che i contenuti della visualizzazione sono definito all'interno di un elemento Window anziché di un oggetto Page. Questo perché il progetto desktop è un'applicazione WinUI 3, che può usare elementi Window per definire il contenuto della visualizzazione:

<Window x:Class="SimplePhotos.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:SimplePhotos"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">

    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="ImageGridView_ItemTemplate" 
                          x:DataType="local:ImageFileInfo">
                <Grid Height="300"
                      Width="300"
                      Margin="8">
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>

                    <Image x:Name="ItemImage"
                           Source="Assets/StoreLogo.png"
                           Stretch="Uniform" />

                    <StackPanel Orientation="Vertical"
                                Grid.Row="1">
                        <TextBlock Text="{x:Bind ImageTitle}"
                                   HorizontalAlignment="Center"
                                   Style="{StaticResource SubtitleTextBlockStyle}" />
                        <StackPanel Orientation="Horizontal"
                                    HorizontalAlignment="Center">
                            <TextBlock Text="{x:Bind ImageFileType}"
                                       HorizontalAlignment="Center"
                                       Style="{StaticResource CaptionTextBlockStyle}" />
                            <TextBlock Text="{x:Bind ImageDimensions}"
                                       HorizontalAlignment="Center"
                                       Style="{StaticResource CaptionTextBlockStyle}"
                                       Margin="8,0,0,0" />
                        </StackPanel>

                        <RatingControl Value="{x:Bind ImageRating}" 
                                       IsReadOnly="True"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>

            <Style x:Key="ImageGridView_ItemContainerStyle"
                   TargetType="GridViewItem">
                <Setter Property="Background" 
                        Value="Gray"/>
                <Setter Property="Margin" 
                        Value="8"/>
            </Style>

            <ItemsPanelTemplate x:Key="ImageGridView_ItemsPanelTemplate">
                    <ItemsWrapGrid Orientation="Horizontal"
                                   HorizontalAlignment="Center"/>
                </ItemsPanelTemplate>
        </Grid.Resources>

        <GridView x:Name="ImageGridView"
                  ItemsSource="{x:Bind Images}"
                  ItemTemplate="{StaticResource ImageGridView_ItemTemplate}"
                  ItemContainerStyle="{StaticResource ImageGridView_ItemContainerStyle}"
                  ItemsPanel="{StaticResource ImageGridView_ItemsPanelTemplate}"
                  ContainerContentChanging="ImageGridView_ContainerContentChanging" />
    </Grid>
</Window>

L'implementazione multipiattaforma di Uno Platform dei controlli trovati nell'elemento Window, come GridView, Image e RatingControl, garantisce che la visualizzazione stessa funzioni su tutte le piattaforme supportate con una semplice quantità di lavoro. Copiare il contenuto di Window e incollarlo nell'elemento Page del file MainPage.xaml nel progetto UnoSimplePhotos Uno Platform. La visualizzazione XAML MainPage dovrebbe risultare come segue:

<Page x:Class="UnoSimplePhotos.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:UnoSimplePhotos"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="ImageGridView_ItemTemplate"
                          x:DataType="local:ImageFileInfo">
                <Grid Height="300"
                      Width="300"
                      Margin="8">
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>

                    <Image x:Name="ItemImage"
                           Source="Assets/StoreLogo.png"
                           Stretch="Uniform" />

                    <StackPanel Orientation="Vertical"
                                Grid.Row="1">
                        <TextBlock Text="{x:Bind ImageTitle}"
                                   HorizontalAlignment="Center"
                                   Style="{StaticResource SubtitleTextBlockStyle}" />
                        <StackPanel Orientation="Horizontal"
                                    HorizontalAlignment="Center">
                            <TextBlock Text="{x:Bind ImageFileType}"
                                       HorizontalAlignment="Center"
                                       Style="{StaticResource CaptionTextBlockStyle}" />
                            <TextBlock Text="{x:Bind ImageDimensions}"
                                       HorizontalAlignment="Center"
                                       Style="{StaticResource CaptionTextBlockStyle}"
                                       Margin="8,0,0,0" />
                        </StackPanel>

                        <RatingControl Value="{x:Bind ImageRating}" 
                                       IsReadOnly="True"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>

            <Style x:Key="ImageGridView_ItemContainerStyle"
                   TargetType="GridViewItem">
                <Setter Property="Background" 
                        Value="Gray"/>
                <Setter Property="Margin" 
                        Value="8"/>
            </Style>

            <ItemsPanelTemplate x:Key="ImageGridView_ItemsPanelTemplate">
                <ItemsWrapGrid Orientation="Horizontal"
                               HorizontalAlignment="Center"/>
            </ItemsPanelTemplate>
        </Grid.Resources>

        <GridView x:Name="ImageGridView"
                  ItemsSource="{x:Bind Images}"
                  ItemTemplate="{StaticResource ImageGridView_ItemTemplate}"
                  ItemContainerStyle="{StaticResource ImageGridView_ItemContainerStyle}"
                  ItemsPanel="{StaticResource ImageGridView_ItemsPanelTemplate}"
                  ContainerContentChanging="ImageGridView_ContainerContentChanging">
        </GridView>
    </Grid>
</Page>

Forse ricorderai che la soluzione desktop aveva anche un file MainWindow.xaml.cs che conteneva code-behind che corrisponde alla visualizzazione. Nel progetto Uno Platform il code-behind per la visualizzazione MainPage copiata è contenuto nel file MainPage.xaml.cs.

Per rendere questo code-behind multipiattaforma, è necessario prima spostare il codice seguente nel file MainPage.xaml.cs:

  • Proprietà Images: fornisce GridView con una raccolta osservabile di file di immagine

  • Contenuto del costruttore: chiama GetItemsAsync() per popolare la raccolta Images con elementi che rappresentano i file di immagine

  • Rimuovere la modifica manuale della proprietà ImageGridView del controllo ItemsSource

  • Metodo ImageGridView_ContainerContentChanging: usato come parte di una strategia per caricare progressivamente elementi GridView mentre vengono fatti scorrere nella visualizzazione

  • Metodo ShowImage: carica i file di immagine nell'oggetto GridView

  • Metodo GetItemsAsync: ottiene i file della risorsa immagine dalla cartella Samples

  • Metodo LoadImageInfoAsync: costruisce un oggetto ImageFileInfo da un StorageFile creato

Dopo aver spostato tutto, MainPage.xaml.cs dovrebbe risultare come segue:

using Microsoft.UI.Xaml.Controls;
using System.Collections.ObjectModel;
using Windows.Storage;
using Windows.Storage.Search;

namespace UnoSimplePhotos;

public sealed partial class MainPage : Page
{
    public ObservableCollection<ImageFileInfo> Images { get; } 
    = new ObservableCollection<ImageFileInfo>();

    public MainPage()
    {
        this.InitializeComponent();
        GetItemsAsync();
    }

    private void ImageGridView_ContainerContentChanging(ListViewBase sender,
        ContainerContentChangingEventArgs args)
    {
        if (args.InRecycleQueue)
        {
            var templateRoot = args.ItemContainer.ContentTemplateRoot as Grid;
            var image = templateRoot.FindName("ItemImage") as Image;
            image.Source = null;
        }

        if (args.Phase == 0)
        {
            args.RegisterUpdateCallback(ShowImage);
            args.Handled = true;
        }
    }

    private async void ShowImage(ListViewBase sender, ContainerContentChangingEventArgs args)
    {
        if (args.Phase == 1)
        {
            // It's phase 1, so show this item's image.
            var templateRoot = args.ItemContainer.ContentTemplateRoot as Grid;
            var image = templateRoot.FindName("ItemImage") as Image;
            var item = args.Item as ImageFileInfo;
            image.Source = await item.GetImageThumbnailAsync();
        }
    }

    private async Task GetItemsAsync()
    {
        StorageFolder appInstalledFolder = Package.Current.InstalledLocation;
        StorageFolder picturesFolder = await appInstalledFolder.GetFolderAsync("Assets\\Samples");

        var result = picturesFolder.CreateFileQueryWithOptions(new QueryOptions());

        IReadOnlyList<StorageFile> imageFiles = await result.GetFilesAsync();
        foreach (StorageFile file in imageFiles)
        {
            Images.Add(await LoadImageInfoAsync(file));
        }
    }

    public async static Task<ImageFileInfo> LoadImageInfoAsync(StorageFile file)
    {
        var properties = await file.Properties.GetImagePropertiesAsync();
        ImageFileInfo info = new(properties,
                                    file, file.DisplayName, file.DisplayType);

        return info;
    }
}

Nota

I file nel progetto dell’app Uno devono essere usare UnoSimplePhotos come spazio dei nomi.

Finora, i file per la visualizzazione principale con cui stiamo lavorando contengono tutte le funzionalità della soluzione desktop. Dopo aver copiato il file del modello ImageFileInfo.cs, capiremo come modificare i blocchi orientati al desktop del codice per la compatibilità multipiattaforma.

Copiare ImageFileInfo dal progetto desktop e incollarlo nel file ImageFileInfo.cs. Apportare le modifiche seguenti:

  • Rinominare lo spazio dei nomi in modo che sia UnoSimplePhotos invece di SimplePhotos:

    // Found towards the top of the file
    namespace UnoSimplePhotos;
    
  • Modificare il tipo di parametro del metodo OnPropertyChanged in modo che sia annullabile:

    // string -> string?
    protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    ...
    
  • Rendere annullabile PropertyChangedEventHandler :

    // PropertyChangedEventHandler -> PropertyChangedEventHandler?
    public event PropertyChangedEventHandler? PropertyChanged;
    

Mettere insieme, ora il file dovrebbe risultare come segue:

using Microsoft.UI.Xaml.Media.Imaging;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Windows.Storage;
using Windows.Storage.FileProperties;
using Windows.Storage.Streams;
using ThumbnailMode = Windows.Storage.FileProperties.ThumbnailMode;

namespace UnoSimplePhotos;

public class ImageFileInfo : INotifyPropertyChanged
{
    public ImageFileInfo(ImageProperties properties,
        StorageFile imageFile,
        string name,
        string type)
    {
        ImageProperties = properties;
        ImageName = name;
        ImageFileType = type;
        ImageFile = imageFile;
        var rating = (int)properties.Rating;
        var random = new Random();
        ImageRating = rating == 0 ? random.Next(1, 5) : rating;
    }

    public StorageFile ImageFile { get; }

    public ImageProperties ImageProperties { get; }

    public async Task<BitmapImage> GetImageSourceAsync()
    {
        using IRandomAccessStream fileStream = await ImageFile.OpenReadAsync();

        // Create a bitmap to be the image source.
        BitmapImage bitmapImage = new();
        bitmapImage.SetSource(fileStream);

        return bitmapImage;
    }

    public async Task<BitmapImage> GetImageThumbnailAsync()
    {
        StorageItemThumbnail thumbnail =
            await ImageFile.GetThumbnailAsync(ThumbnailMode.PicturesView);
        // Create a bitmap to be the image source.
        var bitmapImage = new BitmapImage();
        bitmapImage.SetSource(thumbnail);
        thumbnail.Dispose();

        return bitmapImage;
    }

    public string ImageName { get; }

    public string ImageFileType { get; }

    public string ImageDimensions => $"{ImageProperties.Width} x {ImageProperties.Height}";

    public string ImageTitle
    {
        get => string.IsNullOrEmpty(ImageProperties.Title) ? ImageName : ImageProperties.Title;
        set
        {
            if (ImageProperties.Title != value)
            {
                ImageProperties.Title = value;
                _ = ImageProperties.SavePropertiesAsync();
                OnPropertyChanged();
            }
        }
    }

    public int ImageRating
    {
        get => (int)ImageProperties.Rating;
        set
        {
            if (ImageProperties.Rating != value)
            {
                ImageProperties.Rating = (uint)value;
                _ = ImageProperties.SavePropertiesAsync();
                OnPropertyChanged();
            }
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Questa classe fungerà da modello per rappresentare i file di immagine in GridView. Sebbene sia tecnicamente possibile eseguire l'app a questo punto, potrebbe non eseguire correttamente il rendering delle immagini o visualizzare le relative proprietà. Nelle sezioni successive apporteremo una serie di modifiche a questi file copiati per renderli compatibili in un contesto multipiattaforma.

Usare le direttive per il preprocessore

Nel progetto desktop dell'esercitazione precedente il file MainPage.xaml.cs contiene un metodo GetItemsAsync che elenca gli elementi da StorageFolder che rappresenta il percorso del pacchetto installato. Poiché tale percorso non è disponibile su determinate piattaforme, come WebAssembly, è necessario apportare modifiche a questo metodo per renderlo compatibile con tutte le piattaforme. Di conseguenza verranno apportate alcune modifiche alla classe ImageFileInfo per garantire la compatibilità.

Prima di tutto, apportare le modifiche necessarie al metodo GetItemsAsync. Sostituire il metodo GetItemsAsync nel file MainPage.xaml.cs con il seguente codice:

private async Task GetItemsAsync()
{
#if WINDOWS
    StorageFolder appInstalledFolder = Package.Current.InstalledLocation;
    StorageFolder picturesFolder = await appInstalledFolder.GetFolderAsync("UnoSimplePhotos\\Assets\\Samples");

    var result = picturesFolder.CreateFileQueryWithOptions(new QueryOptions());

    IReadOnlyList<StorageFile> imageFiles = await result.GetFilesAsync();
#else
    var imageFileNames = Enumerable.Range(1, 20).Select(i => new Uri($"ms-appx:///UnoSimplePhotos/Assets/Samples/{i}.jpg"));
    var imageFiles = new List<StorageFile>();

    foreach (var file in imageFileNames)
    {
        imageFiles.Add(await StorageFile.GetFileFromApplicationUriAsync(file));
    }
#endif
    foreach (StorageFile file in imageFiles)
    {
        Images.Add(await LoadImageInfoAsync(file));
    }
}

Questo metodo usa ora una direttiva per il preprocessore per determinare il codice da eseguire in base alla piattaforma. In Windows, il metodo ottiene l'oggetto StorageFolder che rappresenta il percorso del pacchetto installato e ne restituisce la cartella Samples. In altre piattaforme, il metodo conta fino a 20, recuperando i file di immagine dalla cartella Samples usando un Uri per rappresentare il file di immagine.

Modificare quindi il metodo LoadImageInfoAsync per adattare le modifiche apportate al metodo GetItemsAsync. Sostituire il metodo LoadImageInfoAsync nel file MainPage.xaml.cs con il seguente codice:

public async static Task<ImageFileInfo> LoadImageInfoAsync(StorageFile file)
{
#if WINDOWS
    var properties = await file.Properties.GetImagePropertiesAsync();
    ImageFileInfo info = new(properties,
                                file, file.DisplayName, $"{file.FileType} file");
#else
    ImageFileInfo info = new(file, file.DisplayName, $"{file.FileType} file");
#endif
    return info;
}

Così come il metodo GetItemsAsync, anche questo metodo ora usa una direttiva per il preprocessore per determinare il codice da eseguire in base alla piattaforma. In Windows il metodo ottiene l'oggetto ImageProperties da StorageFile e lo usa per creare un oggetto ImageFileInfo. In altre piattaforme il metodo costruisce un oggetto ImageFileInfo senza il parametro ImageProperties. Successivamente, verranno apportate modifiche alla classe ImageFileInfo per supportare questa modifica.

Controlli come GridView consentono il caricamento progressivo del contenuto del contenitore di elementi aggiornato mentre vengono fatte scorrere nel riquadro di visualizzazione. A tale scopo, viene usato l'evento ContainerContentChanging. Nel progetto desktop dell'esercitazione precedente il metodo ImageGridView_ContainerContentChanging usa questo evento per caricare i file di immagine in GridView. Poiché alcuni aspetti di questo evento non sono supportati in tutte le piattaforme, è necessario apportare modifiche a questo metodo per renderlo compatibile.

Diagramma del viewport del controllo raccolta.

Ad esempio, la proprietà ContainerContentChangingEventArgs.Phase attualmente non è supportata su piattaforme diverse da Windows. Sarà necessario apportare modifiche al metodo ImageGridView_ContainerContentChanging per soddisfare questa modifica. Sostituire il metodo ImageGridView_ContainerContentChanging nel file MainPage.xaml.cs con il seguente codice:

private void ImageGridView_ContainerContentChanging(
ListViewBase sender,
ContainerContentChangingEventArgs args)
{

    if (args.InRecycleQueue)
    {
        var templateRoot = args.ItemContainer.ContentTemplateRoot as Grid;
        var image = templateRoot?.FindName("ItemImage") as Image;
        if (image is not null)
        {
            image.Source = null;
        }
    }

#if WINDOWS
        if (args.Phase == 0)
        {
            args.RegisterUpdateCallback(ShowImage);
            args.Handled = true;
        }
#else
    ShowImage(sender, args);
#endif
}

Il callback specializzato è ora registrato solo usando ContainerContentChangingEventArgs.RegisterUpdateCallback() se la piattaforma è Windows. In caso contrario, il metodo ShowImage viene chiamato direttamente. Sarà anche necessario apportare modifiche al metodo ShowImage per lavorare insieme alle modifiche apportate al metodo ImageGridView_ContainerContentChanging. Sostituire il metodo ShowImage nel file MainPage.xaml.cs con il seguente codice:

private async void ShowImage(ListViewBase sender, ContainerContentChangingEventArgs args)
{
    if (
#if WINDOWS
            args.Phase == 1
#else
        true
#endif
        )
    {

        // It's phase 1, so show this item's image.
        var templateRoot = args.ItemContainer.ContentTemplateRoot as Grid;
        var image = templateRoot?.FindName("ItemImage") as Image;
        var item = args.Item as ImageFileInfo;
#if WINDOWS
        if (image is not null && item is not null)
        {
            image.Source = await item.GetImageThumbnailAsync();
        }
#else
        if (item is not null)
        {
            await item.GetImageSourceAsync();
        }
#endif
    }
}

Anche in questo caso, le direttive per il preprocessore assicurano che la proprietà ContainerContentChangingEventArgs.Phase venga usata solo nelle piattaforme in cui è supportata. Usiamo il metodo precedentemente inutilizzato GetImageSourceAsync() per caricare i file di immagine in GridView nelle piattaforme diverse da Windows. A questo punto, le modifiche apportate in precedenza verranno modificate modificando la classe ImageFileInfo.

Creazione di un percorso di codice separato per altre piattaforme

Aggiornare ImageFileInfo.cs per includere una nuova proprietà denominata ImageSource che verrà usata per caricare il file di immagine.

public BitmapImage? ImageSource { get; private set; }

Poiché le piattaforme come quelle Web non supportano proprietà avanzate dei file di immagine che sono facilmente disponibili in Windows, verrà aggiunto un overload del costruttore che non richiede un parametro ImageProperties tipizzato. Aggiungere il nuovo overload dopo quello esistente usando il codice seguente:

public ImageFileInfo(StorageFile imageFile,
    string name,
    string type)
{
    ImageName = name;
    ImageFileType = type;
    ImageFile = imageFile;
}

Questo overload del costruttore viene usato per costruire un oggetto ImageFileInfo su piattaforme diverse da Windows. Dal momento che abbiamo effettuato questa operazione, è opportuno rendere annullabile la proprietà ImageProperties. Aggiornare la proprietà ImageProperties in modo che sia annullabile usando il codice seguente:

public ImageProperties? ImageProperties { get; }

Aggiornare il metodo GetImageSourceAsync per utilizzare la proprietà ImageSource anziché restituire solo un oggetto BitmapImage. Sostituire il metodo GetImageSourceAsync nel file ImageFileInfo.cs con il seguente codice:

public async Task<BitmapImage> GetImageSourceAsync()
{
    using IRandomAccessStream fileStream = await ImageFile.OpenReadAsync();

    // Create a bitmap to be the image source.
    BitmapImage bitmapImage = new();
    bitmapImage.SetSource(fileStream);

    ImageSource = bitmapImage;
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImageSource)));

    return bitmapImage;
}

Per evitare di ottenere il valore di ImageProperties quando è annullabile, apportare le modifiche seguenti:

  • Modificare la proprietà ImageDimensions per usare l'operatore condizionale nullo:

    public string ImageDimensions => $"{ImageProperties?.Width} x {ImageProperties?.Height}";
    
  • Modificare la proprietà ImageTitle in modo da usare l'operatore condizionale nullo:

    public string ImageTitle
    {
        get => string.IsNullOrEmpty(ImageProperties?.Title) ? ImageName : ImageProperties?.Title;
        set
        {
            if (ImageProperties is not null)
            {
                if (ImageProperties.Title != value)
                {
                    ImageProperties.Title = value;
                    _ = ImageProperties.SavePropertiesAsync();
                    OnPropertyChanged();
                }
            }
        }
    }
    
  • Modificare ImageRating in modo da non dover dipendere da ImageProperties generando una classificazione a stella casuale a scopo dimostrativo:

    public int ImageRating
    {
        get => (int)((ImageProperties?.Rating == null || ImageProperties.Rating == 0) ? (uint)Random.Shared.Next(1, 5) : ImageProperties.Rating);
        set
        {
            if (ImageProperties is not null)
            {
                if (ImageProperties.Rating != value)
                {
                    ImageProperties.Rating = (uint)value;
                    _ = ImageProperties.SavePropertiesAsync();
                    OnPropertyChanged();
                }
            }
        }
    }
    
  • Aggiornare il costruttore che genera un numero intero casuale per non eseguire più questa operazione:

    public ImageFileInfo(ImageProperties properties,
        StorageFile imageFile,
        string name,
        string type)
    {
        ImageProperties = properties;
        ImageName = name;
        ImageFileType = type;
        ImageFile = imageFile;
    }
    

Con queste modifiche, la classe ImageFileInfo deve contenere il codice seguente. Ora ha un percorso di codice separato per le piattaforme diverse da Windows:

using Microsoft.UI.Xaml.Media.Imaging;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Windows.Storage;
using Windows.Storage.FileProperties;
using Windows.Storage.Streams;
using ThumbnailMode = Windows.Storage.FileProperties.ThumbnailMode;

namespace UnoSimplePhotos;

public class ImageFileInfo : INotifyPropertyChanged
{
    public BitmapImage? ImageSource { get; private set; }

    public ImageFileInfo(ImageProperties properties,
        StorageFile imageFile,
        string name,
        string type)
    {
        ImageProperties = properties;
        ImageName = name;
        ImageFileType = type;
        ImageFile = imageFile;
    }

    public ImageFileInfo(StorageFile imageFile,
        string name,
        string type)
    {
        ImageName = name;
        ImageFileType = type;
        ImageFile = imageFile;
    }

    public StorageFile ImageFile { get; }

    public ImageProperties? ImageProperties { get; }

    public async Task<BitmapImage> GetImageSourceAsync()
    {
        using IRandomAccessStream fileStream = await ImageFile.OpenReadAsync();

        // Create a bitmap to be the image source.
        BitmapImage bitmapImage = new();
        bitmapImage.SetSource(fileStream);

        ImageSource = bitmapImage;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImageSource)));

        return bitmapImage;
    }

    public async Task<BitmapImage> GetImageThumbnailAsync()
    {
        StorageItemThumbnail thumbnail =
            await ImageFile.GetThumbnailAsync(ThumbnailMode.PicturesView);
        // Create a bitmap to be the image source.
        var bitmapImage = new BitmapImage();
        bitmapImage.SetSource(thumbnail);
        thumbnail.Dispose();

        return bitmapImage;
    }

    public string ImageName { get; }

    public string ImageFileType { get; }

    public string ImageDimensions => $"{ImageProperties?.Width} x {ImageProperties?.Height}";

    public string ImageTitle
    {
        get => string.IsNullOrEmpty(ImageProperties?.Title) ? ImageName : ImageProperties.Title;
        set
        {
            if (ImageProperties is not null)
            {
                if (ImageProperties.Title != value)
                {
                    ImageProperties.Title = value;
                    _ = ImageProperties.SavePropertiesAsync();
                    OnPropertyChanged();
                }
            }
        }
    }

    public int ImageRating
    {
        get => (int)((ImageProperties?.Rating == null || ImageProperties.Rating == 0) ? (uint)Random.Shared.Next(1, 5) : ImageProperties.Rating);
        set
        {
            if (ImageProperties is not null)
            {
                if (ImageProperties.Rating != value)
                {
                    ImageProperties.Rating = (uint)value;
                    _ = ImageProperties.SavePropertiesAsync();
                    OnPropertyChanged();
                }
            }
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

La classe ImageFileInfo viene usata per rappresentare i file di immagine in GridView. Infine, verranno apportate modifiche al file MainPage.xaml per adattare le modifiche apportate al modello.

Uso del markup XAML specifico della piattaforma

Ci sono un paio di elementi nel markup di visualizzazione che devono essere valutati solo su Windows. Aggiungere un nuovo spazio dei nomi sull'elemento Page del file MainPage.xaml come segue:

...
xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

Ora in MainPage.xaml, sostituire il setter della proprietà ItemsPanel sull'elemento GridView con il seguente codice:

win:ItemsPanel="{StaticResource ImageGridView_ItemsPanelTemplate}"

Anteponendo il nome della proprietà con win: assicura che la proprietà sia impostata solo su Windows. Eseguire di nuovo questa operazione all'interno della risorsa ImageGridView_ItemTemplate. Vogliamo caricare solo gli elementi che usano la proprietà ImageDimensions su Windows. Sostituire l'elemento TextBlock che usa la proprietà ImageDimensions con il seguente codice:

<win:TextBlock Text="{x:Bind ImageDimensions}"
               HorizontalAlignment="Center"
               Style="{StaticResource CaptionTextBlockStyle}"
               Margin="8,0,0,0" />

Il file MainPage.xaml dovrebbe risultare come segue:

<Page x:Class="UnoSimplePhotos.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:UnoSimplePhotos"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      mc:Ignorable="d"
      Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="ImageGridView_ItemTemplate"
                          x:DataType="local:ImageFileInfo">
                <Grid Height="300"
                      Width="300"
                      Margin="8">
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>

                    <Image x:Name="ItemImage"
                           Source="{x:Bind ImageSource}"
                           Stretch="Uniform" />

                    <StackPanel Orientation="Vertical"
                                Grid.Row="1">
                        <TextBlock Text="{x:Bind ImageTitle}"
                                   HorizontalAlignment="Center"
                                   Style="{StaticResource SubtitleTextBlockStyle}" />
                        <StackPanel Orientation="Horizontal"
                                    HorizontalAlignment="Center">
                            <TextBlock Text="{x:Bind ImageFileType}"
                                       HorizontalAlignment="Center"
                                       Style="{StaticResource CaptionTextBlockStyle}" />
                            <win:TextBlock Text="{x:Bind ImageDimensions}"
                                           HorizontalAlignment="Center"
                                           Style="{StaticResource CaptionTextBlockStyle}"
                                           Margin="8,0,0,0" />
                        </StackPanel>

                        <RatingControl Value="{x:Bind ImageRating}"
                                       IsReadOnly="True" />
                    </StackPanel>
                </Grid>
            </DataTemplate>
            
            <Style x:Key="ImageGridView_ItemContainerStyle"
                   TargetType="GridViewItem">
                <Setter Property="Background"
                        Value="Gray" />
                <Setter Property="Margin" 
                        Value="8"/>
            </Style>

            <ItemsPanelTemplate x:Key="ImageGridView_ItemsPanelTemplate">
                <ItemsWrapGrid Orientation="Horizontal"
                               HorizontalAlignment="Center"/>
            </ItemsPanelTemplate>
        </Grid.Resources>

        <GridView x:Name="ImageGridView"
                  ItemsSource="{x:Bind Images, Mode=OneWay}"
                  win:ItemsPanel="{StaticResource ImageGridView_ItemsPanelTemplate}"
                  ContainerContentChanging="ImageGridView_ContainerContentChanging"
                  ItemContainerStyle="{StaticResource ImageGridView_ItemContainerStyle}"
                  ItemTemplate="{StaticResource ImageGridView_ItemTemplate}" />
    </Grid>
</Page>

Esecuzione dell'app

Avviare la destinazione UnoSimplePhotos.Windows. Bisogna tenere presente che questa app WinUI è molto simile all'esercitazione precedente.

Ora è possibile creare ed eseguire l'app in una qualsiasi delle piattaforme supportate. A tale scopo, è possibile usare l'elenco a discesa della barra degli strumenti di debug per selezionare una piattaforma di destinazione da distribuire:

  • Esecuzione dell'intestazione WebAssembly (Wasm):

    • Fare clic con il pulsante destro del mouse sul progetto UnoSimplePhotos.Wasm, scegliere Imposta come progetto di avvio
    • Premere il pulsante UnoSimplePhotos.Wasm per distribuire l'app
    • Eventualmente è possibile aggiungere e usare il progetto UnoSimplePhotos.Server come alternativa
  • Esecuzione del debug per iOS:

    • Fare clic con il pulsante destro del mouse sul progetto UnoSimplePhotos.Mobile e scegliere Imposta come progetto di avvio

    • Nell'elenco a discesa della barra degli strumenti di debug selezionare un dispositivo iOS attivo o il simulatore. Dovrà essere associato a un Mac per consentire il funzionamento.

      Screenshot dell'elenco a discesa di Visual Studio per selezionare un framework di destinazione da distribuire.

  • Esecuzione del debug per Mac Catalyst:

    • Fare clic con il pulsante destro del mouse sul progetto UnoSimplePhotos.Mobile e scegliere Imposta come progetto di avvio
    • Nell'elenco a discesa della barra degli strumenti di debug selezionare un dispositivo macOS remoto. Sarà necessario associarlo a uno per consentire il funzionamento.
  • Esecuzione del debug della piattaforma Android:

    • Fare clic con il pulsante destro del mouse sul progetto UnoSimplePhotos.Mobile e scegliere Imposta come progetto di avvio
    • Nell'elenco a discesa della barra degli strumenti di debug selezionare un dispositivo Android attivo o l'emulatore
      • Selezionare un dispositivo attivo nel sottomenu "Dispositivo"
  • Esecuzione del debug su Linux con Skia GTK:

    • Fare clic con il pulsante destro del mouse sul progetto UnoSimplePhotos.Skia.Gtk e scegliere Imposta come progetto di avvio
    • Premere il pulsante UnoSimplePhotos.Skia.Gtk per distribuire l'app

Vedi anche