教程:使用 WinUI 3 创建简单的照片查看器

注意

有关 WinUI 3 的优势的信息以及其他应用类型选项,请参阅应用开发选项概述

在本主题中,我们将演练在 Visual Studio 中创建新 WinUI 3 项目的过程,然后构建一个简单应用来显示照片。 我们将使用控件、布局面板和数据绑定。 我们将同时编写 XAML 标记(声明性)和你选择的 C# 或 C++ 代码(强制性或程序性)。 使用主题标题上方的语言选取器选择 C# 或 C++/WinRT。

提示

本主题中的源代码以 C# 和 C++/WinRT 提供。 如果你是 C++ 开发人员,有关我们在此处显示的代码的工作原理的更多详细信息和概念,请参阅 C++/WinRT 文档。 相关主题包括 XAML 控件;绑定到 C++/WinRT 属性XAML 项控件;绑定到 C++/WinRT 集合以及照片编辑器 C++/WinRT 示例应用程序

步骤 1:安装适用于 Windows 应用 SDK 的工具

若要设置开发计算机,请参阅安装适用于 Windows App SDK 的工具。 然后,可以选择按照创建你的第一个 WinUI 3 项目操作。

重要

你会发现发行说明主题以及 Windows 应用 SDK 发行通道主题。 每个通道都有发行说明。 请务必查看发行说明中的任何限制和已知问题,因为这些可能会影响按照本教程进行操作和/或运行我们将构建的应用的结果。

步骤 2:创建新项目

在 Visual Studio 中,通过“打包的空白应用(桌面版 WinUI 3)”项目模板创建一个你选择的新 C# 或 C++ 项目。 将项目命名为 SimplePhotos,并(以便文件夹结构与本教程中所述结构相匹配)取消选中“将解决方案和项目放在同一目录中”。 你可以针对客户端操作系统的最新版本(非预览版)。

步骤 3:复制资产文件

我们将要构建的应用以资产文件的形式随身携带图像文件;这些就是它显示的照片。 在本节中,你会将这些资产添加到项目中。 但首先需要获取文件的副本。

  1. 因此,克隆(或作为 .zip 下载)Windows 应用 SDK 示例存储库(请参阅 WindowsAppSDK-Samples)。 完成此操作后,你将找到要在文件夹 \WindowsAppSDK-Samples\Samples\PhotoEditor\cs-winui\Assets\Samples(将此文件夹同时用于 C# 和 C++/WinRT 项目)中使用的资产文件。 如果要联机在存储库中查看这些文件,则可以访问 WindowsAppSDK-Samples/Samples/PhotoEditor/cs-winui/Assets/Samples/

  2. 在文件资源管理器中,选择该“示例”文件夹,并将其复制到剪贴板。

  1. 转到 Visual Studio 中的解决方案资源管理器。 右键单击“Assets”文件夹(这是项目节点的子节点),然后单击“在文件资源管理器中打开文件夹”。 这将在文件资源管理器中打开“资产”文件夹。

  2. 粘贴(到“资产”文件夹)你刚刚复制的“示例”文件夹。

步骤 4:添加 GridView 控件

我们的应用需要显示照片的行和列。 换言之,图像的网格。 对于如下所示的 UI,要使用的主要控件是列表视图和网格视图

  1. 打开 MainWindow.xaml。 目前,有一个 Window 元素,其中包含 StackPanel 布局面板。 StackPanel 内部是一个 Button 控件,它连接到事件处理程序方法。

    所有应用的主窗口表示在运行应用时首先看到的视图。 在我们将要构建的应用中,主窗口的工作是从“示例”文件夹加载照片,并显示这些图像的平铺视图以及有关它们的各种信息。

  2. 将 StackPanel 和 Button 标记替换为以下列表中显示的 Grid 布局面板和 GridView 控件。

    <Window ...>
        <Grid>
            <GridView x:Name="ImageGridView"/>
        </Grid>
    </Window>
    

    提示

    x:Name 标识 XAML 元素,以便你可以在 XAML 中的其他位置和代码隐藏中引用它。

  3. C# 中的检测示例。 打开 MainWindow.xaml.cs,删除 myButton_Click 方法。

  4. C++/WinRT。 打开 MainWindow.xaml.hMainWindow.xaml.cpp,删除 myButton_Click 方法。

可以现在构建并运行,但在此阶段窗口将为空。 为了让 GridView 控件显示内容,需要为其提供要显示的对象集合。 接下来,我们将对此着手。

有关我们刚刚提到的某些类型的背景信息,请参阅布局面板适用于 Windows 应用的控件

步骤 5:ImageFileInfo 模型

模型(在模型、视图和视图模型的意义上)是在某种程度上代表现实世界对象或概念(如银行帐户)的类。 这是现实事物的抽象。 在本部分中,我们将向项目添加名为 ImageFileInfo 的新类。 ImageFileInfo 将是图像文件的模型,例如照片。 本部分将使我们更接近于能够在应用的用户界面 (UI) 中显示照片。

提示

在准备下面的代码示例时,让我们引入术语“可观察”。 可以动态绑定到 XAML 控件的属性(以便每次属性值更改时 UI 都会更新)称为可观察属性。 这一想法基于称为“观察者模式”的软件设计模式。 在本教程中生成的应用中,ImageFileInfo 模型的属性不会变化。 但即便如此,我们将展示如何通过实现 INotifyPropertyChanged 接口来使 ImageFileInfo 可观察。

  1. 右键单击项目节点 (SimplePhotos),然后单击“添加”>“新建项目...”。在“C# 项”>“代码”下,选择“类”。 将名称设置为 ImageFileInfo.cs,然后单击“添加”。

  2. ImageFileInfo.cs 中的内容替换为下面列出的代码。

    using Microsoft.UI.Xaml.Media.Imaging;
    using System;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Threading.Tasks;
    using Windows.Storage;
    using Windows.Storage.FileProperties;
    using Windows.Storage.Streams;
    
    namespace SimplePhotos
    {
        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));
        }
    }
    
  3. 保存并关闭 ImageFileInfo.cs 文件。

