UWP PhotoLab 示例应用 (C#) 的 Windows 应用 SDK 迁移

本主题是获取 C# UWP PhotoLab 示例应用并将其迁移到 Windows 应用 SDK 的案例研究。

首先克隆 UWP 示例应用的存储库,然后在 Visual Studio 中打开解决方案。

重要

有关执行迁移过程的注意事项和策略,以及如何设置用于迁移的开发环境,请参阅总体迁移策略。 请务必参阅从 UWP 移植到 WinUI 3 时支持的功能,这样你可以确保在尝试迁移之前支持应用所需的所有功能。

安装适用于 Windows 应用 SDK 的工具

若要设置开发计算机,请参阅安装适用于 Windows App SDK 的工具

重要

你会发现发行说明主题以及 Windows 应用 SDK 发行通道主题。 每个通道都有发行说明。 Be sure to check any limitations and known issues in those release notes, since those might affect the results of following along with this case study and/or running the migrated app.

创建新项目

在 Visual Studio 中,通过“打包的空白应用 (桌面版 WinUI 3)”项目模板创建一个新的 C# 项目。 将项目命名为“PhotoLabWinUI”,取消选中“将解决方案和项目置于同一目录中”。 你可以针对客户端操作系统的最新版本(非预览版)。

注意

我们将示例项目的 UWP 版本(你从其 repo 克隆的版本)称为源解决方案/项目。 我们将 Windows 应用 SDK 版本称为目标解决方案/项目。

我们迁移代码的顺序

MainPage 是应用中重要且突出的部分。 但如果我们首先迁移 MainPage,我们很快就会意识到“MainPage”依赖于“DetailPage”视图;以及,“DetailPage”依赖于“ImageFileInfo”模型。 因此,对于本演练,我们将采用下面这种方法。

  • 我们首先复制资产文件。
  • 然后,我们将迁移 ImageFileInfo 模型。
  • 接下来,我们将迁移 App 类(因为需要对 DetailPage、MainPage 和 LoadedImageBrush 所依赖的类进行更改)。
  • 然后,我们将迁移 LoadedImageBrush 类。
  • 接下来,我们将开始迁移视图,首先从 DetailPage 开始。
  • 我们将通过迁移 MainPage 视图来完成此操作。

复制资产文件

  1. 在 Visual Studio 的目标项目中,在“解决方案资源管理器”中,右键单击 Assets 文件夹,然后添加一个名为 Samples 的新文件夹。

  2. 在源项目的克隆中,在“文件资源管理器”中,找到文件夹 Windows-appsample-photo-lab>PhotoLab>Assets。 你将在该文件夹中找到七个资产文件,以及一个名为 Samples 的子文件夹,其中包含示例图像。 选择这七个资产文件和 samples 子文件夹,并将其复制到剪贴板。

  3. 同样在“文件资源管理器”中,现在在你创建的目标项目中找到相应的文件夹。 该文件夹的路径为 PhotoLabWinUI>PhotoLabWinUI>Assets。 将你刚刚复制的资产文件和子文件夹粘贴到该文件夹中,并接受提示以替换目标中已存在的任何文件。

  4. 在 Visual Studio 的目标项目中,在“解决方案资源管理器”中,展开 Assets 文件夹后,你将在 Samples 文件夹中看到 Samples 子文件夹(你刚刚粘贴)的内容。 可以将鼠标指针悬停在资产文件上。 将对每个资产文件显示缩略图预览,确认你已正确替换/添加资产文件。

迁移 ImageFileInfo 模型

ImageFileInfo 是表示图像文件(例如照片)的模型(在模型、视图和视图模型的意义上)。

复制 ImageFileInfo 源代码文件

  1. 在源项目的克隆中,在“文件资源管理器”中,找到文件夹 Windows-appsample-photo-lab>PhotoLab。 在该文件夹中,你将找到源代码文件 ImageFileInfo.cs;该文件包含 ImageFileInfo 的实现。 选择该文件,然后将其复制到剪贴板。

  2. 在 Visual Studio 中,右键单击目标项目节点,然后单击“打开文件资源管理器中的文件夹”。 这将在文件资源管理器中打开目标项目文件夹。 将刚刚复制的文件粘贴到该文件夹中。

迁移 ImageFileInfo 源代码

  1. 在你刚刚粘贴的 ImageFileInfo.cs 文件中进行以下查找/替换(匹配大小写和整个单词)。
  • namespace PhotoLab =>namespace PhotoLabWinUI
  • Windows.UI.Xaml =>Microsoft.UI.Xaml

Windows.UI.Xaml 是 UWP XAML 的命名空间;Microsoft.UI.Xaml 是 WinUI XAML 的命名空间。

注意

将 UWP API 映射到 Windows 应用 SDK 主题提供了 UWP API 到它们的 Windows 应用 SDK 等效项的映射。 我们上面所做的更改是迁移过程中必要的命名空间名称更改的示例。

  1. 现在,确认可以生成目标解决方案(但暂时不要运行)。

迁移 App 类

  1. 从源项目的中的 <Application.Resources> 元素 App.xaml 中,查找以下四行。 复制它们,并将它们粘贴到目标项目中。
<SolidColorBrush x:Key="RatingControlSelectedForeground" Color="White"/>
<!--  Window width adaptive breakpoints.  -->
<x:Double x:Key="MinWindowBreakpoint">0</x:Double>
<x:Double x:Key="MediumWindowBreakpoint">641</x:Double>
<x:Double x:Key="LargeWindowBreakpoint">1008</x:Double>

注意

由于目标项目将使用与源项目不同(且更简单)的导航,因此无需从源项目的 App.xaml.cs

  1. 在目标项目中,应用将主窗口对象存储在其私有字段 m_window 中。 在迁移过程的后期(当我们迁移源项目对 Window.Current 的使用时),如果该私有字段改为公共静态属性会很方便。 因此,将 m_window 字段替换为 Window 属性,并将引用更改为 m_window ,如下所示。
// App.xaml.cs
public partial class App : Application
{
    ...
    protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
    {
        Window = new MainWindow();
        Window.Activate();
    }

    public static MainWindow Window { get; private set; }
}
  1. 在迁移过程的后期(当我们迁移显示 FileSavePicker 的代码时),如果应用公开主窗口的句柄 (HWND) 会很方便。 因此,请添加 WindowHandle 属性,并在 OnLaunched 方法中对其进行初始化,如下所示。
// App.xaml.cs
public partial class App : Application
{
    ...
    protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
    {
        Window = new MainWindow();
        Window.Activate();
        WindowHandle = WinRT.Interop.WindowNative.GetWindowHandle(Window);
    }

    public static IntPtr WindowHandle { get; private set; }
}

迁移 LoadedImageBrush 模型

LoadedImageBrush 是 XamlCompositionBrushBase 的一个特化。 PhotoLab 示例应用使用 LoadedImageBrush 类将效果应用于照片。

引用 Win2D NuGet 包

为了支持 LoadedImageBrush 中的代码,源项目依赖于 Win2D 因此,我们还需要在目标项目中依赖 Win2D。

在 Visual Studio 的目标解决方案中,单击“工具”>“NuGet 包管理器”>“管理解决方案 NuGet 程序包...”>“浏览”,然后键入或粘贴“Microsoft.Graphics.Win2D”。 在搜索结果中选择正确的项目,检查 PhotoLabWinUI 项目,然后单击“安装”将包安装到该项目中。

复制 LoadedImageBrush 源代码文件

LoadedImageBrush.cs 从源项目复制到目标项目的方式与复制 ImageFileInfo.cs 的方式相同。

迁移 LoadedImageBrush 源代码

  1. 在你刚刚粘贴的 LoadedImageBrush.cs 文件中进行以下查找/替换(匹配大小写和整个单词)。
  1. 确认可以生成目标解决方案(但暂时不要运行)。

迁移 DetailPage 视图

DetailPage 是代表照片编辑器页面的类,其中 Win2D 效果被切换、设置和链接在一起。 你可以通过在 MainPage 上选择照片缩略图进入照片编辑器页面。 DetailPage 是一个模型(在模型、视图和视图模型的意义上)。

复制 DetailPage 源代码文件

DetailPage.xamlDetailPage.xaml.cs 从源项目复制到目标项目的方式与你在前面的步骤中复制文件的方式相同。

迁移 DetailPage 源代码

  1. 在你刚刚粘贴的 DetailPage.xaml 文件中进行以下查找/替换(匹配大小写和整个单词)。
  • PhotoLab =>PhotoLabWinUI
  1. 在你刚刚粘贴的 DetailPage.xaml.cs 文件中进行以下查找/替换(匹配大小写和整个单词)。
  • namespace PhotoLab =>namespace PhotoLabWinUI
  • Windows.UI.Colors =>Microsoft.UI.Colors
  • Windows.UI.Xaml =>Microsoft.UI.Xaml
  1. 对于下一步,我们将进行 ContentDialog 和 Popup 中解释的更改。 因此,仍然在 DetailPage.xaml.cs 中,在 ShowSaveDialog 方法中,在紧邻 ContentDialogResult result = await saveDialog.ShowAsync(); 行的前面,添加此代码。
if (Windows.Foundation.Metadata.ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
{
    saveDialog.XamlRoot = this.Content.XamlRoot;
}
  1. 还是在 DetailPage.xaml.cs 中,在 OnNavigatedTo 方法中,删除以下两行代码。 只是这两行;在本案例研究的稍后部分,我们将重新引入我们刚刚删除的后退按钮功能。
...
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
    AppViewBackButtonVisibility.Visible;
...
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = 
    AppViewBackButtonVisibility.Collapsed;
...
  1. 对于这一步,我们将进行 MessageDialog 和 Pickers 中介绍的更改。 还是在 DetailPage.xaml.cs 中,在 ExportImage 方法中,在紧邻 var outputFile = await fileSavePicker.PickSaveFileAsync(); 行的前面,添加以下代码行。
WinRT.Interop.InitializeWithWindow.Initialize(fileSavePicker, App.WindowHandle);

MainPage 依赖于 DetailPage,这就是我们首先迁移 DetailPage 的原因。 但是 DetailPage 也依赖于 MainPage ,所以我们还不能生成。

迁移 MainPage 视图

应用的主页表示在运行应用时首先看到的视图。 该页面从内置于示例应用的 Samples 文件夹中加载照片,并显示平铺缩略图视图。

复制 MainPage 源代码文件

MainPage.xamlMainPage.xaml.cs 从源项目复制到目标项目的方式与你在前面的步骤中复制文件的方式相同。

迁移 MainPage 源代码

  1. 在你刚刚粘贴的 MainPage.xaml 文件中进行以下查找/替换(匹配大小写和整个单词)。
  • PhotoLab =>PhotoLabWinUI
  1. 仍在 MainPage.xaml 中,找到标记 animations:ReorderGridAnimation.Duration="400",并删除该标记。

  2. 在你刚刚粘贴的 MainPage.xaml.cs 文件中进行以下查找/替换(匹配大小写和整个单词)。

  • namespace PhotoLab =>namespace PhotoLabWinUI
  • Windows.UI.Xaml =>Microsoft.UI.Xaml
  1. 对于这一步,我们将进行 ContentDialog 和 Popup 中解释的更改。 因此,仍然在 MainPage.xaml.cs 中,在 GetItemsAsync 方法中,在紧邻 ContentDialogResult resultNotUsed = await unsupportedFilesDialog.ShowAsync(); 行的前面,添加此代码。
if (Windows.Foundation.Metadata.ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
{
    unsupportedFilesDialog.XamlRoot = this.Content.XamlRoot;
}
  1. 还是在 MainPage.xaml.cs 中,在 OnNavigatedTo 方法中,删除以下代码行。
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
    AppViewBackButtonVisibility.Collapsed;

在本案例研究的稍后部分,我们将重新引入我们刚刚删除的后退按钮功能。

  1. 确认可以生成目标解决方案(但暂时不要运行)。

PhotoLab 示例应用使用导航逻辑最初导航到 MainPage(然后在 MainPage 和 DetailPage 之间)。 有关需要(以及不需要)导航的 Windows 应用 SDK 应用的详细信息,请参阅是否需要实现页面导航?

因此,我们接下来将进行的更改支持该导航。

  1. MainWindow.xaml 中,删除 <StackPanel> 元素,并将其替换为名为 <Frame> 的元素。 结果类似以下形式:
<Window ...>
    <Frame x:Name="rootFrame"/>
</Window>
  1. MainWindow.xaml.cs 中,删除 myButton_Click 方法。

  2. 仍然在 MainWindow.xaml.cs 中,将以下代码行添加到构造函数中。

public sealed partial class MainWindow : Window
{
    public MainWindow()
    {
        this.InitializeComponent();
        rootFrame.Navigate(typeof(MainPage));
    }
}
  1. 确认可以生成目标解决方案(但暂时不要运行)。

还原后退按钮功能

  1. DetailPage.xaml 中,根元素是 RelativePanel。 在该 RelativePanel 中添加以下标记,紧跟 StackPanel 元素之后。
<AppBarButton x:Name="BackButton" Click="BackButton_Click" Margin="0,0,12,0">
    <SymbolIcon Symbol="Back"/>
</AppBarButton>
  1. DetailPage.xaml.cs 中,将以下两行代码添加到 OnNavigatedTo 方法,位于指示的位置。
if (this.Frame.CanGoBack)
{
    BackButton.Visibility = Microsoft.UI.Xaml.Visibility.Visible;
}
else
{
    BackButton.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
}
  1. 仍然在 DetailPage.xaml.cs 中,添加以下事件处理程序。
private void BackButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
    Frame.GoBack();
}

测试已迁移的应用

现在生成项目,并运行应用来进行测试。 选择图像,设置缩放级别,选择效果,并对其进行配置。