UWP app with Systray extension

A common piece of feedback from folks exploring the move from Win32/NET to UWP is about the lack of Systray (aka notification area) support in UWP. It is true that UWP code can’t run in the Systray, however a UWP package can contain a Win32/NET component that can put itself into the Systray. The UWP package will still be universal (run on Surface Hub, Windows Phone, etc.), and light up the PC-specific Systray functionality on the desktop edition only. In this post I will explain how this can be accomplished on Windows 10.

Capture

TL;DR
See source code on GitHub and install the demo app from the Windows Store:
Get it on Windows 10

Details
First step is adding a WinForms project to your UWP solution. It can be done in C++/Win32 as well, but personally I find WinForms to be the most convenient framework for legacy Systray component development. Next add a class file (in my case called ‘SystrayApplicationContext.cs’) to implement all your systray functionality.

SolutionExplorer

To connect the UWP app and the WinForms systray component, we will be using the following two UWP platform features on Windows 10: FullTrustProcessLauncher and AppService. For the former we need to add a reference to the Windows 10 Desktop Extensions SDK:

AddReference

Now we can declare the “windows.fullTrustProcess” and “windows.AppService” extensions in the manifest for our UWP app package. Note also the two capability declarations. The first one is needed for launching the WinForms code from the UWP, the second one we will need in order to intercept the CloseRequested event (when the user hits the “X” on the window). The latter is somewhat unrelated to the topic of this post, but it’s an implementation choice I made for the sample app – and might be useful for your similar scenarios. Note that this API is available in the Creators Update in preview state.

manifest

With the project structure and manifest declarations in place, we can start implementing the desired functionality. First, we want to intercept the user’s close gesture by subscribing to the CloseRequested event:

SystemNavigationManagerPreview mgr =
 SystemNavigationManagerPreview.GetForCurrentView();
mgr.CloseRequested += SystemNavigationManager_CloseRequested;

When the event got triggered we let the user decide if/how they want to close the app. If they chose “Close to Systray” we will launch our WinForms component that will continue to run our code in the notification area. Note that I am only making this call on systems that support the FullTrustAppContract. This way my app will still be universal and work correctly on all editions of Windows 10, such as Surface Hub or Windows Phone:

case CloseAction.Systray:
 if (ApiInformation.IsApiContractPresent(
  "Windows.ApplicationModel.FullTrustAppContract", 1, 0))
 {
 await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
 }

Now switching over to the WinForms component. All the relevant code lives in SystrayApplicationContext.cs. I am implementing basic functionality here in the context menu to demonstrate a couple of common concepts:

  1. Open UWP” – restores the UWP app back to the foreground
  2. Send message to UWP” – sends a message via AppService, the UWP will display it as toast (if in background) or dialog (if in foreground)
  3. Open legacy companion” – displays a WinForms UI that is implemented in the same component, included in our UWP package
  4. Exit” – exits both the WinForms and UWP processes

Here is how we hook up the menu handlers. Explore the full source code on GitHub for details on each of the menu actions.

public SystrayApplicationContext()
{
 MenuItem openMenuItem =
 new MenuItem("Open UWP", new EventHandler(OpenApp));
 MenuItem sendMenuItem =
 new MenuItem("Send message to UWP", new EventHandler(SendToUWP));
 MenuItem legacyMenuItem =
 new MenuItem("Open legacy companion", new EventHandler(OpenLegacy));
 MenuItem exitMenuItem = new MenuItem("Exit", new EventHandler(Exit));
 openMenuItem.DefaultItem = true;

 notifyIcon = new NotifyIcon();
 notifyIcon.DoubleClick += new EventHandler(OpenApp);
 notifyIcon.Icon = SystrayComponent.Properties.Resources.Icon1;
 notifyIcon.ContextMenu = new ContextMenu(new MenuItem[]
   { openMenuItem, sendMenuItem, legacyMenuItem, exitMenuItem });
 notifyIcon.Visible = true;
}

Two of the actions I want to highlight real quick, as they are using UWP features, called from the WinForms component, which may be a bit of a new concept for some readers. To get access to those APIs (AppListEntry and AppServiceConnection) I just need to add a reference to the Windows.winmd in my WinForms project. Then I can make these calls:

private async void OpenApp(object sender, EventArgs e)
{
 IEnumerable<AppListEntry> appListEntries = await Package.Current.GetAppListEntriesAsync();
 await appListEntries.First().LaunchAsync();
}

private async Task SendToUWP(ValueSet message)
{ 
 if (connection == null)
 {
  connection = new AppServiceConnection();
  connection.PackageFamilyName = Package.Current.Id.FamilyName;
  connection.AppServiceName = "SystrayExtensionService";
  connection.ServiceClosed += Connection_ServiceClosed;
  AppServiceConnectionStatus connectionStatus = await connection.OpenAsync();
 }
 await connection.SendMessageAsync(message);
}

Hope this information was useful for some of you and will help you when you make the jump from Win32/NET to UWP on Windows 10. Please leave your questions and comments here and I will respond as soon as I can.

Advertisement

UpdateTask for Desktop Bridge apps

We had a number of folks asking this question during //BUILD2017 at the Desktop Bridge booth, and today it came up again on Stackoverflow, so I decided to publish a sample and explain it here: How can I trigger some code when an update for my Win32/NET app has been deployed from the Windows Store?

tile
Source code on GitHub

Before I answer it, let me say that I think most apps won’t ever need to do this as they can run whatever new code they need to run when the app is launched the next time. However, for those cases where you do need to run code in the background automatically after the update has been deployed there is the UWP UpdateTask. If you have a packaged Win32/NET app that you brought to the Windows Store via Desktop Bridge, you can now utilize this task for your app in pretty much the same way as a regular UWP app would implement it. I have posted a WinForms app sample on GitHub – let me walk you through the details. First thing we need to do is extend our UWP app package with an extensions for the background task:

<Extensions>
  <Extension Category="windows.updateTask"
             EntryPoint="BackgroundTasks.UpdateTask" />
</Extensions>

To implement the background task we need to create a Windows Runtime component. So let’s add a project of that type to our solution in Visual Studio and name it “BackgroundTasks”:

addproject

All we need to do in this project is implement our UpdateTask class, derived from IBackgroundTask. For the sample we will just display the new package version in a toast notification:

namespace BackgroundTasks
{
    public sealed class UpdateTask : IBackgroundTask
    {
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            PackageVersion version = Package.Current.Id.Version;
            string message = string.Format(
                "WinForms app updated to {0}.{1}.{2}.{3}",
                version.Major,
                version.Minor,
                version.Build,
                version.Revision);
            ToastHelper.ShowToast(message);
        }
    }
}

 

That’s all it takes to set this up. In case you need to execute some of your Win32/NET code outside of the UWP process that is hosting your task, you can do so using the FullTrustProcessLauncher API (not shown in this sample, but we have other samples for that API).

To see the sample in action, take the following steps:

  1. build and deploy the solution on your PC
  2. change the version number in the package manifest
  3. deploy the app again

Result: UpdateTask gets triggered and runs your code which displays the new version number

toast

Note: if you are not using Visual Studio/msbuild to build & package your own application, you will also need to add the “windows.activatableClass.inProcessServer” extension in your package manifest. VS/msbuild does this automatically for you, for any Windows Runtime components referenced by your app project – but if you need to do this manually check the appmanifest.xml file in the build output of the sample as reference.