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


Миграция пакета SDK для приложений для Windows примера приложения редактора фотографий UWP (C++/WinRT)

Эта тема является исследованием примеров приложения-образца редактора фотографий C++/WinRT UWP и его миграции в комплект SDK для приложений Windows.

Внимание

Рекомендации и стратегии для подхода к процессу миграции и настройке среды разработки для миграции см. в статье "Общая стратегия миграции".

Установка инструментов для Windows App SDK

Чтобы настроить среду разработки на компьютере, выполните инструкции из статьи Установка средств для пакета SDK для приложений Windows.

Внимание

Среди разделов с заметками о выпуске вы найдете раздел Каналы выпуска Windows App SDK. Существуют релизные заметки для каждого канала. Не забудьте проверить все ограничения и известные проблемы в этих примечаниях к выпуску, так как они могут повлиять на результаты исследования данного примера и/или запуска перенесенного приложения.

Создание нового проекта

  • В Visual Studio создайте новый проект C++/WinRT из шаблона проекта Blank App, Packaged (WinUI 3 в Desktop). Назовите проект PhotoEditor, снимите флажок Разместить решение и проект в одном каталоге. В качестве целевой версии вы можете выбрать последний выпуск (не предварительную версию) операционной системы клиента.

Примечание.

Мы будем ссылаться на версию примера проекта UWP (клонированную из репозитория) в качестве исходного решения или проекта. Мы будем называть версию пакета SDK для приложений Windows в качестве целевого решения или проекта.

Порядок переноса кода

MainPage — это важная и видная часть приложения. Но если бы мы начали переносить это, то вскоре мы поняли, что MainPage имеет зависимость от представления DetailPage; а затем, что DetailPage имеет зависимость от модели Photo. Поэтому для этого пошагового руководства мы рассмотрим этот подход.

  • Начнем с копирования файлов ресурсов.
  • Затем мы переносим модель фото .
  • Далее мы переносим класс App (так как для этого требуются некоторые члены, добавляемые в него, от этого будет зависеть DetailPage и MainPage).
  • Затем мы начнем миграцию представлений, начиная с DetailPage.
  • И мы завершим работу, перенеся представление MainPage.

Мы будем копировать все файлы исходного кода

В этом руководстве мы будем копировать файлы исходного кода с помощью Проводника. Если вы предпочитаете копировать содержимое файлов, , просмотрите приложение: копирование содержимого файлов модели Photo в конце этого раздела, чтобы узнать пример того, как это можно сделать для Photo (а затем вы можете применить аналогичную процедуру и к другим типам в проекте). Этот вариант включает гораздо больше шагов, однако.

Копирование файлов активов

  1. В вашем клоне исходного проекта в Проводник найдите папку Windows-appsample-photo-editor>PhotoEditor>Assets. В этой папке вы найдете восемь файлов ресурсов. Выберите эти восемь файлов и скопируйте их в буфер обмена.

  2. Также в Проводнике найдите соответствующую папку в созданном целевом проекте. Путь к этой папке — PhotoEditor>PhotoEditor>Assets. Вставьте в эту папку файлы ресурсов, скопированные только что, и примите запрос на замену семи файлов, которые уже существуют в назначении.

  3. В целевом проекте в Visual Studio в Обозреватель решений разверните папку "Ресурсы". Добавьте в эту папку ресурсный файл bg1.png, который вы только что вставили. Наведите указатель мыши на ресурсные файлы. Миниатюры будут отображаться для каждого, подтверждая, что вы правильно заменили или добавили файлы ресурсов.

Перенести модель фотографии

Фотография — это класс среды выполнения, представляющий фотографию. Это модель (в смысле моделей, представлений и моделей представления).

Копирование файлов исходного кода фотографии

  1. В вашем клоне исходного проекта в Проводник найдите папку Windows-appsample-photo-editor>PhotoEditor. В этой папке вы найдете три файла исходного кодаPhoto.idl, Photo.hа Photo.cppтакже эти файлы вместе реализуют класс среды выполнения Photo. Выберите эти три файла и скопируйте их в буфер обмена.

  2. В Visual Studio щелкните правой кнопкой мыши на узле целевого проекта и выберите «Открыть папку в Проводнике». Откроется целевая папка проекта в Проводник. Вставьте в эту папку только что скопированные три файла.

  3. Вернитесь в Обозреватель решений с выбранным целевым узлом проекта, убедитесь, что включен переключатель "Показать все файлы". Щелкните правой кнопкой мыши три файла, которые вы только что вставили, и нажмите кнопку "Включить в проект". Переключатель "Показать все файлы " отключен.

  4. В исходном проекте в Обозревателе решений, 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 нажмите кнопку "Перезагрузить".

