UWP Jump lists done right

I’ve spent some time the JumpList API for UWP apps recently and there were a couple of things that caught me by surprise that I wanted to share with you, so you don’t have to repeat the mistakes I did.

Jump list basics

Jump lists aren’t new, they have been around since at least Windows 7. You’ve seen them when you right-click a program’s icon in the taskbar. The context menu may give you quick access to recently or frequently used items and/or shortcuts to actions within the application.

Example of jump list

Example of jump list

COM developers could customize the jump list with APIs such as ICustomDestinationList and WPF developers may be familiar with the System.Windows.Shell.JumpList class. UWP developers gained similar capabilities in Windows 10 version 1511 (build 10586), which means it’s available on almost every Windows 10 machine by now. The API is really only implemented on desktop and tablet devices (i.e. no phone, HoloLens or Xbox) and the jump list shows up both on the taskbar and the app’s start menu tile. The Windows.UI.StartScreen.JumpList class is where it all happens, and while it has the same class name as the WPF version the usage is different. I encourage you to browse through the JumpList documentation and Microsoft’s sample on Github to familiarize yourself with the API if you haven’t already.

Saving can fail

You use JumpList.SaveAsync() to persist any configuration changes you’ve made. This might sound obvious, but since this involves writing to disk the method may fail for a number of reasons. I mention this because I found it failed more often than I expected, but this could just be due to interference from antivirus on my machine or something. More specifically, I got exceptions with HRESULT 0x80070497 which is Windows error ERROR_UNABLE_TO_REMOVE_REPLACED “Unable to remove the file to be replaced”. Anyway, I recommend that you code defensively and make sure you call SaveAsync from within a try-catch block and consider what the right action is in case of failure.

Icon appearance

You can provide an icon image URI for a jump list task by specifying JumpListItem.Logo. But at the time of this writing, the docs don’t tell you anything about the recommended size for such an icon. Microsoft’s sample application uses the 44×44 app icon, as specified in the appxmanifest. But when I tried that, it became obvious that such an image gets scaled by the system and gives a blurry, poor looking result. In my experience, you instead want to provide 16×16 and 32×32 image resources, just like good old Win32 icons. Which one gets used depends on the system scale factor. Since you refer to the image using a Uri, you can name the 16×16 image Xyz.scale-100.png and the 32×32 one Xyz.scale-200.png and then simply refer to it as Xyz.png, just like other image assets.

Oh and by the way, the icon you provide should look good on the Windows 10 start menu which currently defaults to a dark background color. So using black as the color for your icons, which a certain major social media company is currently doing, might not be the best idea.

Jump list with illegible icons

Jump list with illegible icons

Resource strings are version dependent

You may ask yourself when the best time is, in the app’s life cycle, to add custom jump list items. It’s tempting to see it as a one-off job and write code like this

But a more realistic implementation may need to re-create the list sometimes after an app is updated from one version to another, if the set of items has changed.

Now let’s sidetrack for a bit and talk about localization. Did you know you can use string resources for the user visible string properties of a JumpListItem? DisplayName, GroupName and Description are all localizable. So instead of writing

you may put Cheese = Käse in your German resw file with string resources and then refer to it as

Localization is a good thing and any real-world app that supports more than one language should do it this way. But there’s a catch. When you update your app and the package version changes, it breaks the Windows shell’s reference to your string resource. Going back to the question about when to create the jump list, this is definitely a case where you need to refresh the jump list after each app update. Why would changing app version break resources? Let’s dig deeper.

The Windows shell takes the ms-resource: URI you specify and persists it together with the full package name of your app package. It’s encoded using the format @{PackageFullName?resourceUri}, and can be resolved to the actual string using the SHLoadIndirectString Win32 API. Now keep in mind that package full names include the version of the package. The full resource identifier might look like this for example

Now what happens when you update the app from version 1.0.0.0 to something newer? If you don’t refresh your jump list, your users will see this

 

Broken string resource references

Broken string resource references

If the shell fails to resolve the string resource, it will display the encoded key as a fallback, which makes no sense to your end users. You could argue that the system should do a better job of handling app version changes here, but currently that’s something we need to deal with ourselves. What can we do about this problem? The easiest way is to make sure the jump list is refreshed during app startup every time your app has been updated. You could for example keep track of the app version in LocalSettings. But even that leaves a period of time between when the new version is installed to when it is launched the next time where the jump list will look like gibberish. Fortunately, UWP allows us to run code after an app update, even before the user actually launches the app. To do this you need a type of background task called an Update Task. So that’s typically where I would execute the jump list generation code from.

Side note: During development you may notice that Windows’ cache of the jump list content may persist even if you uninstall and reinstall an app. If you need to start from scratch completely, you can find the corresponding customDestinations-ms file under %APPDATA% \Microsoft\Windows\Recent\CustomDestinations and delete it (after taking a backup of course). These are binary files but with enough textual content that you should be able to find the right one using a text editor.

Summary

So, to summarize, my recommendations when dealing with jump lists are

  • Prepare to handle exceptions when saving the jump list.
  • Test your icons on screens with different scaling factors and make sure they look as expected.
  • If you localize the display strings, manage your jump list actions from an update task.

I’ve uploaded a sample project to Github that puts everything together. It’s a trivial app that lets you navigate to pages of different colors, either using the in-app navigation control or from the app’s jump list. It uses localized strings, provides icons of different scales and updates the jump list from a background task.

1 Comment

  1. Martin

    I also noticed that my app is sometimes crashing for users when calling SaveAsync with COM exception 0x80070497. It’s probably a good idea to handle these errors gracefully.

Leave a Reply