UWP 照片編輯器範例應用的 Windows 應用程式 SDK 移轉 (C++/WinRT)
本主題是採用 C++/WinRT UWP 照片編輯器範例應用程式並將其移轉到 Windows 應用程式 SDK 的案例研究。
- 首先複製 UWP 範例應用的存放庫,然後在 Visual Studio 中開啟解決方案。
重要
如需處理移轉程式的考慮和策略,以及如何設定用於移轉的開發環境,請參閱整體移轉策略。
安裝 Windows 應用程式 SDK 的工具
若要設定您的開發電腦,請參閱安裝 Windows 應用程式 SDK 的工具 (機器翻譯)。
重要
您可以在 Windows 應用程式 SDK 發行通道主題中找到版本資訊主題。 每個通道都有版本資訊。 請務必查看這些版本資訊中的任何限制和已知問題,因為這些可能會影響本案例研究和/或執行移轉的應用程式的結果。
建立新專案
- 在 Visual Studio 中,從空白應用程式封包 (桌面中的 WinUI 3) 專案範本建立一個新的 C++/WinRT 專案。 將專案命名為 PhotoEditor,取消選取將解決方案和專案放在同一目錄中。 您可以將目標設為用戶端作業系統的最新版本 (非預覽版)。
注意
我們將引用範例專案的 UWP 版本 (您從其存放庫複製的版本) 作為來源解決方案/專案。 我們將把 Windows 應用程式 SDK 版本稱為目標解決方案/專案。
我們將移轉程式代碼的順序
MainPage 是應用程式的重要且突出的部分。 但如果我們先移轉,那麼我們很快就會意識到 MainPage 依賴 DetailPage 檢視;然後 DetailPage 依賴 Photo 模型。 因此,在本逐步解說中,我們將採用此方法。
- 我們將首先複製資產檔案。
- 然後,我們將移轉 Photo 模型。
- 接下來,我們將移轉 App 類別 (因為需要新增一些成員,因此 DetailPage 和 MainPage 將相依)。
- 然後,我們會先從 DetailPage 開始移轉檢視。
- 我們將藉由移轉 MainPage 檢視來完成。
我們將複製整個原始碼檔案
在本逐步解說中,我們將使用檔案總管複製原始程式碼檔案。 如果您希望複製文件內容,請參閱本主題末尾的附錄:複製照片模型文件的內容部分,以了解如何對照片執行此操作的範例 (然後您可以套用類似的流程項目中的其他類型)。 不過,這個選項確實涉及更多步驟。
複製資產檔案
在來源專案的複製中,在檔案總管中,找到資料夾 Windows-appsample-photo-lab>PhotoEditor>Assets。 您會在該資料夾中找到八個資產檔案。 選取這八個檔案,並將其複製到剪貼簿。
此外,在檔案總管中,現在會在您所建立的目標專案中找到對應的資料夾。 該資料夾的路徑是 PhotoLabWinUI>PhotoEditor>Assets。 將您剛剛複製的資源檔案貼上到該資料夾中,並接受提示以替換目標中已存在的七個檔案。
在 Visual Studio 的目標專案中,在解決方案檔案總管中,展開資產資料夾。 將
bg1.png
新增至您剛貼上的現有資產檔案資料夾。 您可以將滑鼠指標懸停在資源檔案上。 每個資源檔案都會顯示縮圖預覽,確認您已正確取代/新增資源檔案。
移轉相片模型
Photo 是代表相片的執行階段類別。 它是一個模型 (在模型、檢視和檢視模型的意義上)。
複製相片源代碼檔案
在來源專案的複製中,在檔案總管中,找到資料夾 Windows-appsample-photo-lab>PhotoEditor。 在該資料夾中,您會找到三個原始碼檔案
Photo.idl
、Photo.h
和Photo.cpp
;這些檔案會一起實作Photo 執行階段類別。 選取這三個檔案,並將其複製到剪貼簿。在 Visual Studio 中,右鍵按兩下目標項目節點,然後按兩下在檔案總管中打開資料夾。 這會在檔案總管中開啟目標專案資料夾。 將您剛複製的檔案貼到這三個資料夾中。
返回方案總管,選擇目標專案節點,確保開啟顯示所有檔案。 在您複製的三個檔案上按右鍵,然後按一下加入至專案。 切換關閉顯示所有檔案。
在來源專案的方案總管中,
Photo.h
和.cpp
嵌套在下面以指示它們是從它產生的 (依賴它)Photo.idl
。 如果您喜歡這種安排,那麼您可以透過手動編輯在目標專案中執行相同的操作\PhotoEditor\PhotoEditor\PhotoEditor\PhotoEditor.vcxproj
(您首先需要在 Visual Studio 中儲存全部)。 找到以下內容:<ClInclude Include="Photo.h" />
並使用以下項目加以取代:
<ClInclude Include="Photo.h"> <DependentUpon>Photo.idl</DependentUpon> </ClInclude>
針對
Photo.cpp
重複,然後儲存並關閉專案檔。 當您將焦點設定回 Visual Studio 時,請按兩下重載。
移轉相片源代碼
- 在
Photo.idl
中,搜尋命名空間名稱Windows.UI.Xaml
(這是 UWP XAML 的命名空間),並將其變更為Microsoft.UI.Xaml
(這是 WinUI XAML 的命名空間)。
注意
將 UWP API 對應到 Windows 應用程式 SDK 主題提供了 UWP API 到其 Windows 應用程式 SDK 等效項的對應。 我們上面所做的更改是移轉過程中所需的命名空間名稱更改的範例。
在
Photo.cpp
中,將#include "Photo.g.cpp"
新增至現有的 include 指示詞,緊接在 之後#include "Photo.h"
。 這是 UWP 和 Windows 應用程式 SDK 專案之間需要注意的資料夾和檔案名稱差異 (C++/WinRT) 之一。將下列尋找/取代專案 (比對大小寫和整字) 放在您剛複製並貼上之檔案中所有原始程式碼的內容中。
- %
從
pch.h
來源專案中複製下列內容,並將其貼到pch.h
目標專案中。 這是來源項目中包含的標頭檔案的子集;這些只是我們支援迄今為止已移轉的程式碼所需的標頭。#include <winrt/Microsoft.UI.Xaml.Media.Imaging.h> #include <winrt/Windows.Storage.h> #include <winrt/Windows.Storage.FileProperties.h> #include <winrt/Windows.Storage.Streams.h>
現在確認您可以建置目標解決方案 (但尚未執行)。
移轉 App 類別
目標專案的 App.idl
和 App.xaml
不需要任何變更。 但我們確實需要編輯 App.xaml.h
和應用程式 .xaml.cpp
,才能將一些新成員新增至 App 類別。 我們將這樣做的方式,讓我們在每個區段之後建置 (但最後一節除外,也就是 App::OnLaunched)。
讓主視窗物件可供使用
在此步驟中,我們將進行變更,如將 Windows.UI.Xaml.Window.Current 變更為 App.Window 中所述。
在目標專案中,App 將主視窗物件儲存在其私有資料成員視窗中。 稍後在移轉過程中 (當我們移轉來源專案對 Window.Current 的使用時),如果該視窗資料成員是靜態的,將會很方便;並且也可以透過訪問器函數來使用。 因此,接下來我們將進行這些變更。
由於我們正在將視窗設為靜態,因此我們必須在
App.xaml.cpp
中初始化,而不是透過程式代碼目前使用的預設成員初始化表達式進行初始化。 以下是App.xaml.h
和App.xaml.cpp
中這些變更的外觀。// App.xaml.h ... struct App : AppT<App> { ... static winrt::Microsoft::UI::Xaml::Window Window(){ return window; }; private: static winrt::Microsoft::UI::Xaml::Window window; }; ... // App.xaml.cpp ... winrt::Microsoft::UI::Xaml::Window App::window{ nullptr }; ...
App::OnNavigationFailed
相片編輯器範例應用程式會使用瀏覽邏輯在 MainPage 和 DetailPage 之間導覽。 如需 Windows 應用程式 SDK 需要瀏覽的應用程式的詳細資訊 (以及不需要瀏覽的應用程式),請參閱是否需要實作頁面導覽?。
因此,我們將在接下來的幾節中移轉的成員都是為了支援應用程式內的導航而存在的。
讓我們從移轉 OnNavigationFailed 事件處理程序開始。 從來源專案複製該成員函數的宣告和定義,並將其貼上到目標專案中 (在
App.xaml.h
和App.xaml.cpp
中)。在您貼入
App.xaml.h
的程式代碼中,將變更Windows::UI::Xaml
為Microsoft::UI::Xaml
。
App::CreateRootFrame
來源專案包含名為 App::CreateRootFrame 的協助程式函式。 從來源專案複製該輔助函數的宣告和定義,並將其貼上到目標專案中 (在
App.xaml.h
和App.xaml.cpp
中)。在您貼入
App.xaml.h
的程式代碼中,將變更Windows::UI::Xaml
為Microsoft::UI::Xaml
。在您貼入
App.xaml.cpp
的程式代碼中,將 出現的兩個Window::Current()
變更為window
(這是我們稍早看到之 App 類別的數據成員名稱)。
App::OnLaunched
目標專案已經包含 OnLaunched 事件處理程式的實作。 它的參數是對 Microsoft::UI::Xaml::LaunchActivatedEventArgs 的常數引用,這對於 Windows 應用程式 SDK 是正確的 (與使用 Windows::ApplicationModel::Activation::LaunchActivatedEventArgs 的來源項目相比,它是對於UWP是正確的)。
我們只需要合併 OnLaunched 的兩個定義 (來源和目標),讓目標專案中
App.xaml.cpp
的 App::OnLaunched 看起來像下面的清單。 請注意,它會使用window
(而不是Window::Current()
,就像 UWP 版本一樣)。void App::OnLaunched(LaunchActivatedEventArgs const&) { window = make<MainWindow>(); Frame rootFrame = CreateRootFrame(); if (!rootFrame.Content()) { rootFrame.Navigate(xaml_typename<PhotoEditor::MainPage>()); } window.Activate(); }
上述程式代碼提供 App 在 MainPage 上的相依性,因此在移轉 DetailPage 和 MainPage 之前,我們無法從這一點建置。 當我們能夠再次建置時,我們會這樣說。
移轉 DetailPage 檢視
DetailPage 是代表相片編輯器頁面的類別,其中 Win2D 效果會切換、設定及連結在一起。 選取 MainPage 上的相片縮圖,即可進入相片編輯器頁面。 DetailPage 是一種 檢視 (在檢視和 ViewModel 的意義上)。
參考 Win2D NuGet 套件
若要在 DetailPage 中支援程式代碼,來源專案相依於 Microsoft.Graphics.Win2D。 因此,我們也需要在目標專案中相依於 Win2D。
- 在 Visual Studio 的目標解決方案中,按兩下工具、>NuGet 包管理器>、管理解決方案的 NuGet 包...>瀏覽。 確保未選取包括預發布,然後在搜尋框中鍵入或貼上 Microsoft.Graphics.Win2D。 在搜尋結果中選擇正確的專案,勾選 PhotoEditor 專案,然後按兩下安裝以安裝軟體套件。
複製 DetailPage 原始碼檔案
在來源專案的複製中,在檔案總管中,找到資料夾 Windows-appsample-photo-lab>PhotoEditor。 在該資料夾中,您會找到四個原始程式碼檔案
DetailPage.idl
、DetailPage.xaml
、DetailPage.h
和DetailPage.cpp
;這些檔案會一起實作 DetailPage 檢視。 選取這四個檔案,並將其複製到剪貼簿。在 Visual Studio 中,右鍵按兩下目標項目節點,然後按兩下在檔案總管中打開資料夾。 這會在檔案總管中開啟目標專案資料夾。 將您剛複製的檔案貼到這四個資料夾中。
仍在 檔案總管 中,分別將
DetailPage.h
和DetailPage.cpp
的名稱變更為DetailPage.xaml.h
和DetailPage.xaml.cpp
。 這是 UWP 和 Windows 應用程式 SDK 專案之間需要注意的資料夾和檔案名稱差異 (C++/WinRT) 之一。返回方案總管,選擇目標專案節點,確保開啟顯示所有檔案。 以滑鼠右鍵按下您剛貼上的四個檔案 (並重新命名),然後按兩下包含在專案中。 切換關閉顯示所有檔案。
在來源專案中,在方案總管中,
DetailPage.idl
巢狀於DetailPage.xaml
底下。 如果您喜歡這種安排,那麼您可以透過手動編輯在目標專案中執行相同的操作\PhotoEditor\PhotoEditor\PhotoEditor\PhotoEditor.vcxproj
(您首先需要在 Visual Studio 中儲存全部)。 找到以下內容:<Midl Include="DetailPage.idl" />
並使用以下項目加以取代:
<Midl Include="DetailPage.idl"> <DependentUpon>DetailPage.xaml</DependentUpon> </Midl>
儲存並關閉專案檔。 當您將焦點設定回 Visual Studio 時,請按兩下重載。
移轉 DetailPage 原始程式碼
在
DetailPage.idl
中,搜尋Windows.UI.Xaml
,並將變更為Microsoft.UI.Xaml
。在
DetailPage.xaml.cpp
中,將#include "DetailPage.h"
變更為#include "DetailPage.xaml.h"
。緊接在該位置下方,新增
#include "DetailPage.g.cpp"
。針對對靜態 App::Window 方法的呼叫 (即將新增的) 進行編譯,仍在
DetailPage.xaml.cpp
中,緊接在#include "Photo.h"
之前新增#include "App.xaml.h"
。將下列尋找/取代專案 (比對大小寫和整字) 放在您剛複製並貼上之檔案中原始程式碼的內容中。
DetailPage.xaml.h
中的.xaml.cpp
、Windows::UI::Composition
和 >Microsoft::UI::Composition
DetailPage.xaml.h
中的.xaml.cpp
、Windows::UI::Xaml
和 >Microsoft::UI::Xaml
- 在
DetailPage.xaml.cpp
,Window::Current()
=>App::Window()
中
從
pch.h
來源專案中複製下列內容,並將其貼到pch.h
目標專案中。#include <winrt/Windows.Graphics.Effects.h> #include <winrt/Microsoft.Graphics.Canvas.Effects.h> #include <winrt/Microsoft.Graphics.Canvas.UI.Xaml.h> #include <winrt/Microsoft.UI.Composition.h> #include <winrt/Microsoft.UI.Xaml.Input.h> #include <winrt/Windows.Graphics.Imaging.h> #include <winrt/Windows.Storage.Pickers.h>
另外,在
pch.h
的頂部,緊接在#pragma once
之後,新增以下內容:// This is required because we are using std::min and std::max, otherwise // we have a collision with min and max macros being defined elsewhere. #define NOMINMAX
我們尚無法建置,但在移轉 MainPage 之後,我們將能夠進行 (接下來)。
移轉 MainPage 檢視
應用程式的主頁代表您執行應用程式時首先看到的檢視。 此頁面從圖片庫載入照片,並顯示平鋪縮圖視圖。
複製 MainPage 原始碼檔案
類似於您對 DetailPage 所做的作業,現在會複製
MainPage.idl
、MainPage.xaml
、MainPage.h
和MainPage.cpp
。將
.h
和.cpp
檔案分別重新命名為.xaml.h
和.xaml.cpp
。在目標專案中包含所有四個檔案,例如之前。
在來源專案中,在方案總管中,
MainPage.idl
巢狀於MainPage.xaml
底下。 如果您喜歡這種安排,則可以手動編輯\PhotoEditor\PhotoEditor\PhotoEditor\PhotoEditor.vcxproj
,在目標項目中執行相同的動作。 找到以下內容:<Midl Include="MainPage.idl" />
並使用下列程式碼取代:
<Midl Include="MainPage.idl"> <DependentUpon>MainPage.xaml</DependentUpon> </Midl>
移轉 MainPage 原始程式碼
在
MainPage.idl
中,搜尋Windows.UI.Xaml
,並將這兩個出現的項目變更為Microsoft.UI.Xaml
。在
MainPage.xaml.cpp
中,將#include "MainPage.h"
變更為#include "MainPage.xaml.h"
。緊接在該位置下方,新增
#include "MainPage.g.cpp"
。針對對靜態 App::Window 方法的呼叫 (即將新增的) 進行編譯,仍在
MainPage.xaml.cpp
中,緊接在#include "Photo.h"
之前新增#include "App.xaml.h"
。
在下一個步驟中,我們將進行 ContentDialog 和 Popup 中說明的變更。
因此,仍在
MainPage.xaml.cpp
的 MainPage::GetItemsAsync 方法中,緊接在行之後,新增這一行程序ContentDialog unsupportedFilesDialog{};
代碼。unsupportedFilesDialog.XamlRoot(this->Content().XamlRoot());
將下列尋找/取代專案 (比對大小寫和整字) 放在您剛複製並貼上之檔案中原始程式碼的內容中。
MainPage.xaml.h
中的.xaml.cpp
、Windows::UI::Composition
和 >Microsoft::UI::Composition
MainPage.xaml.h
中的.xaml.cpp
、Windows::UI::Xaml
和 >Microsoft::UI::Xaml
- 在
MainPage.xaml.cpp
,Window::Current()
=>App::Window()
中
從
pch.h
來源專案中複製下列內容,並將其貼到pch.h
目標專案中。#include <winrt/Microsoft.UI.Xaml.Hosting.h> #include <winrt/Microsoft.UI.Xaml.Media.Animation.h> #include <winrt/Windows.Storage.Search.h>
確認您可以建置目標解決方案 (但尚未執行)。
更新 MainWindow
在
MainWindow.xaml
中,刪除 StackPanel 及其內容,因為我們不需要 MainWindow 中的任何 UI。 這會留下空的 Window 元素。在
MainWindow.idl
中,刪除佔位元Int32 MyProperty;
只會留下建構函式。在
MainWindow.xaml.h
和MainWindow.xaml.cpp
中,刪除佔位符 MyProperty 和 myButton_Click 的宣告和定義,僅保留建構子。
執行緒模型差異所需的移轉變更
由於 UWP 與 Windows 應用程式 SDK 之間的執行緒模型差異,本節中的兩個變更是必要的,如 ASTA 對 STA 執行緒模型中所述。 以下是問題原因的簡短描述,然後是解決問題的方法。
MainPage
MainPage 會從您的 Pictures 資料夾載入圖像檔、呼叫 儲存體 ItemContentProperties.GetImagePropertiesAsync 以取得圖像文件的屬性、為每個影像檔建立 Photo 模型物件 (將相同的屬性儲存在資料成員中),並將該 Photo 物件新增至集合。 Photo 物件的集合會系結至 UI 中的 GridView。 MainPage 代表該 GridView 處理 ContainerContentChanging 事件,並且在第 1 階段,該處理程序呼叫會呼叫 StorageFile.GetThumbnailAsync 的協程。 對 GetThumbnailAsync 的呼叫會導致訊息被提取 )它不會立即傳回,並執行其所有工作異步),這會導致重新進入。 結果是 GridView 在配置發生時已變更其 Items 集合,並導致當機。
如果我們將呼叫批注化為 StorageItemContentProperties::GetImagePropertiesAsync,則不會發生當機。 但真正的解決方法是讓 StorageFile.GetThumbnailAsync 透過協作等待來明確非同步叫用 wil::resume_foreground,之後叫用 GetThumbnailAsync。 這是因為 wil::resume_foreground 排程其後的程式代碼成為 DispatcherQueue 上的工作。
以下是要變更的程式代碼:
// MainPage.xaml.cpp
IAsyncAction MainPage::OnContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
...
if (args.Phase() == 1)
{
...
try
{
co_await wil::resume_foreground(this->DispatcherQueue());
auto thumbnail = co_await impleType->GetImageThumbnailAsync(this->DispatcherQueue());
image.Source(thumbnail);
}
...
}
}
相片
Photo::ImageTitle 屬性是系結至 UI 的資料,因此每當它需要其值時,UI 就會呼叫該屬性的存取子函式。 但是,當我們嘗試從 UI 執行緒上的該存取子函式存取 ImageProperties.Title 時,就會發生存取違規。
因此,我們可以從 Photo 的建構子一次存取該 Title,並將其儲存在 m_imageName 資料成員 (如果不為空) 中。 然後在 Photo::ImageTitle 存取器函式中,我們只需要存取 m_imageName 資料成員。
以下是要變更的程式代碼:
// Photo.h
...
Photo(Photo(Windows::Storage::FileProperties::ImageProperties const& props,
...
) : ...
{
if (m_imageProperties.Title() != L"")
{
m_imageName = m_imageProperties.Title();
}
}
...
hstring ImageTitle() const
{
return m_imageName;
}
...
這些是我們移 轉相片編輯器 範例應用程式所需的最後一項變更。 在測試已移轉的應用程式區段中,我們將確認已正確遵循這些步驟。
已知問題
應用程式類型問題 (僅影響 Preview 3)
如果您遵循此案例研究,使用 VSIX for Windows 應用程式 SDK 1.0 版 Preview 3 的專案範本,則必須對 進行小型修正PhotoEditor.vcxproj
。 方法如下所示。
在 Visual Studio 的方案總管中 ,右鍵點擊項目節點,然後按兩下卸載專案 。 現在 PhotoEditor.vcxproj
已開啟以供編輯。 作為 Project 的第一個子元素,加入一個 PropertyGroup 元素,如下所示:
<Project ... >
<PropertyGroup>
<EnableWin32Codegen>true</EnableWin32Codegen>
</PropertyGroup>
<Import ... />
...
儲存後關閉 PhotoEditor.vcxproj
。 以滑鼠右鍵按一下專案節點,然後點擊重新載入專案。 立即重新建置專案。
測試已移轉的應用程式
現在生成專案,並執行應用進行測試。 選取影像、設定縮放層級、選擇效果,並加以設定。
附錄:複製 Photo 模型檔案的內容
如先前所述,您可以選擇複製原始碼檔案本身,或 原始碼檔案的內容。 我們已經示範如何複製原始碼檔案本身。 因此,本節提供複製檔案內容的範例。
在 Visual Studio 的來源專案中,找出 PhotoEditor (Universal Windows)>Models 資料夾。 該資料夾包含檔案 Photo.idl
、Photo.h
和 Photo.cpp
,一起實作 Photo 執行時類別。
新增 IDL 並產生存根
在 Visual Studio 中的目標專案中,將新的 Midl 檔案 (.idl) 項目新增至專案。 將新項目命名為 Photo.idl
。 刪除 Photo.idl
預設內容。
從 Visual Studio 中的來源專案中,複製 Models>Photo.idl
的內容,並將它們貼到剛剛新增到目標專案的 Photo.idl
檔案中。 在您貼上的程式碼中,搜尋 Windows.UI.Xaml
,並將變更為 Microsoft.UI.Xaml
。
儲存檔案。
重要
我們即將執行目標解決方案的組建。 此時,建置不會執行到完成,但它將足以為我們執行必要的工作。
現在要建置目標方案。 儘管它不會完成,但現在建置是必要的,因為它將產生我們開始實作 Photo 模型所需的原始碼檔案 (存根)。
在 Visual Studio 中,右鍵按兩下目標項目節點,然後按兩下在檔案總管中打開資料夾。 這會在檔案總管中開啟目標專案資料夾。 在那裡,瀏覽至 Generated Files\sources
資料夾 (因此您會在 \PhotoEditor\PhotoEditor\PhotoEditor\Generated Files\sources
中)。 複製存根檔案 Photo.h
和 .cpp
,並將其貼到項目資料夾中,現在在中向上顯示兩個資料夾層級 \PhotoEditor\PhotoEditor\PhotoEditor
。
返回方案總管,選擇目標專案節點,確保開啟顯示所有檔案。 右鍵單擊剛剛貼上的存根檔案 (Photo.h
和 .cpp
),然後按一下包含在專案中。 切換關閉顯示所有檔案。
您將在 static_assert
和 Photo.h
內容的頂部看到 .cpp
,您需要將其刪除。
確認您可以再次建置 (但尚未執行)。
將程式代碼移轉至存根
將 Photo.h
和 .cpp
的內容從來源專案複製到目標專案。
從這裡開始,移轉您複製的程式碼的其餘步驟與移轉照片原始碼部分中給出的步驟相同。