Перенос исходного кода фотографии

  1. В Photo.idl выполните поиск пространства имен Windows.UI.Xaml (которое является пространством имен для XAML UWP) и измените это на Microsoft.UI.Xaml (которое является пространством имен для XAML WinUI).

Примечание.

Тема Mapping UWP APIs to the Windows App SDK предоставляет сопоставление API UWP с их эквивалентами в Windows App SDK. Приведенное выше изменение является примером изменения имени пространства имен, необходимого во время процесса миграции.

  1. Добавьте Photo.cpp#include "Photo.g.cpp" в существующие директивы include сразу после#include "Photo.h". Это одно из различий имен папок и файлов (C++/WinRT), которые следует учитывать между проектами пакета SDK для приложений UWP и Windows.

  2. Сделайте следующее средство поиска и замены (регистр соответствия и целое слово) в содержимом всего исходного кода в файлах, которые вы только что скопировали и вставили.

    • Windows::UI::Xaml =>Microsoft::UI::Xaml
  3. Скопируйте включенные файлы из 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>
    
  4. Теперь убедитесь, что вы можете создать целевое решение (но пока не запускайте его).

Перенос класса App

Никаких изменений не требуется для целевого проекта App.idl и App.xaml. Но нам необходимо изменить App.xaml.h, а также App.xaml.cpp, чтобы добавить некоторые новые члены в класс App. Мы будем делать это так, чтобы можно было продолжать работу после каждого раздела (за исключением последнего раздела, который касается App::OnLaunched).

Предоставление доступа к объекту основного окна

На этом шаге мы внесите изменения, описанные в разделе "Изменение Windows.UI.Xaml.Window.Current на App.Window.

