Расширение классического приложения с помощью современных компонентов UWP

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

Во многих случаях можно вызывать API среды выполнения Windows непосредственно из классического приложения, поэтому перед прочтением этого руководства просмотрите статью Улучшение для Windows.

Примечание.

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

Сначала следует настроить решение

Добавьте в решение один или несколько проектов UWP и компонентов среды выполнения.

Начните с решения, которое содержит проект упаковки приложений Windows со ссылкой на классическое приложение.

На этом рисунке показан пример решения.

Extend start project

Если ваше решение не содержит проект упаковки, см. руководство по упаковке классического приложения с помощью Visual Studio.

Настройка классического приложения

Убедитесь, что в классическом приложении указаны ссылки на все файлы, необходимые для вызова API среды выполнения Windows.

Соответствующую процедуру см. в статье Вызов API для среды выполнения Windows в классических приложениях.

Добавление проекта UWP

Добавьте в решение проект Пустое приложение (универсальная платформа Windows).

Там вы сможете создать современный пользовательский интерфейс XAML или использовать API, которые выполняются только в процессе UWP.

Add new project

В проекте упаковки щелкните правой кнопкой узел Приложения и выберите команду Добавить ссылку.

Add reference

Затем добавьте ссылку на проект UWP.

Select UWP project

Решение будет выглядеть следующим образом:

Solution with UWP project

(Необязательно) Создайте компонент среды выполнения Windows

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

runtime component app service

Затем в проекте UWP добавьте ссылку на компонент среды выполнения. Решение будет выглядеть следующим образом:

Runtime Component Reference

Сборка решения

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

Config manager

Рассмотрим некоторые действия, которые можно выполнять с проектами UWP и компонентами среды выполнения.

Отображение современного пользовательского интерфейса XAML

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

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

На рисунке показано приложение Windows Forms, которое открывает современный пользовательский интерфейс на основе XAML, содержащий элементы управления картой.

adaptive-design

Примечание.

В этом примере показан пользовательский интерфейс XAML, полученный добавлением проекта UWP в решение. Это стабильный поддерживаемый подход к отображению пользовательских интерфейсов XAML в классическом приложении. Альтернативный вариант — добавление элементов управления XAML UWP непосредственно в классическое приложение с помощью XAML Islands. XAML Islands сейчас предоставляется в предварительной версии для разработчиков. Несмотря на то что вы можете опробовать их в своем собственном прототипном коде сейчас, мы не рекомендуем использовать их в производственном коде в настоящее время. В будущих выпусках Windows эти API и элементы управления будут дорабатываться и совершенствоваться. См. сведения о XAML Islands в руководстве по элементам управления UWP в классических приложениях.

Шаблон проектирования

Для отображения пользовательского интерфейса на основе XAML выполните следующие действия:

1️⃣ Настройка решения

2️⃣ Создание пользовательского интерфейса XAML

3️⃣ Добавление расширения протокола в проект UWP

4️⃣ Запуск приложения UWP из классического приложения

5️⃣ Отображение нужной страницы в проекте UWP

Настройка решения

Общие рекомендации по настройке решения см . в разделе "Первое " в начале этого руководства.

Сейчас ваше решение выглядит примерно так:

XAML UI Solution

В этом примере проект Windows Forms называется Landmarks, а проект UWP, который содержит пользовательский интерфейс XAML, называется MapUI.

Создание пользовательского интерфейса XAML

Добавьте пользовательский интерфейс XAML в проект UWP Вот код XAML для простой карты.

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="12,20,12,14">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <maps:MapControl x:Name="myMap" Grid.Column="0" Width="500" Height="500"
                     ZoomLevel="{Binding ElementName=zoomSlider,Path=Value, Mode=TwoWay}"
                     Heading="{Binding ElementName=headingSlider,Path=Value, Mode=TwoWay}"
                     DesiredPitch="{Binding ElementName=desiredPitchSlider,Path=Value, Mode=TwoWay}"
                     HorizontalAlignment="Left"
                     MapServiceToken="<Your Key Goes Here" />
    <Grid Grid.Column="1" Margin="12">
        <StackPanel>
            <Slider Minimum="1" Maximum="20" Header="ZoomLevel" Name="zoomSlider" Value="17.5"/>
            <Slider Minimum="0" Maximum="360" Header="Heading" Name="headingSlider" Value="0"/>
            <Slider Minimum="0" Maximum="64" Header=" DesiredPitch" Name="desiredPitchSlider" Value="32"/>
        </StackPanel>
    </Grid>
</Grid>

Добавление расширения протокола

