Поделиться через


Руководство. Создание простого средства просмотра фотографий, предназначенных для нескольких платформ

После создания начального простого приложения средства просмотра фотографий WinUI 3 вы можете задуматься о том, как получить доступ к большему числу пользователей, не перезаписав приложение. В этом руководстве используется uno Platform для расширения доступа к существующему приложению WinUI C# 3, позволяющему повторно использовать бизнес-логику и уровень пользовательского интерфейса в собственном мобильном, веб-приложении и настольном компьютере. С минимальными изменениями в простом приложении просмотра фотографий мы можем запустить идеальную копию приложения, перенесенного на эти платформы.

Снимок экрана: приложение UnoSimplePhoto, предназначенное для веб-сайта и рабочего стола WinUI.

Необходимые компоненты

  • Visual Studio 2022 17.4 или более поздней версии

  • Настройка компьютера разработки (см. статью "Начало работы с WinUI")

  • ASP.NET и рабочую нагрузку веб-разработки (для разработки WebAssembly)

    Снимок экрана: рабочая нагрузка веб-разработки в Visual Studio.

  • Разработка многоплатформенного пользовательского интерфейса приложений .NET (для разработки iOS, Android, Mac Catalyst)

    Снимок экрана: рабочая нагрузка dotnet mobile в Visual Studio.

  • Установленная разработка классических приложений .NET (для разработки Gtk, Wpf и Linux Framebuffer)

    Снимок экрана: рабочая нагрузка dotnet desktop в Visual Studio.

Завершение работы среды

  1. Откройте командную строку, Терминал Windows, если она установлена, или в командной строке или Windows PowerShell из меню "Пуск".

  2. Установите или обновите uno-check средство:

    • Используйте следующую команду:

      dotnet tool install -g uno.check
      
    • Чтобы обновить средство, если вы уже установили старую версию, выполните следующие действия.

      dotnet tool update -g uno.check
      
  3. Запустите средство с помощью следующей команды:

    uno-check
    
  4. Следуйте инструкциям, указанным средством. Так как она должна изменить систему, вам может потребоваться разрешение с повышенными привилегиями.

Установка шаблонов решений Uno Platform

Запустите Visual Studio, а затем щелкните Continue without code. Щелкните Extensions в>Manage Extensions строке меню.

Снимок экрана: элемент строки меню Visual Studio, считывающий расширения.

В диспетчере расширений разверните узел Online и найдите Unoего, установите Uno Platform расширение или скачайте и установите его из Visual Studio Marketplace, а затем перезапустите Visual Studio.

Снимок экрана: окно управления расширениями в Visual Studio с расширением Uno Platform в качестве результата поиска.

Создание приложения

Теперь, когда мы готовы создать мультиплатформенное приложение, мы рассмотрим, как создать новое приложение Uno Platform. Мы скопируйм код из проекта SimplePhotos WinUI 3 предыдущего руководства в проект с несколькими платформами. Это возможно, так как Uno Platform позволяет повторно использовать существующую базу кода. Для функций, зависящих от API ОС, предоставляемых каждой платформой, их можно легко работать с течением времени. Этот подход особенно полезен, если у вас есть существующее приложение, которое вы хотите перенести на другие платформы.

В ближайшее время вы сможете получить преимущества этого подхода, так как вы можете нацелиться на более платформы с знакомым вкусом XAML и базой кода, у вас уже есть.

Откройте Visual Studio и создайте проект с помощьюFile>>NewProject:

Снимок экрана: диалоговое окно создания проекта.

Найдите uno и выберите шаблон проекта приложения Uno Platform App:

Снимок экрана: диалоговое окно создания проекта с приложением Uno Platform в качестве выбранного типа проекта.

Создайте новое решение C# с помощью типа приложения Uno Platform App на начальной странице Visual Studio. Чтобы избежать конфликтов с кодом из предыдущего руководства, мы предоставим этому решению другое имя UnoSimplePhotos. Укажите имя проекта, имя решения и каталог. В этом примере проект UnoSimplePhotos с несколькими платформами принадлежит UnoSimplePhotos решению, которое будет жить в C:\Projects:

Снимок экрана: указание сведений о проекте для нового проекта Uno Platform.

Теперь вы выберете базовый шаблон, чтобы сделать приложение простой коллекции фотографий мультиплатформенным.