В целевом проекте Приложение удерживает объект главного окна в своем частном члене данных. Далее в процессе миграции (когда мы мигрируем использование 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 App SDK, которым требуется навигация, и тех, которым не требуется, см. в статье «Необходимо ли реализовать навигацию по страницам?».

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

  1. Начнем с миграции обработчика событий OnNavigationFailed . Скопируйте объявление и определение этой функции-члена из исходного проекта и вставьте его в целевой проект (в App.xaml.h и App.xaml.cpp).

  2. В коде, который вы вставили в App.xaml.h, измените Windows::UI::Xaml на Microsoft::UI::Xaml.

App::CreateRootFrame

  1. Исходный проект содержит вспомогательные функции с именем App::CreateRootFrame. Скопируйте объявление и определение этой вспомогательной функции из исходного проекта и вставьте ее в целевой проект (в App.xaml.h и App.xaml.cpp).

  2. В коде, который вы вставили в App.xaml.h, измените Windows::UI::Xaml на Microsoft::UI::Xaml.

  3. В коде, который вы вставили в App.xaml.cpp, измените два вхождения Window::Current() на window (это имя члена данных класса App, который мы видели ранее).

App::OnLaunched

Целевой проект уже содержит реализацию обработчика событий OnLaunched . Его параметр является постоянной ссылкой на Microsoft::UI::Xaml::LaunchActivatedEventArgs, что правильно для пакета SDK для приложений Windows (контрастирует с исходным проектом, использующим Windows::ApplicationModel::Activation::LaunchActivatedEventArgs, что правильно для UWP).

  • Нам просто нужно объединить два определения (источник и цель) OnLaunched, чтобы App::OnLaunched в App.xaml.cpp целевом проекте выглядело следующим образом. Обратите внимание, что он использует 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 — это представление (в смысле моделей, представлений и моделей представлений).

Ссылка на пакет NuGet Win2D

Для поддержки кода в DetailPage исходный проект имеет зависимость от Microsoft.Graphics.Win2D. Поэтому нам также потребуется зависимость от Win2D в целевом проекте.

  • В целевом решении в Visual Studio щелкните Tools>Диспетчер пакетов NuGet>Управление пакетами NuGet для решения...>Обзор. Убедитесь, что флажок "Включить предварительную версию " снят и введите или вставьте Microsoft.Graphics.Win2D в поле поиска. Выберите правильный элемент в результатах поиска, проверьте проект PhotoEditor и нажмите кнопку "Установить ", чтобы установить пакет.

Копирование файлов исходного кода DetailPage

  1. В клоне исходного проекта в Проводник найдите папку Windows-appsample-photo-editor>PhotoEditor. В этой папке вы найдете четыре файла исходного кода DetailPage.idl, DetailPage.xamlDetailPage.hиDetailPage.cpp; эти файлы вместе реализуют представление DetailPage. Выберите эти четыре файла и скопируйте их в буфер обмена.

  2. В Visual Studio щелкните правой кнопкой мыши узел целевого проекта и выберите Открыть папку в Проводнике. Целевая папка проекта откроется в Проводнике. Вставьте в эту папку только что скопированные четыре файла.

  3. По-прежнему в Проводник, измените имена DetailPage.h и DetailPage.cpp на DetailPage.xaml.h и DetailPage.xaml.cpp соответственно. Это одно из различий имен папок и файлов (C++/WinRT), которые следует учитывать между проектами пакета SDK для приложений UWP и Windows.

  4. Вернитесь в Обозреватель решений с выбранным целевым узлом проекта, убедитесь, что включен переключатель "Показать все файлы". Щелкните правой кнопкой мыши четыре файла, которые вы только что вставили (и переименовали), и нажмите кнопку "Включить в проект". Переключатель "Показать все файлы " отключен.

  5. Элемент 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

  1. В DetailPage.idl, найдите Windows.UI.Xamlи измените это на Microsoft.UI.Xaml.

  2. В DetailPage.xaml.cpp измените #include "DetailPage.h" на #include "DetailPage.xaml.h".

  3. Сразу после этого добавьте #include "DetailPage.g.cpp".

  4. Для того чтобы вызов статического метода App::Window (который мы собираемся добавить) успешно скомпилировался, в DetailPage.xaml.cpp добавьте #include "App.xaml.h" непосредственно перед #include "Photo.h".

  5. Выполните указанные ниже действия поиска и замены (регистр соответствия и целое слово) в содержимом исходного кода в только что скопированных и вставленных файлах.

    • В DetailPage.xaml.h и .xaml.cpp, Windows::UI::Composition =>Microsoft::UI::Composition
    • В DetailPage.xaml.h и .xaml.cpp, Windows::UI::Xaml =>Microsoft::UI::Xaml
    • In DetailPage.xaml.cpp, Window::Current() =>App::Window()
  6. Скопируйте следующие включения из 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>
    
  7. Кроме того, в верхней части 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

  1. Аналогично тому, что вы сделали с DetailPage, теперь скопируйте MainPage.idl, MainPage.xaml, MainPage.h и MainPage.cpp.

  2. Переименуйте файлы .h и .cpp на .xaml.h и .xaml.cpp соответственно.

  3. Включите все четыре файла в целевой проект, как и раньше.

  4. В исходном проекте в Обозревателе решений элемент MainPage.idl вложен под MainPage.xaml. Если вам нравится это расположение, то вы можете сделать то же самое в целевом проекте, вручную изменив \PhotoEditor\PhotoEditor\PhotoEditor\PhotoEditor.vcxproj. Найдите следующее:

    <Midl Include="MainPage.idl" />
    

    И замените ее на:

    <Midl Include="MainPage.idl">
      <DependentUpon>MainPage.xaml</DependentUpon>
    </Midl>
    

Перенос исходного кода MainPage

  1. В MainPage.idl выполните поиск Windows.UI.Xaml, и измените оба вхождения на Microsoft.UI.Xaml.

  2. В MainPage.xaml.cpp измените #include "MainPage.h" на #include "MainPage.xaml.h".

  3. Сразу после этого добавьте #include "MainPage.g.cpp".

  4. Для получения возможности компиляции вызова статического метода App::Window (который мы собираемся добавить), всё ещё находясь в MainPage.xaml.cpp, добавьте #include "App.xaml.h" непосредственно перед #include "Photo.h".

На следующем шаге мы внесем изменения, описанные в ContentDialog и Popup.

  1. Таким образом, по-прежнему в MainPage.xaml.cppметоде MainPage::GetItemsAsync сразу после строки ContentDialog unsupportedFilesDialog{};добавьте эту строку кода.

    unsupportedFilesDialog.XamlRoot(this->Content().XamlRoot());
    
  2. Выполните указанные ниже действия поиска и замены (регистр соответствия и целое слово) в содержимом исходного кода в только что скопированных и вставленных файлах.

    • В MainPage.xaml.h и .xaml.cpp, Windows::UI::Composition =>Microsoft::UI::Composition
    • В MainPage.xaml.h и .xaml.cpp, Windows::UI::Xaml =>Microsoft::UI::Xaml
    • In MainPage.xaml.cpp, Window::Current() =>App::Window()
  3. Скопируйте следующие включения из 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

  1. Удалите MainWindow.xamlStackPanel и его содержимое, так как нам не нужен пользовательский интерфейс в MainWindow. Это оставляет только пустой элемент Window .

  2. Удалите в MainWindow.idl заполнитель Int32 MyProperty;, оставив только конструктор.

  3. В MainWindow.xaml.h и MainWindow.xaml.cpp, удалите объявления и определения заполнителя MyProperty и myButton_Click, оставив только конструктор.

Изменения в миграции, необходимые для различий в модели потоков

Эти два изменения в этом разделе необходимы из-за разницы в потоковой модели между UWP и Windows App SDK, как описано в модель потоков ASTA и STA. Ниже приведены краткие описания причин проблем, а затем способ устранения каждого из них.

Главная Страница

MainPage загружает файлы изображений из папки "Изображения", вызывает StorageItemContentProperties.GetImagePropertiesAsync для получения свойств файла изображения, создаетобъект модели фото для каждого файла изображения (сохраняя те же свойства в элементе данных) и добавляет этот объект Photo в коллекцию. Коллекция объектов Photo привязана к GridView в пользовательском интерфейсе. От имени этого GridViewMainPage обрабатывает событие ContainerContentChanging, и на этапе 1 этот обработчик вызывает корутину, которая вызывает StorageFile.GetThumbnailAsync. Этот вызов GetThumbnailAsync приводит к перекачиваемым сообщениям (он не возвращается немедленно и выполняет все его асинхронное выполнение) и приводит к повторному входу. В результате коллекция объектов в GridView изменена в процессе выполнения макета, что вызывает сбой.

Если мы закомментируем вызов 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 привязано к пользовательскому интерфейсу, поэтому пользовательский интерфейс вызывает функцию доступа для этого свойства при необходимости его значения. Но когда мы пытаемся получить доступ к ImageProperties.Title из этой функции доступа в потоке пользовательского интерфейса, мы получаем нарушение доступа.

Таким образом, мы можем просто получить доступ к Title один раз из конструктора Photo и сохранить его в элементе данных 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;
}
...