В обозревателе решений откройте файл package.appxmanifest проекта упаковки из нужного решения и добавьте это расширение.

<Extensions>
  <uap:Extension Category="windows.protocol" Executable="MapUI.exe" EntryPoint="MapUI.App">
    <uap:Protocol Name="xamluidemo" />
  </uap:Extension>
</Extensions>

Задайте имя протокола, введите имя исполняемого файла, созданного проектом UWP, и имя класса для точки входа.

Также можно открыть package.appxmanifest в конструкторе, выбрать вкладку Объявления и затем добавить сюда расширение.

declarations-tab

Примечание.

Элементы управления картой скачивают данные из Интернета, следовательно, если вы используете такой элемент, необходимо также добавить в манифест возможность internet client.

Запуск приложения UWP

Во-первых, из классического приложения создайте URI, включающий имя протокола и все параметры, которые необходимо передать в приложение UWP. Затем вызовите метод LaunchUriAsync.


private void Statue_Of_Liberty_Click(object sender, EventArgs e)
{
    ShowMap(40.689247, -74.044502);
}

private async void ShowMap(double lat, double lon)
{
    string str = "xamluidemo://";

    Uri uri = new Uri(str + "location?lat=" +
        lat.ToString() + "&?lon=" + lon.ToString());

    var success = await Windows.System.Launcher.LaunchUriAsync(uri);

}

Анализ параметров и отображение страницы

В классе App проекта UWP переопределите обработчик событий OnActivated. Если приложение активируется вашим протоколом, выполните анализ параметров и откройте требуемую страницу.

protected override void OnActivated(Windows.ApplicationModel.Activation.IActivatedEventArgs e)
{
    if (e.Kind == ActivationKind.Protocol)
    {
        ProtocolActivatedEventArgs protocolArgs = (ProtocolActivatedEventArgs)e;
        Uri uri = protocolArgs.Uri;
        if (uri.Scheme == "xamluidemo")
        {
            Frame rootFrame = new Frame();
            Window.Current.Content = rootFrame;
            rootFrame.Navigate(typeof(MainPage), uri.Query);
            Window.Current.Activate();
        }
    }
}

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

protected override void OnNavigatedTo(NavigationEventArgs e)
 {
     if (e.Parameter != null)
     {
         WwwFormUrlDecoder decoder = new WwwFormUrlDecoder(e.Parameter.ToString());

         double lat = Convert.ToDouble(decoder[0].Value);
         double lon = Convert.ToDouble(decoder[1].Value);

         BasicGeoposition pos = new BasicGeoposition();

         pos.Latitude = lat;
         pos.Longitude = lon;

         myMap.Center = new Geopoint(pos);

         myMap.Style = MapStyle.Aerial3D;

     }

     base.OnNavigatedTo(e);
 }

Присвоение классическому приложению роли получателя данных

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

Пользователи смогут выбрать ваше приложение, чтобы передать изображения из Microsoft Edge или приложения "Фотографии". Вот пример приложения WPF, которое поддерживает такую возможность.

share target.

Полный пример см. здесь.

Шаблон проектирования

Чтобы сделать ваше приложение получателем данных, выполните следующие действия:

1️⃣ Добавление целевого расширения общего ресурса

2️⃣ Переопределите обработчик событий OnShareTargetActivated

3️⃣ Добавление расширения классического приложения в проект UWP

4️⃣ Добавление расширения процесса полного доверия

5️⃣ Изменение классического приложения для получения общего файла

Дальнейшие шаги

Добавление расширения получателя данных

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

<Extensions>
      <uap:Extension
          Category="windows.shareTarget"
          Executable="ShareTarget.exe"
          EntryPoint="App">
        <uap:ShareTarget>
          <uap:SupportedFileTypes>
            <uap:SupportsAnyFileType />
          </uap:SupportedFileTypes>
          <uap:DataFormat>Bitmap</uap:DataFormat>
        </uap:ShareTarget>
      </uap:Extension>
</Extensions>  

Укажите имя исполняемого файла, созданного проектом UWP, и имя класса точки входа. В этой разметке предполагается, что исполняемый файл для приложения UWP называется ShareTarget.exe.

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

Переопределение обработчика событий OnShareTargetActivated

Переопределите обработчик событий OnShareTargetActivated в классе App проекта UWP.

Этот обработчик событий вызывается, когда пользователи выбирают приложение в качестве получателя файлов.


protected override void OnShareTargetActivated(ShareTargetActivatedEventArgs args)
{
    shareWithDesktopApplication(args.ShareOperation);
}