步骤 6:定义和填充图像集合的属性

在本部分中,我们将向 MainWindow 类添加新属性。 该属性(名为 Images)将是一个集合类,其中包含我们要显示的图像。

  1. MainWindow.xaml.cs 中定义属性,如下所示:

    ...
    using System.Collections.ObjectModel;
    ...
    namespace SimplePhotos
    {
        public sealed partial class MainWindow : Window
        {
            public ObservableCollection<ImageFileInfo> Images { get; } = 
                new ObservableCollection<ImageFileInfo>();
            ...
        }
    }
    
  2. 用图像填充新集合属性的代码显示在下面的 GetItemsAsync 和 LoadImageInfoAsync 方法中。 将 using 指令和两个方法实现也粘贴到 MainWindow.xaml.cs 中。 这些方法是 MainWindow 类的成员,因此请将它们粘贴到其中,就像粘贴上面的 Images 属性一样。

    ...
    using System.Threading.Tasks;
    using Windows.ApplicationModel;
    using Windows.Storage;
    using Windows.Storage.Search;
    ...
    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));
        }
    
        ImageGridView.ItemsSource = Images;
    }
    
    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;
    }
    
  3. 在本部分中,我们需要做的最后一件事是更新 MainWindow 的构造函数以调用 GetItemsAsync。

    public MainWindow()
    {
        ...
        GetItemsAsync();
    }
    

如果愿意,现在可以构建并运行(以确认已正常按照步骤操作),但在此阶段窗口中没有太多可显示的内容。 这是因为到目前为止我们所做的是要求 GridView 呈现 ImageFileInfo 类型的对象集合;而 GridView 尚不知道如何做到这一点。

请记住,Images 属性是 ImageFileInfo 对象的可观察集合。 GetItemsAsync 的最后一行告诉 GridView(名为 ImageGridView)其项目的来源 (ItemsSource) 是 Images 属性。 然后,GridView 的工作就是显示这些项。

但是我们还没有告诉 GridView 有关 ImageFileInfo 类的任何信息。 因此,到目前为止,最好是显示集合中每个 ImageFileInfo 对象的 ToString 值。 默认情况下,这只是类型的名称。 在下一部分中,我们将创建数据模板来定义希望显示 ImageFileInfo 对象的方式。

提示

我在上面使用了可观察集合这个术语。 在我们在本教程中构建的应用中,图像的数量不会改变(正如前文所述,每个图像的属性值也不会改变)。 但是,使用数据绑定将 UI 初始连接到数据仍然很方便,也是一种很好的做法。 这就是我们将执行的操作。

步骤 7:添加数据模板

首先,让我们使用类似于草图的占位符数据模板。 在完成对一些布局选项的探索前,这将一直有效。 之后,可以更新数据模板来显示实际照片。

提示

这实际上是一件非常实用的事情。 已经发现,如果 UI 看起来像草图(也就是低保真度),那么人们更愿意用它提出建议和/或测试快速的想法,有时涉及相当大的变化。 那是因为我们(正确地)猜测这样的改变成本不高。