Это последние изменения, которые необходимо внести для переноса примера приложения "Редактор фотографий". В разделе "Тестирование перенесенного приложения" мы убедимся, что мы правильно выполнили действия.

Известные проблемы

Проблема с типом приложения (влияет только на предварительную версию 3)

Если вы следовали этому кейсу, используя шаблон проекта из VSIX для Windows App SDK Предварительная версия 3, вам необходимо внести небольшое исправление в PhotoEditor.vcxproj. Вот как это сделать.

В Visual Studio, в Обозревателе решений щелкните правой кнопкой мыши на узле проекта и выберите Выгрузить проект. Теперь PhotoEditor.vcxproj открыт для редактирования. В качестве первого дочернего элемента Project добавьте элемент PropertyGroup следующим образом:

<Project ... >
    <PropertyGroup>
        <EnableWin32Codegen>true</EnableWin32Codegen>
    </PropertyGroup>
    <Import ... />
...

Сохраните и закройте PhotoEditor.vcxproj. Щелкните правой кнопкой мыши узел проекта и щелкните " Перезагрузить проект". Теперь перестроите проект.

Тестирование перенесенного приложения

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

Приложение: копирование содержимого файлов модели Photo

Как мы уже говорили ранее, вы можете скопировать файлы исходного кода самостоятельно или содержимое файлов исходного кода. Мы уже показали, как скопировать файлы исходного кода самостоятельно. Поэтому в этом разделе приведен пример копирования содержимого файла.

В исходном проекте в Visual Studio найдите папку PhotoEditor (универсальная платформа Windows)>Models. В этой папке содержатся файлы Photo.idlи Photo.hPhoto.cpp, которые вместе реализуют класс среды выполнения Photo.

Добавьте IDL и создайте заглушки

В целевом проекте в Visual Studio добавьте новый элемент Midl File (.idl). Присвойте новому элементу имя Photo.idl. Удалите содержимое Photo.idlпо умолчанию.

Скопируйте содержимое моделейPhoto.idlпроекта в Visual Studio и вставьте их в 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 из исходного проекта в целевой проект.

Отсюда оставшиеся шаги для переноса скопированного кода такие же, как в разделе Миграция исходного кода Фото.