private async void shareWithDesktopApplication(ShareOperation shareOperation)
{
    if (shareOperation.Data.Contains(StandardDataFormats.StorageItems))
    {
        var items = await shareOperation.Data.GetStorageItemsAsync();
        StorageFile file = items[0] as StorageFile;
        IRandomAccessStreamWithContentType stream = await file.OpenReadAsync();

        await file.CopyAsync(ApplicationData.Current.LocalFolder);
            shareOperation.ReportCompleted();

        await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
    }
}

В этом коде мы сохраняем переданное пользователем изображение в папку локального хранилища приложений. Позже мы изменим классическое приложение, чтобы оно извлекало изображения из этой папки. Классическое приложение может это сделать, так как включено в тот же пакет, что и приложение UWP.

Добавление расширения классического приложения в проект UWP

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

desktop extension

Добавление расширения процесса полного доверия

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

<Extensions>
  ...
      <desktop:Extension Category="windows.fullTrustProcess" Executable="PhotoStoreDemo\PhotoStoreDemo.exe" />
  ...
</Extensions>  

Это расширение позволит приложению UWP запускать классическое приложение, которому передается файл. В нашем примере это исполняемый файл классического приложения WPF PhotoStoreDemo.

Изменение классического приложения для получения переданного файла

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

Photos.Path = Windows.Storage.ApplicationData.Current.LocalFolder.Path;

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

...

   FileSystemWatcher watcher = new FileSystemWatcher(Photos.Path);

...

private void Watcher_Created(object sender, FileSystemEventArgs e)
{
    // new file got created, adding it to the list
    Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() =>
    {
        if (File.Exists(e.FullPath))
        {
            ImageFile item = new ImageFile(e.FullPath);
            Photos.Insert(0, item);
            PhotoListBox.SelectedIndex = 0;
            CurrentPhoto.Source = (BitmapSource)item.Image;
        }
    }));
}

Создание фоновой задачи

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

Вот пример приложения WPF, которое регистрирует фоновую задачу.

background task

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

Полный пример см. здесь.

Шаблон проектирования

Чтобы создать фоновую службу, выполните следующие действия:

1️⃣ Реализация фоновой задачи

2️⃣ Настройка фоновой задачи

3️⃣ Регистрация фоновой задачи

Реализация фоновой задачи

Реализуйте фоновую задачу, добавив код в проект компонентов среды выполнения Windows.

public sealed class SiteVerifier : IBackgroundTask
{
    public async void Run(IBackgroundTaskInstance taskInstance)
    {

        taskInstance.Canceled += TaskInstance_Canceled;
        BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
        var msg = await MeasureRequestTime();
        ShowToast(msg);
        deferral.Complete();
    }

    private async Task<string> MeasureRequestTime()
    {
        string msg;
        try
        {
            var url = ApplicationData.Current.LocalSettings.Values["UrlToVerify"] as string;
            var http = new HttpClient();
            Stopwatch clock = Stopwatch.StartNew();
            var response = await http.GetAsync(new Uri(url));
            response.EnsureSuccessStatusCode();
            var elapsed = clock.ElapsedMilliseconds;
            clock.Stop();
            msg = $"{url} took {elapsed.ToString()} ms";
        }
        catch (Exception ex)
        {
            msg = ex.Message;
        }
        return msg;
    }

Настройка фоновой задачи

В конструкторе манифестов откройте файл package.appxmanifest проекта упаковки в своем решении.

На вкладке Объявления добавьте объявление Фоновые задачи.

Background task option

Затем выберите требуемые свойства. В нашем примере используется свойство Timer.

Timer property

Укажите полное имя класса в компоненте среды выполнения Windows, который реализует фоновую задачу.

Specify entry point

Регистрация фоновой задачи

Добавьте код в проект классического приложения, регистрирующий фоновую задачу.

public void RegisterBackgroundTask(String triggerName)
{
    var current = BackgroundTaskRegistration.AllTasks
        .Where(b => b.Value.Name == triggerName).FirstOrDefault().Value;

    if (current is null)
    {
        BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
        builder.Name = triggerName;
        builder.SetTrigger(new MaintenanceTrigger(15, false));
        builder.TaskEntryPoint = "HttpPing.SiteVerifier";
        builder.Register();
        System.Diagnostics.Debug.WriteLine("BGTask registered:" + triggerName);
    }
    else
    {
        System.Diagnostics.Debug.WriteLine("Task already:" + triggerName);
    }
}

Получение ответов на вопросы

Есть вопросы? Задайте их на Stack Overflow. Наша команда следит за этими тегами. Вы также можете задать нам вопросы здесь.