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

В этом разделе приводится пример использования примера приложения "Редактор фотографий UWP UWP" C++/WinRT и его миграции на Windows App SDK.

Важно!

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

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

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

Важно!

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

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

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

Примечание

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

Порядок, в котором мы будем переносить код

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

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

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

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

Копирование файлов ресурсов

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

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

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

Перенос модели "Фото"

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

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

  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 пространства имен (которое является пространством имен для UWP XAML) и измените его на Microsoft.UI.Xaml (которое является пространством имен для WinUI XAML).

Примечание

В разделе Сопоставление API UWP с Windows App SDK приводится сопоставление API UWP с их эквивалентами Windows App SDK. Внесенное выше изменение является примером изменения имени пространства имен, необходимого в процессе миграции.

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

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

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

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

В целевом проекте Приложение сохраняет объект окна main в окне члена личных данных. Далее в процессе миграции (при переносе исходного проекта с использованием 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, который является правильным для Windows App SDK (в отличие от исходного проекта, в котором используется 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();
    }
    

Приведенный выше код дает приложению зависимость от MainPage, поэтому мы не сможем выполнить сборку с этого момента, пока мы не перенесли DetailPage , а затем MainPage. Когда мы снова сможем выполнить сборку, мы это скажем.

Перенос представления DetailPage

DetailPage — это класс, представляющий страницу редактора фотографий, где эффекты Win2D переключаются, задаются и объединяются в цепочку. Чтобы открыть страницу редактора фотографий, выберите эскиз фотографии на MainPage. DetailPage — это представление (в смысле моделей, представлений и моделей представлений).

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

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

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

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

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

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

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

  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. Для компиляции DetailPage.xaml.cppвызова статического метода App::Window (который мы собираемся добавить) добавьте #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
    • В 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

Страница main приложения представляет представление, которое вы увидите впервые при запуске приложения. Это страница, которая загружает фотографии из библиотеки изображений и отображает мозаичное представление эскизов.

Копирование файлов исходного кода 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. Для компиляции MainPage.xaml.cppвызова статического метода App::Window (который мы собираемся добавить) добавьте #include "App.xaml.h" непосредственно перед #include "Photo.h".

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

  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
    • В 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.xamlудалите StackPanel и его содержимое, так как нам не нужен пользовательский интерфейс в MainWindow. При этом остается только пустой элемент Window .

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

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

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

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

MainPage

MainPage загружает файлы изображений из папки Изображения , вызывает StorageItemContentProperties.GetImagePropertiesAsync , чтобы получить свойства файла изображения, создает объект модели Photo для каждого файла изображения (сохраняя те же свойства в элементе данных) и добавляет этот объект Photo в коллекцию. Коллекция объектов Photo привязана к данным GridView в пользовательском интерфейсе. От имени этого GridViewMainPage обрабатывает событие 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

Свойство Photo::ImageTitle привязано к данным пользовательского интерфейса, поэтому пользовательский интерфейс вызывает функцию доступа для этого свойства, когда ему требуется его значение. Но при попытке получить доступ к ImageProperties.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 версии 1.0, предварительная версия 3, необходимо внести небольшое исправление в PhotoEditor.vcxproj. Вот как это сделать.

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

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

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

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

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

Приложение. Копирование содержимого файлов фото-модели

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

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

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

В целевом проекте в Visual Studio добавьте в проект новый элемент Midl File (.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 в целевой проект.

Отсюда оставшиеся действия по переносу скопированного кода совпадают с действиями, описанными в разделе Миграция исходного кода фотографии.