A Windows App SDK migration of the UWP Photo Editor sample app (C++/WinRT)
This topic is a case study of taking the C++/WinRT UWP Photo Editor sample app, and migrating it to the Windows App SDK.
- Begin by cloning the UWP sample app's repo, and opening the solution in Visual Studio.
Important
For considerations and strategies for approaching the migration process, and how to set up your development environment for migrating, see Overall migration strategy.
Install tools for the Windows App SDK
To set up your development computer, see Install tools for the Windows App SDK.
Important
You'll find release notes topics along with the Windows App SDK release channels topic. There are release notes for each channel. 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.
Create a new project
- In Visual Studio, create a new C++/WinRT project from the Blank App, Packaged (WinUI 3 in Desktop) project template. Name the project PhotoEditor, uncheck Place solution and project in the same directory. You can target the most recent release (not preview) of the client operating system.
Note
We'll be referring to the UWP version of the sample project (the one that you cloned from its repo) as the source solution/project. We'll be referring to the Windows App SDK version as the target solution/project.
The order in which we'll migrate the code
MainPage is an important and prominent piece of the app. But if we were to begin by migrating that, then we'd soon realize that MainPage has a dependency on the DetailPage view; and then that DetailPage has a dependency on the Photo model. So for this walkthrough we'll take this approach.
- We'll begin by copying over the asset files.
- Then we'll migrate the Photo model.
- Next we'll migrate the App class (because that needs some members adding to it that DetailPage and MainPage will depend on).
- Then we'll begin migrating the views, starting with DetailPage first.
- And we'll finish up by migrating the MainPage view.
We'll be copying entire source code files
In this walkthrough we'll be copying over source code files using File Explorer. If you prefer to copy file contents over, then see the Appendix: copying the contents of the Photo model's files section at the end of this topic for an example of how you could do that for Photo (and you could then apply a similar procedure to other types in the project). That option does involve a lot more steps, though.
Copy asset files
In your clone of the source project, in File Explorer, locate the folder Windows-appsample-photo-editor > PhotoEditor > Assets. You'll find eight asset files in that folder. Select those eight files, and copy them to the clipboard.
Also in File Explorer, now locate the corresponding folder in the target project that you created. The path to that folder is PhotoEditor > PhotoEditor > Assets. Paste into that folder the asset files that you just copied, and accept the prompt to replace the seven files that already exist in the destination.
In your target project in Visual Studio, in Solution Explorer, expand the Assets folder. Add to that folder the existing
bg1.png
asset file that you just pasted. You can hover the mouse pointer over the asset files. A thumbnail preview will appear for each, confirming that you've replaced/added the asset files correctly.
Migrate the Photo model
Photo is a runtime class that represents a photo. It's a model (in the sense of models, views, and viewmodels).
Copy Photo source code files
In your clone of the source project, in File Explorer, locate the folder Windows-appsample-photo-editor > PhotoEditor. In that folder you'll find the three source code files
Photo.idl
,Photo.h
, andPhoto.cpp
; those files together implement the Photo runtime class. Select those three files, and copy them to the clipboard.In Visual Studio, right-click the target project node, and click Open Folder in File Explorer. This opens the target project folder in File Explorer. Paste into that folder the three files that you just copied.
Back in Solution Explorer, with the target project node selected, make sure that Show All Files is toggled on. Right-click the three files that you just pasted, and click Include In Project. Toggle Show All Files off.
In the source project, in Solution Explorer,
Photo.h
and.cpp
are nested underPhoto.idl
to indicate that they're generated from (dependent upon) it. If you like that arrangement, then you can do the same thing in the target project by manually editing\PhotoEditor\PhotoEditor\PhotoEditor\PhotoEditor.vcxproj
(you'll first need to Save All in Visual Studio). Find the following:<ClInclude Include="Photo.h" />
And replace it with this:
<ClInclude Include="Photo.h"> <DependentUpon>Photo.idl</DependentUpon> </ClInclude>
Repeat that for
Photo.cpp
, and save and close the project file. When you set focus back to Visual Studio, click Reload.
Migrate Photo source code
- In
Photo.idl
, search for the namespace nameWindows.UI.Xaml
(which is the namespace for UWP XAML), and change that toMicrosoft.UI.Xaml
(which is the namespace for WinUI XAML).
Note
The Mapping UWP APIs to the Windows App SDK topic provides a mapping of UWP APIs to their Windows App SDK equivalents. The change we made above is an example of a namespace name change necessary during the migration process.
In
Photo.cpp
, add#include "Photo.g.cpp"
to the existing include directives, immediately after#include "Photo.h"
. This is one of the Folder and file name differences (C++/WinRT) to be aware of between UWP and Windows App SDK projects.Make the following find/replacement (match case and whole word) in the contents of all of the source code in the files that you just copied and pasted.
Windows::UI::Xaml
=>Microsoft::UI::Xaml
From
pch.h
in the source project, copy the following includes, and paste them intopch.h
in the target project. This is a subset of the header files included in the source project; these are just the headers we need to support the code we've migrated so far.#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>
Now confirm that you can build the target solution (but don't run yet).
Migrate the App class
No changes are necessary to the target project's App.idl
and App.xaml
. But we do need to edit App.xaml.h
and App.xaml.cpp
to add some new members to the App class. We'll do so in a way that lets us build after each section (with the exception of the last section, which is about App::OnLaunched).
Making the main window object available
In this step we'll make the change that's explained in Change Windows.UI.Xaml.Window.Current to App.Window.
In the target project, App stores the main window object in its private data member window. Later in the migration process (when we migrate the source project's use of Window.Current), it'll be convenient if that window data member is static; and is also made available via an accessor function. So we'll make those changes next.
Since we're making window static, we'll need to initialize it in
App.xaml.cpp
instead of via the default member initializer that the code is currently using. Here are what those changes look like inApp.xaml.h
andApp.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
The Photo Editor sample app uses navigation logic to navigate between MainPage and DetailPage. For more info about Windows App SDK apps that need navigation (and those that don't), see Do I need to implement page navigation?.
So the members we'll migrate in the next few sections all exist to support navigation within the app.
Let's begin by migrating the OnNavigationFailed event handler. Copy the declaration and the definition of that member function from the source project, and paste it into the target project (in
App.xaml.h
andApp.xaml.cpp
).In the code you pasted into
App.xaml.h
, changeWindows::UI::Xaml
toMicrosoft::UI::Xaml
.
App::CreateRootFrame
The source project contains a helper function named App::CreateRootFrame. Copy the declaration and the definition of that helper function from the source project, and paste it into the target project (in
App.xaml.h
andApp.xaml.cpp
).In the code you pasted into
App.xaml.h
, changeWindows::UI::Xaml
toMicrosoft::UI::Xaml
.In the code you pasted into
App.xaml.cpp
, change the two occurrences ofWindow::Current()
towindow
(which is the name of the data member of the App class that we saw earlier).
App::OnLaunched
The target project already contains an implementation of the OnLaunched event handler. Its parameter is a constant reference to a Microsoft::UI::Xaml::LaunchActivatedEventArgs, which is correct for the Windows App SDK (contrast that to the source project, which uses Windows::ApplicationModel::Activation::LaunchActivatedEventArgs, which is correct for UWP).
We just need to merge the two definitions (source and target) of OnLaunched so that App::OnLaunched in
App.xaml.cpp
in the target project looks like the listing below. Note that it useswindow
(instead ofWindow::Current()
, like the UWP version does).void App::OnLaunched(LaunchActivatedEventArgs const&) { window = make<MainWindow>(); Frame rootFrame = CreateRootFrame(); if (!rootFrame.Content()) { rootFrame.Navigate(xaml_typename<PhotoEditor::MainPage>()); } window.Activate(); }
The code above gives App a dependency on MainPage, so we won't be able to build from this point until we've migrated DetailPage and then MainPage. When we're able to build again, we'll say so.
Migrate the DetailPage view
DetailPage is the class that represents the photo editor page, where Win2D effects are toggled, set, and chained together. You get to the photo editor page by selecting a photo thumbnail on MainPage. DetailPage is a view (in the sense of models, views, and viewmodels).
Reference the Win2D NuGet package
To support code in DetailPage, the source project has a dependency on Microsoft.Graphics.Win2D. So we'll also need a dependency on Win2D in our target project.
- In the target solution in Visual Studio, click Tools > NuGet Package Manager > Manage NuGet Packages for Solution... > Browse. Make sure that Include prerelease is unchecked, and type or paste Microsoft.Graphics.Win2D into the search box. Select the correct item in search results, check the PhotoEditor project, and click Install to install the package.
Copy DetailPage source code files
In your clone of the source project, in File Explorer, locate the folder Windows-appsample-photo-editor > PhotoEditor. In that folder you'll find the four source code files
DetailPage.idl
,DetailPage.xaml
,DetailPage.h
, andDetailPage.cpp
; those files together implement the DetailPage view. Select those four files, and copy them to the clipboard.In Visual Studio, right-click the target project node, and click Open Folder in File Explorer. This opens the target project folder in File Explorer. Paste into that folder the four files that you just copied.
Still in File Explorer, change the names of
DetailPage.h
andDetailPage.cpp
toDetailPage.xaml.h
andDetailPage.xaml.cpp
, respectively. This is one of the Folder and file name differences (C++/WinRT) to be aware of between UWP and Windows App SDK projects.Back in Solution Explorer, with the target project node selected, make sure that Show All Files is toggled on. Right-click the four files that you just pasted (and renamed), and click Include In Project. Toggle Show All Files off.
In the source project, in Solution Explorer,
DetailPage.idl
is nested underDetailPage.xaml
. If you like that arrangement, then you can do the same thing in the target project by manually editing\PhotoEditor\PhotoEditor\PhotoEditor\PhotoEditor.vcxproj
(you'll first need to Save All in Visual Studio). Find the following:<Midl Include="DetailPage.idl" />
And replace it with this:
<Midl Include="DetailPage.idl"> <DependentUpon>DetailPage.xaml</DependentUpon> </Midl>
Save and close the project file. When you set focus back to Visual Studio, click Reload.
Migrate DetailPage source code
In
DetailPage.idl
, search forWindows.UI.Xaml
, and change that toMicrosoft.UI.Xaml
.In
DetailPage.xaml.cpp
, change#include "DetailPage.h"
to#include "DetailPage.xaml.h"
.Immediately below that, add
#include "DetailPage.g.cpp"
.For the call to the static App::Window method (which we're about to add) to compile, still in
DetailPage.xaml.cpp
, add#include "App.xaml.h"
immediately before#include "Photo.h"
.Make the following find/replacements (match case and whole word) in the contents of the source code in the files that you just copied and pasted.
- In
DetailPage.xaml.h
and.xaml.cpp
,Windows::UI::Composition
=>Microsoft::UI::Composition
- In
DetailPage.xaml.h
and.xaml.cpp
,Windows::UI::Xaml
=>Microsoft::UI::Xaml
- In
DetailPage.xaml.cpp
,Window::Current()
=>App::Window()
- In
From
pch.h
in the source project, copy the following includes, and paste them intopch.h
in the target project.#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>
Also, at the top of
pch.h
, immediately after#pragma once
, add this:// 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
We can't build yet, but we will be able to after migrating MainPage (which is next).
Migrate the MainPage view
The main page of the app represents the view that you see first when you run the app. It's the page that loads the photos from the Pictures Library, and displays a tiled thumbnail view.
Copy MainPage source code files
Similar to what you did with DetailPage, now copy over
MainPage.idl
,MainPage.xaml
,MainPage.h
, andMainPage.cpp
.Rename the
.h
and.cpp
files to.xaml.h
and.xaml.cpp
, respectively.Include all four files in the target project like before.
In the source project, in Solution Explorer,
MainPage.idl
is nested underMainPage.xaml
. If you like that arrangement, then you can do the same thing in the target project by manually editing\PhotoEditor\PhotoEditor\PhotoEditor\PhotoEditor.vcxproj
. Find the following:<Midl Include="MainPage.idl" />
And replace it with:
<Midl Include="MainPage.idl"> <DependentUpon>MainPage.xaml</DependentUpon> </Midl>
Migrate MainPage source code
In
MainPage.idl
, search forWindows.UI.Xaml
, and change both occurrences toMicrosoft.UI.Xaml
.In
MainPage.xaml.cpp
, change#include "MainPage.h"
to#include "MainPage.xaml.h"
.Immediately below that, add
#include "MainPage.g.cpp"
.For the call to the static App::Window method (which we're about to add) to compile, still in
MainPage.xaml.cpp
, add#include "App.xaml.h"
immediately before#include "Photo.h"
.
For the next step, we'll make the change that's explained in ContentDialog, and Popup.
So, still in
MainPage.xaml.cpp
, in the MainPage::GetItemsAsync method, immediately after the lineContentDialog unsupportedFilesDialog{};
, add this line of code.unsupportedFilesDialog.XamlRoot(this->Content().XamlRoot());
Make the following find/replacements (match case and whole word) in the contents of the source code in the files that you just copied and pasted.
- In
MainPage.xaml.h
and.xaml.cpp
,Windows::UI::Composition
=>Microsoft::UI::Composition
- In
MainPage.xaml.h
and.xaml.cpp
,Windows::UI::Xaml
=>Microsoft::UI::Xaml
- In
MainPage.xaml.cpp
,Window::Current()
=>App::Window()
- In
From
pch.h
in the source project, copy the following includes, and paste them intopch.h
in the target project.#include <winrt/Microsoft.UI.Xaml.Hosting.h> #include <winrt/Microsoft.UI.Xaml.Media.Animation.h> #include <winrt/Windows.Storage.Search.h>
Confirm that you can build the target solution (but don't run yet).
Update MainWindow
In
MainWindow.xaml
, delete the StackPanel and its contents, since we don't need any UI in MainWindow. That leaves only the empty Window element.In
MainWindow.idl
, delete the placeholderInt32 MyProperty;
, leaving only the constructor.In
MainWindow.xaml.h
andMainWindow.xaml.cpp
, delete the declarations and definitions of the placeholder MyProperty and myButton_Click, leaving only the constructor.
Migration changes needed for threading model difference
The two changes in this section are necessary due to a threading model difference between UWP and the Windows App SDK, as described in ASTA to STA threading model. Here are brief descriptions of the causes of the issues, and then a way to resolve each.
MainPage
MainPage loads image files from your Pictures folder, calls StorageItemContentProperties.GetImagePropertiesAsync to get the image file's properties, creates a Photo model object for each image file (saving those same properties in a data member), and adds that Photo object to a collection. The collection of Photo objects is data-bound to a GridView in the UI. On behalf of that GridView, MainPage handles the ContainerContentChanging event, and for phase 1 that handler calls into a coroutine that calls StorageFile.GetThumbnailAsync. This call to GetThumbnailAsync results in messages being pumped (it doesn't return immediately, and do all of its work async), and that causes reentrancy. The result is that the GridView has its Items collection changed while layout is taking place, and that causes a crash.
If we comment out the call to StorageItemContentProperties::GetImagePropertiesAsync, then we don't get the crash. But the real fix is to make the StorageFile.GetThumbnailAsync call be explicitly async by cooperatively awaiting wil::resume_foreground immediately before calling GetThumbnailAsync. This works because wil::resume_foreground schedules the code that follows it to be a task on the DispatcherQueue.
Here's the code to change:
// 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
The Photo::ImageTitle property is data-bound to the UI, so the UI calls into the accessor function for that property whenever it needs its value. But when we try to access ImageProperties.Title from that accessor function on the UI thread, we get an access violation.
So instead, we can just access that Title one-time, from the constructor of Photo, and store it in the m_imageName data member if it's not empty. Then in the Photo::ImageTitle accessor function we need only to access the m_imageName data member.
Here's the code to change:
// 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;
}
...
Those are the last of the changes we need to make to migrate the Photo Editor sample app. In the Test the migrated app section we'll confirm that we've correctly followed the steps.
Known issues
App type issue (affects only Preview 3)
If you followed along with this case study using the project template from the VSIX for Windows App SDK version 1.0 Preview 3, then you'll need to make a small correction to PhotoEditor.vcxproj
. Here's how to do that.
In Visual Studio, in Solution Explorer, right-click the project node, and click Unload Project. Now PhotoEditor.vcxproj
is open for editing. As the first child of Project, add a PropertyGroup element like this:
<Project ... >
<PropertyGroup>
<EnableWin32Codegen>true</EnableWin32Codegen>
</PropertyGroup>
<Import ... />
...
Save and close PhotoEditor.vcxproj
. Right-click the project node, and click Reload Project. Now rebuild the project.
Test the migrated app
Now build the project, and run the app to test it. Select an image, set a zoom level, choose effects, and configure them.
Appendix: copying the contents of the Photo model's files
As we discussed earlier, you have the option to copy over source code files themselves, or the contents of source code files. We've already shown how to copy over source code files themselves. So this section gives an example of copying file contents.
In the source project in Visual Studio, locate the folder PhotoEditor (Universal Windows) > Models. That folder contains the files Photo.idl
, Photo.h
, and Photo.cpp
, which together implement the Photo runtime class.
Add the IDL, and generate stubs
In your target project in Visual Studio, add a new Midl File (.idl) item to the project. Name the new item Photo.idl
. Delete the default contents of Photo.idl
.
From the source project in Visual Studio, copy the contents of Models > Photo.idl
, and paste them into the Photo.idl
file that you just added to your target project. In the code you pasted, search for Windows.UI.Xaml
, and change that to Microsoft.UI.Xaml
.
Save the file.
Important
We're about to perform a build of your target solution. The build won't run to completion at this point, but it will get far enough to do necessary work for us.
Now build the target solution. Even though it won't complete, building is necessary now because it'll generate the source code files (stubs) that we need to get started implementing the Photo model.
In Visual Studio, right-click the target project node, and click Open Folder in File Explorer. This opens the target project folder in File Explorer. There, navigate into the Generated Files\sources
folder (so you'll be in \PhotoEditor\PhotoEditor\PhotoEditor\Generated Files\sources
). Copy the stub files Photo.h
and .cpp
, and paste them into the project folder, which is now up two folder levels in \PhotoEditor\PhotoEditor\PhotoEditor
.
Back in Solution Explorer, with the target project node selected, make sure that Show All Files is toggled on. Right-click the stub files that you just pasted (Photo.h
and .cpp
), and click Include In Project. Toggle Show All Files off.
You'll see a static_assert
at the top of the contents of Photo.h
and .cpp
, which you'll need to delete.
Confirm that you can build again (but don't run yet).
Migrate code into the stubs
Copy the contents of Photo.h
and .cpp
from the source project into the target project.
From here, the remaining steps to migrate the code that you copied are the same as those given in the Migrate Photo source code section.
Related topics
Windows developer