另一方面,UI 看起来越完整(保真度越高),我们(再次正确地)猜测大量工作已投入其当前的外观。 这使得我们不太倾向于提出建议或尝试新想法。

  1. 打开 MainWindow.xaml,更改 Window 的内容,使其看起来像如下所示的标记:

    <Window ...>
        <Grid>
            <Grid.Resources>
                <DataTemplate x:Key="ImageGridView_ItemTemplate">
                    <Grid/>
                </DataTemplate>
            </Grid.Resources>
            <GridView x:Name="ImageGridView"
                    ItemTemplate="{StaticResource ImageGridView_ItemTemplate}">
            </GridView>
        </Grid>
    </Window>
    

    在布局根目录中,我们添加了简单的 DataTemplate 资源,并为其指定了 ImageGridView_ItemTemplate 键。 我们使用同一个键来设置 GridView 的 ItemTemplate。 GridView 等项控件具有 ItemTemplate 属性(正如之前看到它们具有 ItemsSource 属性)。 项目模板是数据模板;它用于显示集合中的每个项目。

    有关详细信息,请参阅项容器和模板

  2. 现在,我们可对数据模板进行几次编辑,即添加和编辑其中的元素,使其更有趣和有用。 我们将给根网格的高度和宽度设置为 300,边距为 8。 然后我们将添加两个行定义,并将第二个行定义的高度设置为“自动”。

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

    有关详细信息,请参阅对齐、边距和填充

  3. 我们希望数据模板显示每张照片的图像、名称、文件类型、尺寸和评分。 因此,我们将分别添加一个图像控件、一些 TextBlock 控件和一个 RatingControl 控件。 我们将在 StackPanel 布局面板中布局文本。 图像最初将显示项目的类似草图的 Microsoft Store 徽标作为占位符。

  4. 完成所有这些编辑后,数据模板的外观如下所示:

    <DataTemplate x:Key="ImageGridView_ItemTemplate">
        <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="ImageTitle"
                           HorizontalAlignment="Center"
                           Style="{StaticResource SubtitleTextBlockStyle}" />
                <StackPanel Orientation="Horizontal"
                            HorizontalAlignment="Center">
                    <TextBlock Text="ImageFileType"
                               HorizontalAlignment="Center"
                               Style="{StaticResource CaptionTextBlockStyle}" />
                    <TextBlock Text="ImageDimensions"
                               HorizontalAlignment="Center"
                               Style="{StaticResource CaptionTextBlockStyle}"
                               Margin="8,0,0,0" />
                </StackPanel>
    
                <RatingControl Value="3" IsReadOnly="True"/>
            </StackPanel>
        </Grid>
    </DataTemplate>
    

立即构建项目,然后运行应用以查看带有刚刚所创建项目模板的 GridView 控件。 接下来,我们将了解项目布局方式。我们将更改一些画笔,并在各项之间添加空间。

The placeholder item template.

步骤 8:编辑项容器的样式

与项控件(如 GridView)相关的另一个概念是项容器。 项容器是一个内容控件,该控件将项显示为其 Content 属性的值。 项控件根据需要创建尽可能多的项容器,以便随时显示在屏幕上可见的项。

作为控件,项容器具有样式和控件模板。 其样式和控件模板决定了项容器在其各种状态(例如选择、指针悬停和焦点)下的外观。 而且,正如我们所见,项模板(数据模板)决定了项本身的外观。

对于 GridView,其项容器的类型为 GridViewItem

因此,在本部分中,我们将重点设计项容器的样式。 为此,我们将为 GridViewItem 创建样式资源,然后将其设置为 *GridView 的 ItemContainerStyle。 在该样式中,我们将设置项目容器的 Background 和 Margin 属性,为其提供灰色背景并在其外部留出一点边距。

  1. MainWindow.xaml 中,将新的 Style 资源添加到用于放置数据模板的同一 Grid.Resources XML 元素中。

    <Grid>
        <Grid.Resources>
        ...
            <Style x:Key="ImageGridView_ItemContainerStyle"
                TargetType="GridViewItem">
                <Setter Property="Background" Value="Gray"/>
                <Setter Property="Margin" Value="8"/>
            </Style>
        </Grid.Resources>
    
  2. 接下来,我们使用 ImageGridView_ItemContainerStyle 键设置 GridView 的 ItemContainerStyle

    <GridView x:Name="ImageGridView"
            ...
            ItemContainerStyle="{StaticResource ImageGridView_ItemContainerStyle}">
    </GridView>
    

