WPF: Advanced Jump Lists using a Single Instance Application

March 2nd, 2011

Back in January I wrote a post on how to add Jump Lists to the Windows 7 Task Bar for your application.  One of the comments for that post was filled with some frustration, because the poster has not been able to find an example of a jump list that was more realistic or geared towards a more advanced implementation.  This post will try to help ease the poster of that comment as well as others seeking the same thing.

This post is intended to get you started in using jump lists in an advanced implementation, and is not meant to be an all inclusive solution for all problems you may run into.  The problem we will solve with this post is how to open another part of an application that is already running based on the jump list item the user clicked.

Outlook for example has a number of tasks that be performed using the jump list:

image

The first thing we need to do is ensure that our WPF application is a single instance application.  Meaning that only one instance can run at once.  We want to retain any state that the user may be in and perform any related tasks with the jump list, so we want prevent opening a new instance of the application and work with what is currently running.

There are a couple of ways to create a single instance application.  One involves using Windows Forms classes, but since I don’t want to pollute my WPF application with WinForms I will use an implementation from a Microsoft reference application.

Single Instance Application

First thing you need to do is add the SingleInstance.cs file to your application.  This class provides all the hard work that is done for you. 

Next you need to add a reference to System.Runtime.Remoting to your project. 

image

Now have your App.xaml.cs class implement ISingleInstanceApp.  There is only one method in this interface called SignalExternalCommandLineArgs that provides an ILIst<string> as a parameter.  It is called when a second instance of your application tries to run.  The args parameter is the command line arguments passed to the second instance.

public partial class App : Application, ISingleInstanceApp
{
    #region ISingleInstanceApp Members

    public bool SignalExternalCommandLineArgs(IList<string> args)
    {
        throw new NotImplementedException();
    }

    #endregion
}

The next step is to define your own Main function that will use the SingleInstance<TApplication> class and an Init method.  Your application class should look something like this:

public partial class App : Application, ISingleInstanceApp
{
    [STAThread]
    public static void Main()
    {
        if (SingleInstance<App>.InitializeAsFirstInstance("AdvancedJumpList"))
        {
            var application = new App();

            application.Init();
            application.Run();

            // Allow single instance code to perform cleanup operations
            SingleInstance<App>.Cleanup();
        }
    }

    public void Init()
    {
        this.InitializeComponent();
    }

    #region ISingleInstanceApp Members

    public bool SignalExternalCommandLineArgs(IList<string> args)
    {
        //TODO: handle command line arguments
        return true;
    }

    #endregion
}

 

Now this next step is VERY IMPORTANT. You need to change the Build Action of the App.xaml to Page.

image

That’s it for the single instance application.  Now only one instance of you application will run at one time.

Adding a Jump List

Now that we know we will only have one instance of our application running, we need to create our jump list.  For this post we will have a simple Hello and Goodbye JumpTask as follows.

private void CreateJumpList()
{
    JumpList jumpList = new JumpList();
    JumpList.SetJumpList(Application.Current, jumpList);

    JumpTask helloTask = new JumpTask();
    helloTask.Title = "Say Hello";
    helloTask.Description = "Shows a Hello World message.";
    helloTask.ApplicationPath = Assembly.GetEntryAssembly().Location;
    helloTask.Arguments = "/hello";
    jumpList.JumpItems.Add(helloTask);

    JumpTask goodbyeTask = new JumpTask();
    goodbyeTask.Title = "Say Goodbye";
    goodbyeTask.Description = "Shows a goodbye message.";
    goodbyeTask.ApplicationPath = Assembly.GetEntryAssembly().Location;
    goodbyeTask.Arguments = "/goodbye";
    jumpList.JumpItems.Add(goodbyeTask);

    jumpList.Apply();
}

Notice that we are using reflection to get the location of our application.  We are also passing an string to the Argument property of the JumpTask object.  This is what we will use to know what action to perform.

image

Responding to JumpTasks

So now we have a single instance application and our JumpList is defined.  Next thing we need to do is respond when a JumpTask is clicked.  To do that we need to do two things.  First we need a method somewhere that will perform the action.  The method will need to return a bool because it will be called from the SignalExternalCommandLineArgs method in the App.xaml.cs.  In this example I will create it on the MainWindow.

public bool ProcessCommandLineArgs(IList<string> args)
{
    if (args == null || args.Count == 0)
        return true;

    if ((args.Count > 1))
    {
        //the first index always contains the location of the exe so we need to check the second index
        if ((args[1].ToLowerInvariant() == "/hello"))
        {
            MessageBox.Show("Hello World");
        }
        else if ((args[1].ToLowerInvariant() == "/goodbye"))
        {
            MessageBox.Show("Goodbye cruel world");
        }
    }

    return true;
}

 

Now this method is going to be called when a second instance is created and the SignalExternalCommandLineArgs method is run:

public bool SignalExternalCommandLineArgs(IList<string> args)
{
    return ((MainWindow)MainWindow).ProcessCommandLineArgs(args);
}

Conclusion

As you can see the most complicated part of this whole thing is making sure we have a single instance application.  The rest is pretty easy.  I know this post doesn’t cover every single scenario of using a jump list, but is should give you the start you need to start solving those problems.

Be sure to download the source and start playing around with the code.

Comments are closed.