Добавление функционала UWP к существующему программному обеспечению настольных компьютеров

С выпуском прошлым летом юбилейного обновления Windows 10 Anniversary Update и объявления о поддержке в Windows Store приложений, построенных с технологией Desktop Bridge, мы хотим поделиться более подробной информацией и примерами кода по вопросам дальнейшего усиления и расширения функционала вашего программного обеспечения для ПК на ОС Windows 10. В дополнение к распространению через Windows Store и современные технологии развертывания, Desktop Bridge позволяет использовать впечатляющий функционал и возможности универсальной платформы Windows (UWP), которые ранее были недоступны для существующего программного обеспечения для ПК. В этой публикации мы поделимся четырьмя примерами добавления такого функционала, с полным исходным кодом, доступным на GitHub. Соответствующие приложения также доступны в Windows Store, так что вы можете начать изучать их на компьютерах с Windows 10 без среды разработки. Мы выбрали различные технологии настольных приложений для каждого из примеров, чтобы дополнительно подчеркнуть, что Desktop Bridge применима ко всем вариантам программного обеспечения для ПК. Примеры, осуществляющие вызовы Windows 10 UWP API, используют методы, о которых мы рассказывали в другой публикации.

Добавление пользовательского опыта UWP XAML

В нашем первом примере, мы расширяем приложение на Visual Basic 6, чтобы использовать элемент управления UWP XAML Map control для отображения информации о местоположении в приложении с базой данных. Пример также использует UWP API для всплывающих тост-уведомлений.
Ссылки на исходный код и загрузку приложения:

Ссылка на приложение в Windows Store здесь. А ссылка на исходный код в GitHub здесь.

AppxManifest.xml
Чтобы понять, как UWP и инструмент Desktop Bridge позволяют осуществить этот сценарий, давайте посмотрим на соответствующие строки в файле AppxManifest.xml для этого решения. Точкой входа здесь является приложение на VB6, которое мы конвертировали с помощью Desktop Bridge. Теперь мы можем добавить UWP компонент (реализованный в MapUI.exe) в пакет, который обеспечивает пользовательский интерфейс XAML. Для облегчения активации современного компонента из существующего приложения, мы определяем расширение протокола в манифесте. Кроме того, обратите внимание, что мы должны объявить две возможности: (а) ‘runFullTrust’, чтобы сохранить выполнение кода VB6 на том же уровне доверия как до преобразования с помощью Desktop Bridge и (б) ‘internetClient’ для элемента управления Map control, чтобы загрузить данные карты из Интернета.

  1. <Applications>
  2.   <Application
  3.        Id=«VB6App» Executable=«VB6App.exe»
  4.        EntryPoint=«Windows.FullTrustApplication»>
  5.     <uap:VisualElements/>
  6.     <Extensions>
  7.       <uap:Extension
  8.           Category=«windows.protocol»
  9.           Executable=«MapUI.exe»
  10.           EntryPoint=» MapUI.App»>
  11.         <uap:Protocol Name=«desktopbridgemapsample» />
  12.       </uap:Extension>
  13.     </Extensions>
  14.   </Application>
  15. </Applications>
  16. <Capabilities>
  17.   <rescap:Capability Name=«runFullTrust» />
  18.   <Capability Name=«internetClient» />
  19. </Capabilities>

Фрагменты кода
Для протокольной активации UWP вида из приложения на VB6, мы вызываем UWP API LaunchUriAsync, обернув его функцией вызываемой из VB6 (‘LaunchMap’) и передаём широту и долготу в качестве параметров:

  1. Private Declare Function LaunchMap Lib «UWPWrappers.dll» _
  2.   (ByVal lat As Double, ByVal lon As Double) As Boolean
  3. Private Sub EiffelTower_Click()
  4.     LaunchMap 48.858222, 2.2945
  5. End Sub