立即创建并运行应用,然后查看其外观。 调整窗口大小时,GridView 控件会负责重新排列项,以放置在空间中的最佳位置。 在某些宽度下,应用窗口的右侧有很多空间。 如果将 GridView 和/或其内容居中,则显示效果更好。 接下来,我们将执行这项操作。

提示

若要试验一下,请尝试将 Background 和 Margin 资源库设置为不同的值,并查看一下效果如何。

步骤 9:试验布局

你可能想知道最好是将 GridView 本身居中,还是将其内容居中。 我们先尝试将 GridView 居中。

  1. 为了便于查看 GridView 在窗口中的确切位置,以及在我们试验布局时会发生什么,我们将其 Background 属性设置为红色。

    <GridView x:Name="ImageGridView"
            ...
            Background="Red">
    </GridView>
    
  2. 现在,我们将将其 HorizontalAlignment 属性设置为“居中”。

    <GridView x:Name="ImageGridView"
            ...
            HorizontalAlignment="Center">
    </GridView>
    

    另请参阅对齐、边距和填充

立即构建并运行,并试验调整窗口宽度。 可以看到 GridView 的红色背景两侧都有等量的空白空间。 因此,我们已实现将图像居中的目标。 但现在比以前更清楚的是,滚动条属于 GridView,而不是窗口。 因此,我们需要将 GridView 更改回填充窗口。 我们已经证明,需要将 GridView 中的图像居中,而不是使 GridView 在窗口中居中。

  1. 因此,现在删除在上一步中添加的 HorizontalAlignment 属性。

步骤 10:编辑项面板模板

项控件在所谓的项目面板中布置其项容器。 我们可以通过编辑 GridView 的项面板模板来定义使用哪种类型的面板,并在该面板上设置属性。 这就是本部分中要执行的操作。

  1. MainWindow.xaml 中,将 ItemsPanelTemplate 资源添加到我们的资源字典中。 项面板的类型为 ItemsWrapGrid,我们将其 HorizontalAlignment 属性设置为“居中”。

    <Grid>
        <Grid.Resources>
        ...
            <ItemsPanelTemplate x:Key="ImageGridView_ItemsPanelTemplate">
                <ItemsWrapGrid Orientation="Horizontal"
                               HorizontalAlignment="Center"/>
            </ItemsPanelTemplate>
        </Grid.Resources>
    
  2. 接下来,我们使用 ImageGridView_ItemsPanelTemplate 键设置 GridView 的 ItemsPanel

    <GridView x:Name="ImageGridView"
            ...
            ItemsPanel="{StaticResource ImageGridView_ItemsPanelTemplate}">
    </GridView>
    

在此时构建和运行,并尝试调整窗口宽度,图像两侧的 GridView 的红色背景数量相同。 由于 GridView 会填充窗口,因此滚动条与窗口边缘可很好地对齐,用户可能期望这样。

  1. 现在,我们已完成布局实验,请从 GridView 中删除 Background="Red"

步骤 11:将占位符图像替换为照片

现在可将草图提升到更高的保真度;这意味着将占位符图像替换为真实图像,并将“lorem ipsum”样式的占位符文本替换为真实数据。 我们先来处理图像。

重要

我们将用于在 Assets\Samples 文件夹中显示照片的技术涉及逐步更新 GridView 的项目。 具体来说,就是下面代码示例中 ImageGridView_ContainerContentChanging 和 ShowImage 方法中的代码,包括对 ContainerContentChangingEventArgs.InRecycleQueue 和 ContainerContentChangingEventArgs.Phase 属性的使用。 有关详细信息,请参阅 ListView 和 GridView UI 优化。 但简而言之,GridView 会(通过事件)让我们知道其中一个项目容器何时准备好显示其项。 然后,我们将跟踪项目容器处于其更新生命周期的哪个阶段,以便确定它何时准备好显示照片数据。

  1. MainWindow.xaml.cs 中,向 MainWindow 添加一个名为 ImageGridView_ContainerContentChanging 的新方法。 这是一种事件处理方法,它处理的事件是 ContainerContentChanging。 我们还需要提供 ImageGridView_ContainerContentChanging 所依赖的 ShowImage 方法的实现。 将 using 指令和两个方法实现粘贴到 MainWindow.xaml.cs 中:

    ...
    using Microsoft.UI.Xaml.Controls;
    ...
    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();
        }
    }
    
  1. 然后,在 MainWindow.xaml 中,使用 GridView 的 ContainerContentChanging 事件注册 ImageGridView_ContainerContentChanging 事件处理程序。

    <GridView x:Name="ImageGridView"
            ...
            ContainerContentChanging="ImageGridView_ContainerContentChanging">
    </GridView>
    