Шаблон приложения Uno Platform поставляется с двумя предварительно настроенными параметрами, которые позволяют быстро приступить к работе с пустым решением или конфигурацией по умолчанию , которая включает ссылки на библиотеки Uno.Material и Uno.Toolkit. Конфигурация по умолчанию также включает Uno.Extensions, которая используется для внедрения зависимостей, настройки, навигации и ведения журнала. Кроме того, он использует MVUX вместо MVVM, что делает его отличной отправной точкой для быстрого создания реальных приложений.

Снимок экрана: шаблон решения Uno для типа запуска проекта.

Чтобы упростить работу, выберите пустой предустановку. Затем нажмите кнопку "Создать ". Дождитесь создания проектов и их зависимостей.

Баннер в верхней части редактора может попросить перезагрузить проекты, щелкните " Перезагрузить проекты":

Снимок экрана: баннер Visual Studio для перезагрузки проектов для завершения изменений.

В Обозреватель решений должна появиться следующая структура файлов по умолчанию:

Снимок экрана: структура файлов по умолчанию в Обозреватель решений.

Добавление ресурсов изображений в проект

Вашему приложению потребуется отобразить некоторые изображения. Вы можете использовать те же изображения из предыдущего руководства.

UnoSimplePhotos В проекте создайте новую папку с именем Assets и скопируйте файлы изображений JPG в вложенную папкуSamples. Теперь Assets структура папок должна выглядеть следующим образом:

Снимок экрана: панель Обозреватель решений в Visual Studio с добавленными новыми файлами и папками.

Дополнительные сведения о создании Assets папки и добавлении в нее изображений см. в документации по uno Platform о ресурсах и изображении.

Подготовка приложения

Теперь, когда вы создали функциональную начальную точку мультиплатформенного приложения WinUI, вы можете скопировать код в него из классического проекта.

Копирование представления

Так как Uno Platform позволяет использовать уже знакомый вами вкус XAML, можно скопировать тот же код, который вы создали в предыдущем руководстве.

Вернитесь к проекту SimplePhotos из предыдущего руководства. В Обозреватель решений найдите файл с именем MainWindow.xaml и откройте его. Обратите внимание, что содержимое представления определяется в элементе Window , а не в элементе Page. Это связано с тем, что классический проект является приложением WinUI 3, которое может использовать Window элементы для определения содержимого представления:

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

Разоложите мультиплатформенную реализацию элементов управления, найденных в Window элементе, например ImageGridView, и RatingControlубедитесь, что представление будет работать на всех поддерживаемых платформах только с тривиальным объемом усилий. Скопируйте его содержимое Window и вставьте их Page в элемент MainPage.xaml файла в проекте UnoSimplePhotos Uno Platform. Представление MainPage XAML должно выглядеть следующим образом:

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

Вы можете вспомнить, что в классическом решении также есть MainWindow.xaml.cs файл, содержащий код, который соответствует представлению. В проекте Uno Platform код MainPage для представления, скопированного в файл, содержится в MainPage.xaml.cs файле.

Чтобы перенести этот код за несколькими платформами, сначала необходимо переместить следующий код в MainPage.xaml.cs файл:

  • Images свойство: предоставляет GridView наблюдаемую коллекцию файлов изображений

  • Содержимое конструктора: вызовы GetItemsAsync() для заполнения Images коллекции элементами, представляющими файлы изображений

  • Удаление ручного ImageGridView изменения свойства элемента управления ItemsSource

  • ImageGridView_ContainerContentChangingметод. Используется в рамках стратегии для постепенной загрузки GridView элементов по мере прокрутки в представление

  • ShowImage метод. Загружает файлы изображений в файл GridView

  • GetItemsAsync метод. Возвращает файлы ресурсов образа из Samples папки

  • LoadImageInfoAsync метод: создает ImageFileInfo объект из созданного объекта StorageFile

После перемещения всего, MainPage.xaml.cs теперь должен выглядеть следующим образом:

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;
    }
}

Примечание.

Файлы в проекте приложения Uno должны использоваться UnoSimplePhotos в качестве пространства имен.

До сих пор файлы для основного представления, с которыми мы работаем, содержат все возможности классического решения. После копирования файла модели мы научимся ImageFileInfo.cs изменять классические блоки кода для совместимости с несколькими платформами.

Скопируйте ImageFileInfo из классического проекта и вставьте его в ImageFileInfo.cs файл. Внесите следующие изменения:

  • Переименуйте пространство имен вместо UnoSimplePhotos SimplePhotos:

    // Found towards the top of the file
    namespace UnoSimplePhotos;
    
  • Измените тип OnPropertyChanged параметра метода, чтобы иметь значение NULL:

    // string -> string?
    protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    ...
    
  • Сделайте значение NULL допустимым PropertyChangedEventHandler :

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

