Extend your desktop app with modern UWP components

Some Windows experiences (For example, a touch-enabled UI page) must run inside of an AppContainer. If you want to add such experiences, then extend your desktop app with UWP projects and Windows Runtime components.

In many cases you can call Windows Runtime APIs directly from your desktop application, so before you review this guide, see Enhance for Windows.

Note

The features described in this topic require that your app is packaged (has package identity at runtime). That includes packaged apps (see Create a new project for a packaged WinUI 3 desktop app) and packaged apps with external location (see Grant package identity by packaging with external location). Also see Features that require package identity.

First, set up your solution

Add one or more UWP projects and runtime components to your solution.

Start with a solution that contains a Windows Application Packaging Project with a reference to your desktop application.

This image shows an example solution.

Extend start project

If your solution doesn't contain a packaging project, see Package your desktop application by using Visual Studio.

Configure the desktop application

Make sure that your desktop application has references to the files that you need to call Windows Runtime APIs.

To do this, see Call Windows Runtime APIs in desktop apps.

Add a UWP project

Add a Blank App (Universal Windows) to your solution.

This is where you'll build a modern XAML UI or use APIs that run only within a UWP process.

Add new project

In your packaging project, right-click the Applications node, and then click Add Reference.

Add reference

Then, add a reference the UWP project.

Select UWP project

Your solution will look something like this:

Solution with UWP project

(Optional) Add a Windows Runtime component

To accomplish some scenarios, you'll have to add code to a Windows Runtime component.

runtime component app service

Then, from your UWP project, add a reference to the runtime component. Your solution will look something like this:

Runtime Component Reference

Build your solution

Build your solution to ensure that no errors appear. If you receive errors, open Configuration Manager and ensure that your projects target the same platform.

Config manager

Let's take a look at a few things you can do with your UWP projects and runtime components.

Show a modern XAML UI

As part of your application flow, you can incorporate modern XAML-based user interfaces into your desktop application. These user interfaces are naturally adaptive to different screen sizes and resolutions and support modern interactive models such as touch and ink.

For example, with a small amount of XAML markup, you can give users with powerful map-related visualization features.

This image shows a Windows Forms application that opens a XAML-based modern UI that contains a map control.

adaptive-design

Note

This example shows a XAML UI by adding a UWP project to the solution. That is the stable supported approach to showing XAML UIs in a desktop application. The alternative to this approach is to add UWP XAML controls directly to your desktop application by using a XAML Island. XAML Islands are currently available as a developer preview. Although we encourage you to try them out in your own prototype code now, we do not recommend that you use them in production code at this time. These APIs and controls will continue to mature and stabilize in future Windows releases. To learn more about XAML Islands, see UWP controls in desktop applications

The design pattern

To show a XAML-based UI, do these things:

1️⃣ Set up your solution

2️⃣ Create a XAML UI

3️⃣ Add a protocol extension to the UWP project

4️⃣ Start the UWP app from your desktop app

5️⃣ In the UWP project, show the page that you want

Set up your solution

For general guidance on how to set up your solution, see the First, set up your solution section at the beginning of this guide.

Your solution would look something like this:

XAML UI Solution

In this example, the Windows Forms project is named Landmarks and the UWP project that contains the XAML UI is named MapUI.

Create a XAML UI

Add a XAML UI to your UWP project. Here's the XAML for a basic map.

<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>

Add a protocol extension

In Solution Explorer, open the package.appxmanifest file of the Packaging project in your solution, and add this extension.

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

Give the protocol a name, provide the name of the executable produced by the UWP project, and the name of the entry point class.

You can also open the package.appxmanifest in the designer, choose the Declarations tab, and then add the extension there.

declarations-tab

Note

Map controls download data from the internet so if you use one, you'll have to add the "internet client" capability to your manifest as well.

Start the UWP app

First, from your desktop application, create a Uri that includes the protocol name and any parameters you want to pass into the UWP app. Then, call the LaunchUriAsync method.


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);

}

Parse parameters and show a page

