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.

Advertisements

42 comments

  1. The Systray can’t support background media player of the UWP project.
    When I closed the app, the background task still be received the cancel event.

    Because I want to implement the feature:
    When app be close, the background media player can keep play music in the Systray.

    Is it possible? thank you.

    Like

  2. Thank you for this great article.
    I would like to ask a question.
    If I update my existing UWP app that is published in store to use capabilities mentioned here, should I expect anything unusual? I mean, I’ve seen that projects that are converted through the bridge are publishing with a special onboarding process, will it also be the case for my app?

    Like

    1. Yes, when you submit your update that adds the ‘runFullTrust’ capability the submission will go through a special onboarding review. Let me know if you run into any problems with that.

      Like

  3. Hi and thank you for a great article.
    I would like to ask a question.
    I have a UWP app published in store, and I would like to know if there would be any obstacles while publishing an update if I update my app to use the capability mentioned here.
    Thanks.

    Like

  4. did something break with fall creators update? I get the error c:\program files\windowsapps\….\Systraycomponent.exe -> The data area passed to a system call is too small. This was installed from the store. I haven’t tried from the repository yet.

    Like

      1. Well timing is everything. We’ll have to wait for the CU for SQL to come out to fix this as the article says, we’re using filestreams in the database. Good to know though for future.

        Like

  5. Dear Stefanwick,

    As per our current analysis, systray app is just acting like a Dock in the system tray. It is stopping its activity immediately after minimized to systray.

    We are having the following requirement:

    Once the app minimized to systray, It should be running in background and keep on sending SendIOControlAsync() [Async IOCTL from UWP] request from background to a custom kernel mode driver [for a virtual device].

    Once we got response from driver it should show notification to user.

    Note: Background task worker thread can be invoked only by the response from driver. We don’t have any system event to invoke it. Also the driver will respond back to the Async IOCTL only after 1 hr/2 hr.(It means we could not define a specific response time period)

    Will that be supported in UWP systray app?

    Your suggestions are greatly appreciated.

    Regards,
    Vicky

    Like

    1. I think your question is really unrelated to the systray. You can keep the UWP running when minimized – no need for systray to do this. Take a look at this: https://docs.microsoft.com/en-us/windows/uwp/launch-resume/run-in-the-background-indefinetly

      You may or may not have a systray presence – that’s really your design choice.

      Regarding this sample: yes, the UWP process by default stops executing when minimized, but the systray component keeps running, so you could also just make those periodic calls from that component. This has the added advantage that you have access to the full Windows API from that component, whereas the UWP process is limited to the UWP API surface.

      Like

  6. I have a requirement to host a WCF service to receive responses from a service. As UWP app doesn’t have wcf hosting capabilities, I chose this system tray component where I will host my service so that I get responses and communicate to UWP app via app service and show toasts.

    But I couldn’t figure out what’s happening, the systray component terminates immediately after the launch. To be clear on my systray integration with uwp I already tested it and is working and then extended it with WCF hosting.

    Is there any issue in my concept or this scenario isn’t acceptable with UWP?
    Kindly help.

    Like

    1. Hi Siddhardha,

      I can’t think of any reason why this wouldn’t work. We would need more details to analyze why it’s not working. Have you run this under a debugger yet? You might want to put this out on Stackoverflow and we’ll get you an answer there.

      Thanks,
      Stefan

      Like

  7. Hi Stefanwick

    Wonderful work! But I ran into an issue. My UWP app is in JavaScript and I used your C# code for Systray Extention. I ran into this weird error of “The type or namespace name ‘Windows’ does not exist in the namespace ‘System’ (are you missing an assembly reference?)”. I added the System.Windows.Forms reference too and I tried changing the .NET framework version to 4.5.2 and then the error changed to “The type or namespace name ‘ApplicationModel’ does not exist in the namespace ‘Windows’ (are you missing an assembly reference?)”. Can you please help me with this.

    Thanks,
    Madhav

    Like

      1. Thanks a ton! It works great except that when I open from the system tray, the app restarts to its home page, even when the app is just minimized and not closed. Is there a way to open the app at its current page if it is in suspended state?

        Thanks,
        Madhav

        Like

  8. Hey Stefanwick,

    Fantastic tutorial, thanks so much!

    Quick question – I’m trying to initialize Firebase (step-up-labs version) in the SystrayComponent, but it seems to crash. Is there any way to see the error messages being thrown? I’m also using JS for the UWP app and logs from that appear fine.

    Is there anything I’m missing to get the Firebase initialization working? (e.g. internet permissions etc.?) https://github.com/step-up-labs/firebase-authentication-dotnet

    Thanks,
    Kieran

    Like

  9. Hi Stefanwick

    Did you find a way to give admin rights to the win32 app? I’m asking because I’d like to log to EventViewer and I need admin rights in order to create a source. Also, I’ve tried to use MetroLog for both apps, but I can not find the log files for win32 app, only for UWP one. What would you recommend for logging here?

    Thanks

    Like

    1. Hi Adrian:

      there are two answers to your question:

      1. being able to run the win32 process with admin privileges is a new feature that is coming in the 2018 fall update of Windows 10. It should already be working in current insider builds (need to declare the new restricted capability “allowElevation”). Hope I can share a sample for this soon.

      2. Just for the purpose of logging you should not be required to elevate. Not very familiar with Metrolog, but have you raised an issue with them? Note that declaring the new capability for elevation may prevent you from shipping in the Store, so this may not be the preferred route for this problem anyway.

      Thanks,
      Stefan

      Like

  10. I have tried converting an existing application using Desktop App Convertor by providing input as the .msi file from the installer package folder path of my existing desktop application. I tried installing it and using the application post conversion and it works.. But I have few questions below.

    1. The converted application couldn’t be launched on the OS version which is less than the Base image version took for conversion. Is this expected? So if we want to support old versions should we take the 10.0.14393 as base image and convert?

    2. Post installing the converted app, launched WACK to test the installed app and it fails for “Package sanity test” saying ProcessorArchitecture is x86 in manifest and these 2 dll’s (comctl32.dll & GdiPlus.dll) were of x64 config.
    I didn’t mentioned anywhere as x86 during conversion, no idea how it is taken in manifest. How to resolve this?

    3. The converted application has only .appx created, no .appxupload file generated. For these kind of converted applications case how to reach the Store?

    Like

    1. Hi SiD,

      1. just set MinVersion=”10.0.14393.0″ in your appxmanifest file to support that version, assuming your app doesn’t depend on OS features that have been added only in later versions

      2. use -packageArch parameter to specify x64 if you intended to create an x64 package
      https://docs.microsoft.com/en-us/windows/uwp/porting/desktop-to-uwp-run-desktop-app-converter#architecture-params

      3. you can upload .appx files to the store as well. But if you want to go with .appxupload, the best way to create an .appxupload file for your Win32 application would be to use the Visual Studio packaging project for creating the packages.

      Btw, for general questions like these that aren’t directly related to the blog post, I recommend using stackoverflow with tag “DesktopBridge” in the future, to get the best visibility and traction on your requests.

      Thanks,
      Stefan

      Like

      1. 1. As I used the Convertor I had not prepared any manifest manually. Post Conversion it is generated automatically and it already has min version tag with 14393 version. Still it is not launching on RS1 & RS2.

        2. So do you mean we have to generate multiple packages with same desktop application input to generate separate appx?

        3. As this is converted app using the converted tool, I can’t package it through Visual Studio as I’m not using the source but making use of the builded product i.e. msi file.

        Like

  11. 1. Need more details about the app to answer that. I suggest you post a question on stackoverflow with repro and/or logs.

    2. It’s your choice. If you want to distribute multiple architecture then you will need to create multiple appx package – one for each architecture. You can then zip them up and rename to .appxbundle to combine all of them and submit them in one shot to the Store.

    3. It’s possible to use the packaging project also with pre-built binaries, but you don’t have to use VS to create an .appxupload file. You can create it manually, it’s just a zip file that contains the .appxbundle and the .appxsym file. Then again, you need to have an .appxupload as you could also submit either the .appx or the .appxbundle file directly to the Store.

    Like

  12. Hi Stefanwick,

    Thanks for the excellent sample for enabling Uwp UI to be rendered in response to systray [ / notification area ] process context menu selections. A couple of related questions . . .

    q1. When I f5 on the SystrayExtension and try to debug through SystrayComponent breakpoints that doesn’t work as it appears vs17 15.8.2 debugger launch process doesn’t successfully hookup debugger to both processes. Is there a trick to making that work? All I’ve been able to make work thus far is launch a debug version of SystrayExtension from outside of vs17 that I then close to leave SystrayComponent process running at which point I attach vs17 debugger to in order to get support for stepping through all but startup code for SystrayComponent.

    q2. If I want startup to immediately got to minimized SystrayComponent process presence, vs uwp UI view, would I just have SystrayExtension | MainPage.xaml.cs | SystrayExtension OnNavigatedTo() event issue a call to version of SystemNavigationManager_CloseRequested() event handler that executes the case CloseAction.Systray: steps?

    q3. Currently the SystrayComponent display what appears to be a SystrayApplicationContext constructor runtime defined Windows.Forms based context menu. Is there a way to have it instead pop a SystrayExtension defined uwp UI above the systray icon when context / right mouse button is clicked similar to what it seems the OneDrive, Sounds and Windows Ink Workspace do?

    Like

    1. For Q1: this is currently a gap in the Visual Studio debugging support. It does not automatically attach to both processes as you would expect. The VS team is looking into possible solutions.

      For Q2: You would just call FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync() during start up of the UWP app, e.g. from your MainPage’s OnNavigatedTo event.

      For Q3: A UWP UI is currently not supported there. However, with the new RS5 support for hosting UWP-XAML in Win32 processes you should be able to accomplish such scenarios.

      Liked by 1 person

      1. Thanks for the responses, that helps.

        Any reason why with this uwp desktop bridge scenario a person shouldn’t be using a “windows application packaging project” to produce the store publishing upload similar to what you made use of in GlobalHotkey uwp desktop bridge sample vs the package output of the SystrayExension uwp app project?

        Like

      2. Actually, it’s highly recommended to use the Packaging project also for this type of scenario. The only reason I was not using it here is because I created this sample a long time ago before the Packaging project was able to support the scenario (and then I never found the time to update it later).

        Liked by 1 person

      3. In the derivation of your sample i’m working on i created a SystrayComponent | SystrayApplicationContext.cs user input event handler that opens a registry key for read and write purposes [ using (RegistryKey key = Registry.CurrentUser.OpenSubKey(“Control Panel\\Mouse”, true)) ] that is successfully able to read the SwapMouseButtons value but fails to write it.

        The exact same code running in a .net framework console app is able to successfully write to that key/value without requiring any “run as administrator privileges”.

        Is there some setting i’m overlooking that is necessary to allow the .net framework / winforms based SystrayComponent to be able to write to hkcu paths?

        Like

      4. Thanks for response. If one where to make their systray [ / notification area ] application using only .net framework windows app project, e.g. the systraycomponent project only, would that change this restriction?

        Like

      5. Thanks for clarification that helps. Sounds like one would have to resort to a wix msi installed set of bits if it was critical to app experience that you have a way to get around the registry hkcu write security issue.

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s