Сложенный файл должен выглядеть следующим образом:

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));
}

Этот класс будет служить моделью для представления файлов изображений в объекте GridView. Хотя на этом этапе приложение должно быть технически возможно, он не может правильно отображать изображения или отображать их свойства. В следующих разделах мы введем набор изменений в эти скопированные файлы, чтобы сделать их совместимыми в контексте с несколькими платформами.

Использование директив препроцессора

В классическом проекте из предыдущего руководства MainPage.xaml.cs файл содержит GetItemsAsync метод, перечисляющий элементы из установленного StorageFolder расположения пакета. Так как это расположение недоступно на определенных платформах, таких как WebAssembly, необходимо внести изменения в этот метод, чтобы сделать его совместимым со всеми платформами. Соответственно, мы внодем некоторые изменения в ImageFileInfo класс, чтобы обеспечить совместимость.

Сначала внесите необходимые изменения в GetItemsAsync метод. Замените GetItemsAsync метод в MainPage.xaml.cs файле следующим кодом:

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));
    }
}

Этот метод теперь использует директиву препроцессора, чтобы определить, какой код будет выполняться на основе платформы. В Windows метод получает StorageFolder представление установленного расположения пакета и возвращает папку из нее Samples . На других платформах метод подсчитывает до 20, получая файлы изображений из Samples папки с помощью Uri файла изображения для представления файла изображения.

Затем настройте LoadImageInfoAsync метод, чтобы вместить изменения, внесенные GetItemsAsync в метод. Замените LoadImageInfoAsync метод в MainPage.xaml.cs файле следующим кодом:

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;
}

Как и в GetItemsAsync случае с методом, этот метод теперь использует директиву препроцессора для определения кода, выполняемого на основе платформы. В Windows метод получает ImageProperties из StorageFile него и использует его для создания ImageFileInfo объекта. На других платформах метод создает ImageFileInfo объект без ImageProperties параметра. Позже изменения будут внесены в ImageFileInfo класс для удовлетворения этого изменения.

Элементы управления, такие как GridView возможность постепенной загрузки обновленного содержимого контейнера элементов по мере прокрутки в окно просмотра. Это делается с помощью ContainerContentChanging события. В классическом проекте из предыдущего руководства ImageGridView_ContainerContentChanging метод использует это событие для загрузки файлов изображений в файл GridView. Так как некоторые аспекты этого события не поддерживаются на всех платформах, нам потребуется внести изменения в этот метод, чтобы сделать его совместимым с ними.

Схема окна представления элементов управления коллекцией.

Например, ContainerContentChangingEventArgs.Phase свойство в настоящее время не поддерживается на платформах, отличных от Windows. Для удовлетворения этого изменения необходимо внести изменения в ImageGridView_ContainerContentChanging метод. Замените ImageGridView_ContainerContentChanging метод в MainPage.xaml.cs файле следующим кодом:

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
}

Специализированный обратный вызов теперь зарегистрирован только в ContainerContentChangingEventArgs.RegisterUpdateCallback() том случае, если платформа является Windows. ShowImage В противном случае метод вызывается напрямую. Мы также должны внести изменения в ShowImage метод, чтобы работать вместе с изменениями, внесенными в ImageGridView_ContainerContentChanging метод. Замените ShowImage метод в MainPage.xaml.cs файле следующим кодом:

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
    }
}

Опять же, директивы препроцессора гарантируют, что ContainerContentChangingEventArgs.Phase свойство используется только на платформах, где она поддерживается. Мы используем ранее неиспользуемый GetImageSourceAsync() метод для загрузки файлов изображений на GridView платформы, отличные от Windows. На этом этапе мы будем учесть изменения, внесенные выше, изменив ImageFileInfo класс.

Создание отдельного пути кода для других платформ

Обновите ImageFileInfo.cs новое свойство, которое ImageSource будет использоваться для загрузки файла образа.

public BitmapImage? ImageSource { get; private set; }

Так как платформы, такие как Интернет, не поддерживают расширенные свойства файлов изображений, которые легко доступны в Windows, мы добавим перегрузку конструктора, которая не требует типизированного ImageProperties параметра. Добавьте новую перегрузку после существующего с помощью следующего кода:

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

Эта перегрузка конструктора используется для создания ImageFileInfo объекта на платформах, отличных от Windows. Так как мы сделали это, имеет смысл сделать ImageProperties свойство nullable. ImageProperties Обновите свойство, чтобы иметь значение NULL, используя следующий код:

public ImageProperties? ImageProperties { get; }

GetImageSourceAsync Обновите метод, чтобы использовать ImageSource свойство вместо возврата BitmapImage только объекта. Замените GetImageSourceAsync метод в ImageFileInfo.cs файле следующим кодом:

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;
}

Чтобы предотвратить получение значения ImageProperties , когда оно равно NULL, внесите следующие изменения:

  • Измените ImageDimensions свойство, чтобы использовать условный оператор NULL:

    public string ImageDimensions => $"{ImageProperties?.Width} x {ImageProperties?.Height}";
    
  • Измените ImageTitle свойство, чтобы использовать условный оператор NULL:

    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();
                }
            }
        }
    }
    
  • Изменение ImageRating , чтобы не полагаться ImageProperties , создав случайный рейтинг звезд для демонстрационных целей:

    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 ImageFileInfo(ImageProperties properties,
        StorageFile imageFile,
        string name,
        string type)
    {
        ImageProperties = properties;
        ImageName = name;
        ImageFileType = type;
        ImageFile = imageFile;
    }
    

При этих изменениях ImageFileInfo класс должен содержать следующий код. Теперь он имеет недавно разделенный путь кода для платформ, отличных от 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));
}

Этот ImageFileInfo класс используется для представления файлов изображений в файле GridView. Наконец, мы внодим изменения MainPage.xaml в файл, чтобы вместить изменения в модель.

Использование разметки XAML для конкретной платформы

В разметке представления есть несколько элементов, которые должны оцениваться только в Windows. Добавьте новое пространство имен в Page элемент MainPage.xaml файла следующим образом:

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

Теперь замените MainPage.xamlItemsPanel метод задания свойств в GridView элементе следующим кодом:

win:ItemsPanel="{StaticResource ImageGridView_ItemsPanelTemplate}"

Предустановка имени win: свойства гарантирует, что свойство задано только в Windows. Повторите это в ресурсе ImageGridView_ItemTemplate . Мы хотим загрузить только элементы, использующие ImageDimensions свойство в Windows. Замените TextBlock элемент, использующий ImageDimensions свойство следующим кодом:

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

Теперь MainPage.xaml файл должен выглядеть следующим образом:

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

Запуск приложения

Запустите целевой UnoSimplePhotos.Windows объект. Обратите внимание, что это приложение WinUI очень похоже на предыдущее руководство.

Теперь вы можете создать и запустить приложение на любой из поддерживаемых платформ. Для этого можно использовать раскрывающийся список панели инструментов отладки для выбора целевой платформы для развертывания:

  • Чтобы запустить голову WebAssembly (Wasm):

    • Щелкните проект правой UnoSimplePhotos.Wasm кнопкой мыши, выберите "Задать в качестве запускаемого проекта"
    • Нажмите кнопку UnoSimplePhotos.Wasm для развертывания приложения
    • При желании можно добавить и использовать UnoSimplePhotos.Server проект в качестве альтернативы
  • Отладка для iOS:

    • Щелкните проект правой UnoSimplePhotos.Mobile кнопкой мыши и выберите "Задать в качестве запускаемого проекта"

    • В раскрывающемся списке панели инструментов отладки выберите активное устройство iOS или симулятор. Для работы вам потребуется связаться с Mac.

      Снимок экрана: раскрывающийся список Visual Studio, чтобы выбрать целевую платформу для развертывания.

  • Отладка для Mac Catalyst:

    • Щелкните проект правой UnoSimplePhotos.Mobile кнопкой мыши и выберите "Задать в качестве запускаемого проекта"
    • В раскрывающемся списке панели инструментов отладки выберите удаленное устройство macOS. Для работы вам потребуется связаться с одним из них.
  • Отладка платформы Android :

    • Щелкните проект правой UnoSimplePhotos.Mobile кнопкой мыши и выберите "Задать в качестве запускаемого проекта"
    • В раскрывающемся списке панели инструментов отладки выберите активное устройство Android или эмулятор
      • Выберите активное устройство в подмене "Устройство"
  • Чтобы выполнить отладку в Linux с помощью Skia GTK, выполните следующую команду:

    • Щелкните проект правой UnoSimplePhotos.Skia.Gtk кнопкой мыши и выберите "Задать в качестве запускаемого проекта"
    • Нажмите кнопку UnoSimplePhotos.Skia.Gtk для развертывания приложения

См. также