In the App class of your UWP project, override the OnActivated event handler. If the app is activated by your protocol, parse the parameters and then open the page that you want.

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();
        }
    }
}

In the code behind your XAML page, override the OnNavigatedTo method to use the parameters passed into the page. In this case, we'll use the latitude and longitude that were passed into this page to show a location in a map.

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);
 }

Making your desktop application a share target

You can make your desktop application a share target so that users can easily share data such as pictures from other apps that support sharing.

For example, users could choose your application to share pictures from Microsoft Edge, the Photos app. Here's a WPF sample application that has that capability.

share target.

See the complete sample here

The design pattern

To make your application a share target, do these things:

1️⃣ Add a share target extension

2️⃣ Override the OnShareTargetActivated event handler

3️⃣ Add desktop extensions to the UWP project

4️⃣ Add the full trust process extension

5️⃣ Modify the desktop application to get the shared file

The following steps

Add a share target extension

In Solution Explorer, open the package.appxmanifest file of the Packaging project in your solution and add the share target extension.

<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>  

Provide the name of the executable produced by the UWP project, and the name of the entry point class. This markup assumes that the name of the executable for your UWP app is ShareTarget.exe.

You'll also have to specify what types of files can be shared with your app. In this example, we are making the WPF PhotoStoreDemo desktop application a share target for bitmap images so we specify Bitmap for the supported file type.

Override the OnShareTargetActivated event handler

Override the OnShareTargetActivated event handler in the App class of your UWP project.

This event handler is called when users choose your app to share their files.


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();
    }
}

In this code, we save the image that is being shared by the user into a apps local storage folder. Later, we'll modify the desktop application to pull images from that same folder. The desktop application can do that because it is included in the same package as the UWP app.

Add desktop extensions to the UWP project

Add the Windows Desktop Extensions for the UWP extension to the UWP app project. You'll see more than one version of the extension (for example, 10.0.18362.0 and 10.0.19041.0). For info about how to choose a version, see Extension SDKs, and how to reference them.

desktop extension

Add the full trust process extension

In Solution Explorer, open the package.appxmanifest file of the Packaging project in your solution, and then add the full trust process extension next to the share target extension that you add this file earlier.

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

This extension will enable the UWP app to start the desktop application to which you would like the share a file. In example, we refer to the executable of the WPF PhotoStoreDemo desktop application.

Modify the desktop application to get the shared file

Modify your desktop application to find and process the shared file. In this example, the UWP app stored the shared file in the local app data folder. Therefore, we would modify the WPF PhotoStoreDemo desktop application to pull photos from that folder.

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

For instances of the desktop application that are already open by the user, we might also handle the FileSystemWatcher event and pass in the path to the file location. That way any open instances of the desktop application will show the shared photo.

...

   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;
        }
    }));
}

Create a background task

You add a background task to run code even when the app is suspended. Background tasks are great for small tasks that don't require the user interaction. For example, your task can download mail, show a toast notification about an incoming chat message, or react to a change in a system condition.

Here's a WPF sample application that registers a background task.

background task

The task makes an http request and measures the time that it takes for the request to return a response. Your tasks will likely be much more interesting, but this sample is great for learning the basic mechanics of a background task.

See the complete sample here.

The design pattern

To create a background service, do these things:

1️⃣ Implement the background task

2️⃣ Configure the background task

3️⃣ Register the background task

Implement the background task

Implement the background task by adding code to a Windows Runtime component project.

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;
    }

Configure the background task

In the manifest designer, open the package.appxmanifest file of the Packaging project in your solution.

In the Declarations tab, add a Background Tasks declaration.

Background task option

Then, choose the desired properties. Our sample uses the Timer property.

Timer property

Provide the fully qualified name of the class in your Windows Runtime component that implements the background task.

Specify entry point

Register the background task

Add code to your desktop application project that registers the background task.

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);
    }
}

Find answers to your questions

Have questions? Ask us on Stack Overflow. Our team monitors these tags. You can also ask us here.