И это исходный C ++ код для LaunchMap() API в UWPWrappers.dll, который является обёрткой вызовов в актуальное LaunchUriAsync UWP API:

  1. DllExport bool __stdcall LaunchMap(double lat, double lon)
  2. {
  3.   try
  4.   {
  5.     //
  6.     // format the URI to match the defined protocol:
  7.     // desktopbridgemapsample://location?lat={latitude}&?lon={longitude}
  8.     //
  9.     String ^str = ref new String(L«desktopbridgemapsample://»);
  10.     Uri ^uri = ref new Uri(
  11.       str + L«location?lat=» + lat.ToString() + L«&?lon=» + lon.ToString());
  12.     // now launch the UWP component
  13.     Launcher::LaunchUriAsync(uri);
  14.   }
  15.   catch (Exception^ ex) { return false; }
  16.   return true;
  17. }

Теперь это вызывает событие OnActivated в компоненте UWP. Компонент написан на C++, но может быть реализован на любом языке программирования, который поддерживает UWP. Здесь мы проверяем, активировались ли мы для правильного протокола. Если да, то мы выполним фактическую активацию и загрузим страницу карты с выставленным значением, которое включает информацию о широте и долготе.

  1. void App::OnActivated(Windows::ApplicationModel::Activation::IActivatedEventArgs^ e)
  2. {
  3.   if (e>Kind == ActivationKind::Protocol)
  4.   {
  5.     ProtocolActivatedEventArgs^ protocolArgs = (ProtocolActivatedEventArgs^)e;
  6.     Uri ^uri = protocolArgs>Uri;
  7.     if (uri>SchemeName == «desktopbridgemapsample»)
  8.     {
  9.       Frame ^rootFrame = ref new Frame();
  10.       Window::Current>Content = rootFrame;
  11.       rootFrame>Navigate(TypeName(MainPage::typeid), uri>Query);
  12.       Window::Current>Activate();
  13.     }
  14.   }
  15. }

Дополнительные замечания
В UWPWrappers.dll вы также найдете другую функцию-обёртку UWP API (CreateToast), которая используется для запуска тост-уведомлений из приложения VB6.
Обратите внимание, что во многих случаях, вы будете нуждаться в основном приложении и добавленном UWP компоненте для связи друг с другом в обоих направлениях. Этот пример не демонстрирует рабочий процесс связи, но вы можете посмотреть пример на GitHub, который показывает, как AppServiceConnection может использоваться для двусторонней связи между двумя компонентами в рамках пакета приложений.

Демонстрация UWP App Service

Во втором примере, мы расширяемм приложение данных WinForms, которое предоставляет собой App Service для обеспечения других приложений контролируемым доступом к своей базе данных, даже если приложение WinForms не выполняется.
Ссылки на исходный код и загрузку приложения:

Ссылка на приложение в Windows Store здесь. А ссылка на исходный код в GitHub здесь.

AppxManifest.xml
Точкой входа здесь является приложение Windows Forms, которое мы упаковали с использованием Desktop Bridge. Теперь мы можем добавить UWP компонент (реализованный в Windows Runtime компонент под названием ‘MyAppService.dll’) к пакету, который обеспечивает реализацию App Service. Этот компонент активируется вне основного процесса приложения, поэтому мы также должны объявить расширение уровня пакета, чтобы объяснить, как система будет активировать класс.

  1. <Applications>
  2.   <Application
  3.       Id=«WinformWithAppService»
  4.       Executable=«WinformWithAppService.exe»
  5.       EntryPoint=«Windows.FullTrustApplication»>
  6.     <uap:VisualElements>
  7.     <Extensions>
  8.       <uap:Extension
  9.           Category=«windows.appService»
  10.           EntryPoint=«MyAppService.AppServiceTask»>
  11.         <uap:AppService Name=«com.microsoft.samples.winforms» />
  12.       </uap:Extension>
  13.     </Extensions>
  14.   </Application>
  15. <Applications>
  16. <Capabilities>
  17.   <rescap:Capability Name=«runFullTrust» />
  18. </Capabilities>
  19. <Extensions>
  20.   <Extension
  21.       Category=«windows.activatableClass.inProcessServer»>
  22.     <InProcessServer>
  23.       <Path>MyAppService.dll</Path>
  24.       <ActivatableClass
  25.           ActivatableClassId=«MyAppService.AppServiceTask»
  26.           ThreadingModel=«both» />
  27.     </InProcessServer>
  28.   </Extension>
  29. </Extensions>

Фрагмент кода
В реализации App Service, мы хотим проверять и обрабатывать запросы от других приложений, которые запрашивают доступ к нашей базе данных:

  1. public sealed class AppServiceTask : IBackgroundTask
  2. {
  3.     private BackgroundTaskDeferral backgroundTaskDeferral;
  4.     public void Run(IBackgroundTaskInstance taskInstance)
  5.     {
  6.         this.backgroundTaskDeferral = taskInstance.GetDeferral();
  7.         taskInstance.Canceled += OnTaskCanceled;
  8.         var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
  9.         details.AppServiceConnection.RequestReceived += OnRequestReceived;
  10.     }
  11.     private async void OnRequestReceived(AppServiceConnection sender,
  12.                                          AppServiceRequestReceivedEventArgs args)
  13.     {
  14.         var messageDeferral = args.GetDeferral();
  15.         ValueSet message = args.Request.Message;
  16.         string id = message[«ID»] as string;
  17.         ValueSet returnData = DataBase.GetData(id);
  18.         await args.Request.SendResponseAsync(returnData);
  19.         messageDeferral.Complete();
  20.     }
  21.     private void OnTaskCanceled(IBackgroundTaskInstance sender,
  22.                                 BackgroundTaskCancellationReason reason)
  23.     {
  24.         if (this.backgroundTaskDeferral != null)
  25.         {
  26.             this.backgroundTaskDeferral.Complete();
  27.         }
  28.     }
  29. }

А вот пример кода для клиентского приложения, которое использует App Service:

  1. private async void button_Click(object sender, RoutedEventArgs e)
  2. {
  3.     AppServiceConnection dataService = new AppServiceConnection();
  4.     dataService.AppServiceName = «com.microsoft.samples.winforms»;
  5.     dataService.PackageFamilyName = «Microsoft.SDKSamples.WinformWithAppService»;
  6.     var status = await dataService.OpenAsync();
  7.     if (status == AppServiceConnectionStatus.Success)
  8.     {
  9.         string id = int.Parse(textBox.Text);
  10.         var message = new ValueSet();
  11.         message.Add(«ID», id);
  12.         AppServiceResponse response = await dataService.SendMessageAsync(message);
  13.         string result = «»;
  14.         if (response.Status == AppServiceResponseStatus.Success)
  15.         {
  16.             if (response.Message[«Status»] as string == «OK»)
  17.             {
  18.                 DisplayResult(response.Message[«Result»]);
  19.             }
  20.         }
  21.     }
  22. }

Как сделать в программном обеспечении для настольных компьютеров возможность делиться объектами

В этом примере, мы расширяем возможности приложения для просмотра изображений реализованного на WPF, чтобы получить возможность делиться объектами. Таким образом пользователи смогут легко обмениваться фотографиями с Microsoft Edge, приложением Фотографии и другими приложениями из нашего приложения. Совместное использование фотографий является лишь одним из многих сценариев, которые могут быть решены путем добавления в ваше приложение возможностью поделиться объектами. Также поддерживается несколько других форматов данных. Смотрите страницу документации в Windows Dev Center для получения полного списка и получения более подробной информации.
Ссылки на исходный код и загрузку приложения:

Ссылка на приложение в Windows Store здесь. А ссылка на исходный код в GitHub здесь.

AppxManifest.xml

Структура нашего манифеста выглядит очень похожей на пример XAML, который вы видели ранее в этой публикации. На самом деле, наше совместное использование пользовательского интерфейса также опирается на XAML. Единственное отличие в том, что мы объявляем расширение ‘windows.shareTarget’ вместо активации протокола, чтобы система знать, что мы хотим активировать, если пользователь делится данными указанного типа и выбирает наше приложение в качестве цели обмена.

  1. <Applications>
  2.   <Application
  3.       Id=«PhotoStore»
  4.       Executable=«Win32\PhotoStore.exe»
  5.       EntryPoint=«Windows.FullTrustApplication»>
  6.     <uap:VisualElements/>
  7.     <Extensions>
  8.       <uap:Extension
  9.           Category=«windows.shareTarget»
  10.           Executable=«ShareTarget.exe»
  11.           EntryPoint=«ShareTarget.App»>
  12.         <uap:ShareTarget>
  13.           <uap:SupportedFileTypes>
  14.             <uap:SupportsAnyFileType />
  15.           </uap:SupportedFileTypes>
  16.           <uap:DataFormat>Bitmap</uap:DataFormat>
  17.         </uap:ShareTarget>
  18.       </uap:Extension>
  19.     </Extensions>
  20.   </Application>
  21. </Applications>
  22. <Capabilities>
  23.   <rescap:Capability Name=«runFullTrust» />
  24. </Capabilities>

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

  1. protected override async void OnNavigatedTo(NavigationEventArgs e)
  2. {
  3.   this.shareOperation = (ShareOperation)e.Parameter;
  4.   if (this.shareOperation.Data.Contains(StandardDataFormats.StorageItems))
  5.   {
  6.       this.sharedStorageItems =
  7.         await this.shareOperation.Data.GetStorageItemsAsync();
  8.       foreach (StorageFile item in this.sharedStorageItems)
  9.       {
  10.           ProcessSharedFile(item);
  11.       }
  12.   }
  13. }

Добавление фоновой задачи UWP

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

Ссылка на приложение в Windows Store здесь. А ссылка на исходный код в GitHub здесь.

AppxManifest.xml
Структура манифеста должна уже выглядеть в основном знакомой вам. В дополнение к основному приложению MFC, мы объявляем фоновый тип задачи «pushNotification». Нужно отметить одну специальная вещь здесь, так как фоновая задача реализуется с использованием управляемого кода, нам необходимо объявить ‘CLRHost.dll’ как inProcessServer, так как мы будем нуждаться в этом платформенном компоненте для обработки активации здесь.

  1. <Applications>
  2.   <Application
  3.       Id=«MFCSampleIDE» Executable=«MFCSampleIDE.exe»
  4.       EntryPoint=«Windows.FullTrustApplication»>
  5.     <uap:VisualElements>
  6.       <Extensions>
  7.         <Extension
  8.             Category=«windows.backgroundTasks»
  9.             EntryPoint=«BackgroundTasks.PushNotificationTask»>
  10.           <BackgroundTasks>
  11.             <Task Type=«pushNotification» />
  12.           </BackgroundTasks>
  13.         </Extension>
  14.       </Extensions>
  15.   </Application>
  16. </Applications>
  17. <Capabilities>
  18.   <Capability Name=«internetClient» />
  19.   <rescap:Capability Name=«runFullTrust» />
  20. </Capabilities>
  21. <Extensions>
  22.   <Extension
  23.       Category=«windows.activatableClass.inProcessServer»>
  24.     <InProcessServer>
  25.       <Path>PushNotificationBackgroundTask.dll</Path>
  26.       <ActivatableClass
  27.           ActivatableClassId=«PushNotificationBackgroundTask.MyTask»
  28.           ThreadingModel=«both» />
  29.     </InProcessServer>
  30.   </Extension>
  31. </Extensions>

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

  1. // Hook up push notification foreground event
  2. task<PushNotificationChannel^>(
  3.   PushNotificationChannelManager::CreatePushNotificationChannelForApplicationAsync())
  4.   .then([](PushNotificationChannel ^channel)
  5. {
  6.     channel>PushNotificationReceived +=
  7.            ref new TypedEventHandler<PushNotificationChannel^,
  8.                                      PushNotificationReceivedEventArgs ^>
  9.                                      (&OnPushNotificationReceived);
  10. });
  11. // Register push notification background trigger
  12. BackgroundTaskBuilder ^builder = ref new BackgroundTaskBuilder();
  13. builder>Name = «pushTask»;
  14. builder>TaskEntryPoint = «PushNotificationBackgroundTask.MyTask»;
  15. builder>SetTrigger(ref new PushNotificationTrigger());
  16. builder>Register();

Дополнительное замечание
Обратите внимание, как этот последний пример сохраняет унаследованный старый код отдельно от нового для Windows 10, который полностью реализуется в UWPFeatures.dll. Эта DLL загружается динамически при запуске в Windows 10, чтобы задействовать эти функции. В более ранних версиях операционной системы, таких как Windows 7, то же самое EXE-приложение по-прежнему может быть развернуто как раньше, так как все усовершенствования аккуратно отделены от существующего кода.

Подводя итоги

С UWP и Windows 10, приложения могут воспользоваться несколькими интересными новыми функциями для улучшения их взаимодействия с пользователями. С помощью платформы и инструмента улучшения Desktop Bridge существующее программное обеспечение для настольных компьютеров теперь может быть частью UWP экосистемы и воспользоваться тем же набором новых функций и возможностей платформы операционной системы.

Для получения дополнительной информации о Desktop Bridge, пожалуйста, посетите Dev Center Windows.

Перевод оригинальной статьи Adding UWP features to your existing PC software
Автор: Stefan Wick
Перевод: Сергей Урусов

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *