Портируем C#/XAML приложение Windows 8.1 на UWP

278440b656d14de0bad4900f559d32f4.jpg


Если ваше приложение Windows 8 или 8.1 небольшое, то вы можете создать новый проект универсального приложения UWP и перенести в него код XAML и C#. Если же приложение содержит достаточный объем кода, то есть другие варианты.

Для того чтобы портировать WinRT C#/XAML приложение Windows 8.x на Windows UWP необходимо изменить манифест и файл проекта. Способа сделать это из среды Visual Studio пока что нет, но можно воспользоваться скриптом, который совершит некоторые автоматические операции, взяв на себя большую часть работы.
Найти скрипт можно по ссылке: GitHub Win10DevGuideMVA / ProjectUpgradeUtility

Оттуда скачиваем 2 файла:
Upgrade_to_uwp.ps1 – сам скрипт
Run_Upgrade_to_uwp.bat — командный файл для того, чтобы запустить скрипт автоматически.

Если захотите сделать все вручную, то можете воспользоваться официальной инструкцией о том, как вручную перенести приложение с Windows 8.1 на универсальную платформу Windows (UWP):
Перенос приложений на универсальную платформу Windows (UWP)

С обновлением файла проекта скрипт справляется на проверку неплохо, а вот про обновление манифеста напишу подробнее.
Можно обновить файл манифеста вручную, добавив некоторым элементам префикс uap:
Следующий код манифеста

<Application>
        <Extension Category="windows.protocol">  
          <Protocol Name="mailto" DesiredView="useHalf">  
            <DisplayName>MailTo Protocol</DisplayName>  
          </Protocol>  
        </Extension>  
  </Application>


Превращается в

<Application>
        <uap:Extension Category="windows.protocol">  
          <uap:Protocol Name="mailto" DesiredView="useHalf">  
           <uap:DisplayName>MailTo Protocol</uap:DisplayName>  
          </uap:Protocol>  
        </uap:Extension>  
  </Application>


Некоторые атрибуты необходимо удалить. Пример:

      <m2:VisualElements DisplayName="App1"
                         Square150x150Logo="Assets\Logo.png" 
                         Square30x30Logo="Assets\SmallLogo.png" 
                         Description="some description" 
                         BackgroundColor="#464646" 
                         ForegroundText="light" 
                         ToastCapable="true">
        <m2:SplashScreen Image="Assets\SplashScreen.png" />
        <m2:DefaultTile ShortName="MyApp" 
                        Wide310x150Logo="310x150.png" 
                        Square310x310Logo="310x310.png" 
                        Square70x70Logo="70x70.png" 
                        DefaultSize="square150x150Logo">
        </m2:DefaultTile>
        <m2:ApplicationView MinWidth="width320" />
      </m2:VisualElements>


Из этого кода нам нужно убрать:
ForegroundText=«light»
ToastCapable=«true»
DefaultSize=«square150x150Logo»
<m2:ApplicationView MinWidth=«width320»/>

Кроме этого нужно изменить размеры тайлов (не забудьте заменить файлы изображений тоже).
В коде манифеста Square30x30Logo заменяем на Square44x44Logo, а Square70x70Logo заменяем на Square71x71Logo.
Например:

      <m2:VisualElements DisplayName="App1"
                         Square150x150Logo="Assets\Logo.png" 
                         Square30x30Logo="Assets\SmallLogo.png" 
                         Description="some description" 
                         BackgroundColor="#464646">
        <m2:SplashScreen Image="Assets\SplashScreen.png" />
        <m2:DefaultTile ShortName="MyApp" 
                        Wide310x150Logo="310x150.png" 
                        Square310x310Logo="310x310.png" 
                        Square70x70Logo="70x70.png">
        </m2:DefaultTile>
      </m2:VisualElements>


Изменяем на

      <uap:VisualElements DisplayName="App1" 
                             Square150x150Logo="Assets\Logo.png" 
                             Square44x44Logo="Assets\SmallLogo.png" 
                             Description="some description" 
                             BackgroundColor="#464646">
        <uap:SplashScreen Image="Assets\SplashScreen.png" />
        <uap:DefaultTile ShortName="MyApp"
                             Wide310x150Logo="310x150.png" 
                             Square310x310Logo="310x310.png" 
                             Square71x71Logo="71x71.png">
        </uap:DefaultTile>
      </uap:VisualElements>


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

  <Prerequisites>
    <OSMinVersion>6.3.0</OSMinVersion>
    <OSMaxVersionTested>6.3.0</OSMaxVersionTested>
 </Prerequisites>


Полный список изменений в манифесте вы найдете на страничке What's different in Windows 10

Это была теория. Далее я постараюсь описать, как у меня происходил процесс обновления приложения.

Так как у меня 32-ух разрядная система, то в файле скрипта Upgrade_to_uwp.ps1 я изменил 6-ую строчку с
$FolderPath = «C:\Program Files (x86)\Windows Kits\10\Platforms\UAP»
на
$FolderPath = «C:\Program Files\Windows Kits\10\Platforms\UAP»

Как правило, по умолчанию PowerShell сконфигурирован на запрет выполнения скриптов. Поэтому необходимо временно разрешить запуск неподписанных скриптов.
Открыв PowerShell с правами администратора я запустил команду
Set-ExecutionPolicy Unrestricted
И установил параметром Y.

Запустил скрипт.

Получил вот такой вот отчет
PS D:\Porting to Windows 10\TesT10\TesT> D:\Porting to Windows 10\TesT10\TesT\Upgrade_to_uwp.ps1
Converting TesT.csproj...
True
Недопустимый шаблон регулярного выражения <ItemGroup>
    <AppxManifest Include="Package.appxmanifest">
      <SubType>Designer</SubType>
    </AppxManifest>
    <None Include="TesT_StoreKey.pfx" />
    <None Include="TesT_TemporaryKey.pfx" />
    <Page Include="CharmStyle3.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Page>
    <Page Include="CharmStyle2.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Page>
    <Page Include="CharmStyle1.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Page>
    <Content Include="Assets\en-US\redactor.png" />
    <Content Include="Assets\en-US\SplashScreen.png" />
    <Content Include="Assets\Logo.scale-100.png" />
    <Content Include="Assets\Logo.scale-140.png" />
    <Content Include="Assets\Logo.scale-180.png" />
    <Content Include="Assets\Logo.scale-80.png" />
    <Content Include="Assets\LogoImage.png" />
    <Content Include="Assets\LogoOpenFile.png" />
    <Content Include="Assets\redactor.png" />
    <Content Include="Assets\ru-RU\redactor.png" />
    <Content Include="Assets\ru-RU\SplashScreen.png" />
    <Content Include="Assets\SmallLogo.scale-140.png" />
    <Content Include="Assets\SmallLogo.scale-180.png" />
    <Content Include="Assets\SmallLogo.scale-80.png" />
    <Content Include="Assets\StoreLogo.scale-100.png" />
    <Content Include="Assets\StoreLogo.scale-140.png" />
    <Content Include="Assets\StoreLogo.scale-180.png" />
    <Content Include="Assets\WideLogo.scale-100.png" />
    <Content Include="Assets\WideLogo.scale-140.png" />
    <Content Include="Assets\WideLogo.scale-180.png" />
    <Content Include="Assets\WideLogo.scale-80.png" />
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <Content Include="ENdemo\image1.jpg">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="imgfolder\press1.png" />
    <Content Include="imgfolder\press2.png" />
    <Content Include="imgfolder\press3.png" />
    <Page Include="Common\StandardStyles.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Page>
    <Page Include="FilesPage.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Content Include="imgfolder\right1.png" />
    <None Include="Package.StoreAssociation.xml">
      <SubType>Designer</SubType>
    </None>
    <Content Include="updownleftright.jpg" />
    <Content Include="imgfolder\wrong1.png" />
    <Content Include="demo\????\1.jpg" />
    <Content Include="demo\????\2.jpg" />
    <PRIResource Include="en-US\Resources.resw" />
    <Content Include="ENdemo\demo.tstf">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <PRIResource Include="ru-RU\Resources.resw" />
    <None Include="Common\ReadMe.txt" />
    <Page Include="Flyout.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Page Include="FlyoutAbout.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
  </ItemGroup>.
D:\Porting to Windows 10\TesT10\TesT\Upgrade_to_uwp.ps1:53 знак:3
+   $FileContents = $FileContents -replace "$AppManifestIdentityGroup", ...
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (<ItemGroup>
  ...
  </ItemGroup>:String) [], RuntimeException
    + FullyQualifiedErrorId : InvalidRegularExpression
 
Finished updating csproj file
Converting Package.appxmanifest...
True
Finished updating package.appxmanifest
Creating project.json...
Done 



Запустил PowerShell и вернул значение
Set-ExecutionPolicy Unrestricted
на N.

Как видно из отчета о конвертации, русский текст не был распознан и был заменен на вопросительные знаки.

<Content Include="demo\????\2.jpg" />


То есть мне пришлось немного править файл .csproj вручную. Вы же можете чуть-чуть подправить файл Upgrade_to_uwp.ps1 для того чтобы он проводил конвертацию файла .csproj в формате UFT-8. Для этого в строках 21 и 57:
$FileContents | Out-File $FilePath -Encoding ascii -Force
заменяем на
$FileContents | Out-File $FilePath -Encoding utf8 -Force

Кроме того в манифесте остались еще какие-то ошибки, которые также необходимо было исправить.
Что я сделал:

Добавил в манифест Square44x44Logo
<uap:VisualElements Square44x44Logo=«StoreLogo44.png»
Удалил из той же строки:
ToastCapable=«true» и Square30x30Logo=«Assets\SmallLogo.png»

Так как для телефона мое приложение еще не было опубликовано, то строку:
<mp:PhoneIdentity PhoneProductId=«44823AlexejSommer.TesTRedactoR» PhonePublisherId=«00000000-0000-0000-0000-000000000000»/>
заменил на:
<mp:PhoneIdentity PhoneProductId=«00000000-0000-0000-0000-000000000000» PhonePublisherId=«00000000-0000-0000-0000-000000000000»/>

Удалил из манифеста:

<Extensions>
      <Extension Category="windows.fileOpenPicker">
          <FileOpenPicker>
            <SupportedFileTypes>
              <FileType>.tstf</FileType>
            </SupportedFileTypes>
          </FileOpenPicker>
</Extension>
        <Extension Category="windows.fileTypeAssociation">
          <FileTypeAssociation Name="testredactor">
            <DisplayName>ms-resource:takeatest</DisplayName>
            <Logo>Assets\LogoOpenFile.png</Logo>
            <SupportedFileTypes>
              <FileType>.tstf</FileType>
            </SupportedFileTypes>
          </FileTypeAssociation>
        </Extension>
        <Extension Category="windows.contactPicker" />
</Extensions>


Вместо этого кода можно было добавить вручную:

<Extensions>
        <uap:Extension Category="windows.fileOpenPicker">
          <uap:FileOpenPicker>
            <uap:SupportedFileTypes>
              <uap:FileType>.tstf</uap:FileType>
            </uap:SupportedFileTypes>
          </uap:FileOpenPicker>
        </uap:Extension>
        <uap:Extension Category="windows.fileTypeAssociation">
          <uap:FileTypeAssociation Name="test">
            <uap:SupportedFileTypes>
              <uap:FileType>.tstf</uap:FileType>
            </uap:SupportedFileTypes>
          </uap:FileTypeAssociation>
        </uap:Extension>
      </Extensions>


Но я воспользовался графическим редактором манифеста и добавил «объявления» в нем. Пользоваться графическим редактором манифеста стало возможным после того как я удалил из манифеста весь ошибочный код.

Попробовал собрать и получил больше тысячи ошибок. В том числе ошибку:
Couldn't find the required information in the lock file. Make sure you have UAP,Version=v10.0.10240/win10-anycpu mentioned in your targets.
Так да, ведь тип проекта «Any CPU» на платформе UWP не поддерживается. Типичный ляп. Изменил целевую платформу проекта на x86 и количество ошибок стало приемлемым — всего пара десятков.

В частности были ошибки типа:
Error CS0104 'XmlDocument' is an ambiguous reference between 'Windows.Data.Xml.Dom.XmlDocument' and 'System.Xml.XmlDocument'
Но я довольно быстро исправил все упоминания XmlDocument на Windows.Data.Xml.Dom.XmlDocument.

Кроме того следующий код оказался устаревшим:

SettingsPane.GetForCurrentView().CommandsRequested += OnCommandsRequested;

// ........

        void OnCommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)
        {
           try
            {
 SettingsCommand sCommand = new SettingsCommand("SettingsPage", "Settings", new UICommandInvokedHandler(onSettingsCommand));
 args.Request.ApplicationCommands.Add(sCommand);

 SettingsCommand shareQCommand = new SettingsCommand("ShareQuestion", "Share", new UICommandInvokedHandler(onShareQCommand));
 args.Request.ApplicationCommands.Add(shareQCommand);
            }
            catch { }
        }

        private void onSettingsCommand(IUICommand command)
        {
  // .....  код открывающий FlyOut с настройками
        }
        private void onShareQCommand(IUICommand cmd)
        {
            DataTransferManager.ShowShareUI();
        }


Удалив его я получил рабочий проект. Данный функционал придется позднее вернуть иным способом.

После всего этого, соответственно рекомендациям, я добавил в MainPage после this.InitializeComponent(); следующий код, задающий минимальный размер окна, а также предпочитаемый размер приложения при запуске:

public MainPage()
{
this.InitializeComponent();
 
ApplicationView.GetForCurrentView().SetPreferredMinSize(new Size { Width = 500, Height = 400 });
ApplicationView.PreferredLaunchViewSize = new Size { Height = 800, Width = 600 };
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;
}


Оставшаяся часть работы это замена всплывающих элементов FlyOut (на элементы ContentDialog, PopUp или странички с навигацией), работа с кодом который стал deprecated, адаптация кода под различные девайсы с помощью #if и создание гибкого/отзывчивого/заточенного UI, годного под устройства с различными размерами экрана.

Видео с последней конференции //build/
Moving to the Universal Windows Platform: Porting an App from Windows 8.1 XAML or Windows Phone Silverlight to Windows 10
Видео из курса MVA
A Developer's Guide to Windows 10: (21) Porting 8.1 Apps to Windows 10

© Habrahabr.ru