步骤 12:将占位符文本替换为实际数据

在本部分中,我们将使用一次性数据绑定。 一次性绑定非常适合在运行时不会更改的数据。 这意味着一次性绑定具有高性能且易于创建。

  1. MainWindow.xaml 中,找到 ImageGridView_ItemTemplate 数据模板资源。 我们将告诉数据模板,其工作是成为 ImageFileInfo 类的模板,你会记得这是 GridView 要显示的项的类型。

  2. 为此,请将 x:DataType 值添加到模板,如下所示:

    <DataTemplate x:Key="ImageGridView_ItemTemplate"
                  x:DataType="local:ImageFileInfo">
        ...
    

    如果不熟悉上面显示的 local: 语法(或起始 Window 标记中已有的 xmlns:local 语法),请参阅 XAML 命名空间和命名空间映射

    现在,我们已设置 x:DataType,可以在数据模板中使用 x:Bind 数据绑定表达式来绑定到指定的数据类型(本例中为 ImageFileInfo)的属性。

  3. 在数据模板中,找到第一个 TextBlock 元素(其文本当前设置为 ImageTitle 的元素)。 替换其文本值,如下所示。

    提示

    可以复制并粘贴以下标记,也可以在 Visual Studio 中使用 IntelliSense。 为此,请选择引号内的当前值,然后键入 {。 IntelliSense 会自动添加右大括号,并显示代码完成列表。 可以向下滚动到 x:Bind,然后双击它。 但是键入 x: 可能更高效(注意随后 x:Bind 如何被筛选至完成列表顶部),然后按 TAB 键。 现在按 SPACE 键,然后键入 ImageT(尽可能多使用属性名称 ImageTitle,使其位于完成列表顶部),再按 Tab。

    <TextBlock Text="{x:Bind ImageTitle}"
        ... />
    

    x:Bind 表达式将 UI 属性的值与 data-object 属性的值相关联。 当然,这取决于先将 x:DataType 设置为该 data-object 的类型,以便工具和运行时知道可以绑定到哪些属性。

    有关详细信息,请参阅 {x:Bind} 标记扩展数据绑定深入介绍

  4. 同样,替换其他 TextBlock 和 RatingControl 的值。 结果如下:

    <TextBlock Text="{x:Bind ImageTitle}" ... />
    <StackPanel ... >
        <TextBlock Text="{x:Bind ImageFileType}" ... />
        <TextBlock Text="{x:Bind ImageDimensions}" ... />
    </StackPanel>
    <RatingControl Value="{x:Bind ImageRating}" ... />
    

如果你现在构建并运行应用,而不是占位符,你将看到真实照片和真实文本(和其他数据)。 在视觉上和功能上,这个简单的小应用现已完成。 但在结尾时,我们进行最后的一点数据绑定。

The finished app.

步骤 13:将 GridView 绑定到图像集合(仅限 C#)

重要

仅当创建了 C# 项目时,才执行最后一步。

提示

你会发现,有些操作(通常与动态生成的 UI 相关)在 XAML 标记中无法做到。 但一般而言,如果可以在标记中采取措施,则更可取。 这在 XAML 标记表示的视图和命令式代码表示的模型(或视图模型)之间提供了更清晰的分离。 这往往会改进工具和团队成员之间的工作流。

我们当前要使用命令性代码将 GridView 的 ItemsSource 属性与 MainWindow 的 Images 属性相关联。 但是我们可改为在标记中这样做。

  1. 在 MainWindow 类中,删除(或注释掉)GetItemsAsync 的最后一行,该行会将 ImageGridView 的 ItemsSource 设置为 Images 属性的值。

  2. 然后在 MainWindow.xaml 中找到名为 ImageGridView 的 GridView,并像这样添加 ItemsSource 特性。 如果需要,可使用 IntelliSense 进行此更改。

    <GridView x:Name="ImageGridView"
              ...
              ItemsSource="{x:Bind Images}"
    

此特定应用的 Images 属性值在运行时不会更改。 但由于 Images 属于 ObservableCollection<T> 类型,因此集合的内容可能发生变化(即可以添加或删除元素),并且绑定会自动注意到这些变化并更新 UI。

结束语

在本教程中,我们演练了如何使用 Visual Studio 构建可显示照片的简单 WinUI 3 应用的过程。 希望本教程可为你提供在 WinUI 3 应用中使用控件、布局面板、数据绑定和 GridView UI 优化的经验。

另请参阅

教程:构建面向多个平台的简单照片查看器