Creating Windows 7 Jump Lists With The API Code Pack and Visual Studio 2008

Introduction

Now that Windows 7 is readily available at every computer store, it's only a matter of time until the new operating system becomes mainstream in both homes and corporations. Especially this will happen once new computers start replacing old ones.

From a developer perspective, Windows 7 brings many possibilities. One of the most visible and prominent new features is the improved taskbar, through which applications are launched and even managed. In this article, the focus is on a feature called jump lists, which can be thought of as being a mini Start menu when right-clicking an icon on the taskbar (Figure 1).

A basic Windows 7 jump list
Click here for larger image
Figure 1. A basic jump list.

Jump lists are active both when right-clicking an icon on the taskbar, but also when pointing at an application icon in the Start menu's initial view (Figure 2). Thus, there are two ways to make jump lists visible; however these menus have a different focus: jump lists on the Start menu are about the recent documents, whereas the taskbar-activated jump list contains more options to manage the application and its windows.

Figure 2. Jump lists in the Start menu.
Click here for larger image
Figure 2. Jump lists in the Start menu.

Programming both instances of jump lists requires only a single implementation. Under the hood, jump lists use a COM based API, just like many other features in the Windows shell. Although it is possible to access these COM interfaces directly from your application, there is an easier way. Microsoft has developed a free package for .NET developers called the Windows API Code Pack (Figure 3), which contains many ready-made classes to access Windows features, such as Windows 7's jump lists.

The Windows API Code Pack download page.
Click here for larger image
Figure 3. The Windows API Code Pack download page.

Next, you are going to see how you can use Visual Studio 2008 and C# to enable custom jump lists in your own .NET applications with a WinForms sample application (Figure 4). A similar method could be used with Visual Studio 2010 Beta 2. With Visual Studio 2010 however, you can also use WPF 4.0's new support for Windows 7 shell integration. Even so, these classes are out the scope of this article, but the topic will likely be addressed in the near future.

The sample application
Click here for larger image
Figure 4. The sample application.

Briefly About Jump List Features

Jump lists on Windows 7 can be considered streamlined mini versions of the Windows Start menu. Furthermore, they are tailored for each application. Just like in all Windows versions after Windows 95, you can right-click an icon on the taskbar, and see a popup menu. Jump lists in Windows 7 are an evolution of this menu.

Indeed, even if you don't do anything special, Windows 7 will display a popup menu for your application when its icon is available on the taskbar (see again Figure 1). If you have pinned an application to the taskbar, the default menu will give you the option to unpin it or launch the application. If the application is running, the default menu contains options to pin the program to the taskbar, close it, or launch another instance.

If an application has associated a file type (file extension) with itself in the registry--for instance, Word with .doc and .docx files--Windows will collect a list of recent files opened with the application. This happens automatically if the application uses Windows common file dialogs to open files, or uses the SHAddToRecentDocs API function to let Windows know that files were opened. Windows will also keep track of which files are used most frequently.

Files opened with an application from two so-called known lists. These are the Recent and Frequent lists. The difference between these two is that Recent will collect the most recently opened files (say, five of them), and the Frequent list will on the other hand collect files that are most commonly used over time. The application can control whether one of these lists is shown in the jump list, or neither.

These known lists are said to contain destinations, or nouns. Destinations are always files, and because of this, a file handler for a given file type (or types) must be registered. Windows will also make sure the files are available before showing them on the list.

Another important part of jump lists, and the focus of this article, is the support for verbs. Verbs allow your application to freely add custom commands to the jump list, and then allow users to quickly interact with your application while it's running. Unlike nouns, verbs do not require you to associate a file extension with your application.

Verbs are file based commands that point back to your application, and instruct it to run certain commands. For example, if you were writing a sales application, then the jump list verbs could start a new order, find a customer, and so on. Since these lists are customized for each application, every application has its own set of verbs.

Using the Windows API Code Pack

The freely downloadable Windows API Code Pack (see the Links section at the end for the URL) is a set of .NET classes compiled into several different assemblies. You can easily reference these from your own projects, and thus benefit from the classes.

To get started with the code pack, you first need to download it. It comes in a regular zip file, which you should extract to a convenient location, such as under your Visual Studio projects folder. Then, navigate inside the newly extracted folders, and open a solution named Shell.sln with Visual Studio. Build the whole thing, and then note the location of the resulting DLL files, usually under the bin\Debug folder.

After a successful build, you should see two assemblies, Microsoft.WindowsAPICodePack.Shell.dll and Microsoft.WindowsAPICodePack.dll in the output folder. The latter file is automatically created, as the Shell project depends on a project called Core. The Core project is also part of the code pack.

The next step is to start a new project (or open one, if you already have one) in Visual Studio, and add the two code pack DLLs as references to your project (Figure 5). With the proper references in place, you can start writing code. The assembly filenames already give a hint of the correct namespaces; to use the code pack for jump lists, add the following using statements to your C# code:

  using Microsoft.WindowsAPICodePack.Shell;
  using Microsoft.WindowsAPICodePack.Taskbar;

References have been to the sample project
Click here for larger image
Figure 5. References have been to the sample project.

Inside the latter namespace, you can find a class called JumpList, which is a great starting point to begin exploring the features in jump lists. For instance, let's see how you could add a custom jump list verb to your application's menu.

The first step is to create an instance of the JumpList class. After this, you need to construct an object supporting the IJumpListTask interface for each custom verb you wish to add to your jump list. This can be done with the help from the JumpListLink class, also part of the Microsoft.WindowsAPICodePack.Taskbar namespace.

For instance, you might want to add commands into your application's jump list to quickly launch Notepad or Calculator. To do this, you could use the following code:

  JumpList list = JumpList.CreateJumpList();
  
  string systemFolder = Environment.GetFolderPath(
      Environment.SpecialFolder.System);
  
  string notepadPath = System.IO.Path.Combine(
      systemFolder, "notepad.exe");
  JumpListLink notepadTask = new JumpListLink(
      notepadPath, "Open Notepad");
  notepadTask.IconReference = new IconReference(
      notepadPath, 0);
  
  string calculatorPath = System.IO.Path.Combine(
      systemFolder, "calc.exe");
  JumpListLink calculatorTask = new JumpListLink(
      calculatorPath, "Open Calculator");
  calculatorTask.IconReference = new IconReference(
      calculatorPath, 0);
  
  list.AddUserTasks(notepadTask, calculatorTask);
  list.Refresh();
  MessageBox.Show("Custom tasks have been added!");

Here you are creating a new jump list object, and then constructing two verbs with the JumpListLink class. The constructor requires a file-based command, and in this case this is the full pathname to Notepad.exe (and Calc.exe) in the Windows\System32 directory. The System.IO.Path class helps in the process. Also, menu icons are added using the IconReference class, which takes in a full path and an icon index. Zero means the first icon in the file, which is commonly the main icon.

Finally, the AddUserTasks method of the JumpList class is called to add the new verbs to the menu. Windows 7 will create the commands under the Tasks generic group, and thus the verbs added here can also be called tasks. Note that Windows will keep a record of the tasks you create. Thus, you will only need to create your tasks once.

Creating Windows 7 Jump Lists With The API Code Pack and Visual Studio 2008

Pointing back to your own application

Although starting external applications from jump lists is easy, it doesn't help much if you want to use jump lists to command your own application. Currently, a jump list command must point to a file on disk, i.e. a document file with a registered handler application, or an executable with optional command-line parameters.

Technically, this means that to be able to send commands back to the currently running application instance, you would have to implement some kind of inter-process communication (IPC) between the application instance that shows the jump list (the "server") and the one that Windows will launch when a command is selected from the jump list (the "client").

For instance, let's say you would need to implement two custom commands in your application. For simplicity, these commands could be named Command A and Command B. To create jump list tasks for these two, you would need to construct the full path to your executable, and then append command- line parameters telling the application which command was actually selected. For instance, the path could be: C:\MyApps\Win7JumpListDemo.exe

With this path, Windows would start your executable, and pass in the parameters you specify. The application would then check the existence of command-line parameters, and if correct ones were given, it would communicate them with already running instance and exit immediately.

First, let's see how you could create jump list tasks that point back to your own application. The code looks similar to what you've already seen:

JumpList list = JumpList.CreateJumpList();
...
string selfPath = System.Environment.CommandLine;
// the value comes with quotation marks, strip them out
selfPath = selfPath.Substring(1, selfPath.Length - 3);
  
JumpListLink selfCommandATask = new JumpListLink(
      selfPath, "Command A");
selfCommandATask.Arguments = "Command-A";
selfCommandATask.IconReference = new IconReference(
      selfPath, 0);
  
JumpListLink selfCommandBTask = new JumpListLink(
      selfPath, "Command B");
selfCommandBTask.Arguments = "Command-B";
selfCommandBTask.IconReference = new IconReference(
      selfPath, 0);
  
JumpListSeparator separator = new JumpListSeparator();
list.AddUserTasks(notepadTask, calculatorTask,
      separator, selfCommandATask, selfCommandBTask);

With these tasks added, the jump list would look like the one in Figure 6. The path to the application instance is read from the System.Environment.CommandLine property which contains quotation marks around the full path. These must be removed for the jump list tasks to work correctly; otherwise Windows will report an error when trying to launch the application.

The custom tasks have been added to the application's jump list
Click here for larger image
Figure 6. The custom tasks have been added to the application's jump list.

The other important part is to specify the command-line parameters using the Arguments property. Note that at the time of this writing, the Windows API Code Pack documentation file doesn't list the Arguments property at all. Nonetheless, it is available for usage from code.

Now, if the user would select "Command A" from the application's jump list, Windows would execute the following command (with the real runtime path of course):

C:\MyApps\Win7JumpListDemo.exe Command-A

When the application starts, some code would be needed in the Program.cs file to check for the parameters. In the sample application, the main method of the application looks like this:

  static void Main(string[] args)
  {
    if (CommandLineParameters.ParametersGiven(args))
    {
        CommandLineParameters.ProcessCommandLine(args);
    }
    else
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      Application.ApplicationExit +=
        (obj, evt) =>
        {
            PipeCommunications.SignalCloseEvent();
        };
      Application.Run(new MainForm());
    }
  }

A custom class called CommandLineParameters is used to check if there are command-line parameters, and if yes, then the same class can process them. Otherwise, the application starts normally and shows the user interface.

Communicating with Pipes

Sending messages between two running applications on the same machine is a common requirement. Windows itself calls this inter-process communication, or IPC, and provides multiple ways to implement this. For instance, you could be using memory-mapped files, TCP/IP sockets, or named kernel objects. One option is to use so-called named pipes, which provide a convenient and straightforward way to send messages without tweaking with security or firewall settings. Pipe programming in .NET was also made easier with .NET 3.5's new System.IO.Pipes namespace.

In the sample application, pipes are used for communication as follows. When the sample application is launched without any parameters, it launches the user interface normally and starts a thread that listens to pipe connections.

On the other hand, when the user chooses a command from the jump list, the application is started with command-line parameters. In this case, the application opens a pipe connection to the existing server application pipe, and sends the command-line parameter to the already running instance. Then, the server is free to do whatever is applicable for the command.

The pipe handling is implemented in the custom PipeCommunications class. Although the focus of this article is in jump lists and not in pipe communications, the code is worth walking through briefly, as IPC is a common requirement when working with jump lists. When the sample application starts without parameters, the main window's constructor runs the following code:

PipeCommunications pipes = new PipeCommunications(
  (cmd) =>
  {
     communicationLogTextBox.Invoke(
     new LogMessageToTextBoxDelegate(
        LogMessageToTextBox),cmd);
   });
pipes.CreateListenPipeThread();

Creating Windows 7 Jump Lists With The API Code Pack and Visual Studio 2008

Here, the PipeCommunications class is initialized with a delegate that does the real processing of the command received through the pipe. Note that since the pipe communication is done in a separate thread, the WinForms control's Invoke method must be called to properly access user interface elements without threading issues. A lambda expression is used to construct the event handler. The real work is done in the LogMessageToTextBox method of the main form:

public delegate void LogMessageToTextBoxDelegate(string cmd);
...
private void LogMessageToTextBox(string cmd)
{
 communicationLogTextBox.Text =
  DateTime.Now.ToLongTimeString() +
  ": Got command: " + cmd + "\r\n" +
  communicationLogTextBox.Text;
} 

Although this implementation simply logs the received command on the screen (Figure 7), your own application could easily do some real work here, such as accessing a database, printing a document, or opening a dialog box on the screen.

The server application has received commands through the pipe
Click here for larger image
Figure 7. The server application has received commands through the pipe.

The CreateListenPipeThread method called by the main form's constructor executes the following code:

  using System.IO.Pipes;
  using System.Threading;
  ...
  Action<string> processCommandAction;
  private static AutoResetEvent closeApplicationEvent;
  ...
  internal Thread CreateListenPipeThread()
  {
      closeApplicationEvent = new AutoResetEvent(false);
      Thread serverThread = new Thread(
          new ParameterizedThreadStart(
              PipeHandlerServerThread));
      serverThread.Start(serverThread);
      return serverThread;
  }

The code creates a new thread which handles the server end of the pipe communications, and also creates a single auto-reset event. This event is signaled (set) from the Program.cs file when the application is about to exit. If no such event would be used, the main form would close normally, but the pipe thread would continue running. Thus, the thread must be properly ended to completely shut down the application.

The real server-side work of the pipe communication is done in the following two methods:

  private const string PipeName = "win7-jumplist-demopipe";
  ...
  internal void PipeHandlerServerThread(object threadObj)
  {
    Thread currentThread = (Thread)threadObj;
    NamedPipeServerStream pipeServer = null;
    try
    {
      while (true)
      {
        pipeServer = new NamedPipeServerStream(
          PipeName, PipeDirection.InOut, 1,
          PipeTransmissionMode.Byte,
          PipeOptions.Asynchronous);
        try
        {
          IAsyncResult async =
            pipeServer.BeginWaitForConnection(
            null, null);
          int index = WaitHandle.WaitAny(
            new WaitHandle[]
            {
              async.AsyncWaitHandle,
              closeApplicationEvent
            });
          switch (index)
          {
            case 0:
              pipeServer.EndWaitForConnection(async);
              ProcessPipeCommand(pipeServer);
              break;
            case 1:
              // exit this thread
              return;
            default:
              pipeServer.Close();
              break;
          }
        }
        catch (Exception ex)
        {
          processCommandAction("Exception: " +
            ex.Message);
        }
      }
    }
    finally
    {
      pipeServer.Close();
    }
  }
  
  private void ProcessPipeCommand(
    NamedPipeServerStream pipeServer)
  {
    string command;
    using (StreamReader reader =
      new StreamReader(pipeServer))
    using (StreamWriter writer =
      new StreamWriter(pipeServer))
    {
      command = reader.ReadLine();
      processCommandAction(command);
      writer.WriteLine("OK");
    }
    if (pipeServer.IsConnected)
    {
      pipeServer.Disconnect();
    }
  }

The code creates an instance of the NamedPipeServerStream class, and starts to wait for a connection asynchronously. Waiting is done at the same time for both the pipe connection and the application close event. Depending on which comes first, the pipe thread simply exits, or starts to process the pipe command received (in the ProcessPipeCommand method). Waiting is done using the WaitHandle class, part of the System.Threading namespace.

Next, let's see how the client application behaves. When the application is started with command-line parameters (i.e. through the jump list tasks), the following code runs:

  internal static void ProcessCommandLine(string[] args)
  {
      string command = args[0];
      PipeCommunications pipes = new PipeCommunications();
      string result = pipes.SendCommandToServer(command);
  }

Here, an instance of the same PipeCommunications class is created, but since this is the client side implementation of the pipe (data flows from the application to the already running application instance), no delegate needs to be given to the PipeCommunications class.

Sending commands through the pipe is implemented in the SendCommandToServer method:

  internal string SendCommandToServer(string command)
  {
    try
    {
      NamedPipeClientStream pipeClient =
        new NamedPipeClientStream(
          "localhost", PipeName, PipeDirection.InOut);
      try
      {
        using (StreamReader reader =
          new StreamReader(pipeClient))
        using (StreamWriter writer =
          new StreamWriter(pipeClient))
        {
          pipeClient.Connect();
          writer.WriteLine(command);
          writer.Flush();
          string response = reader.ReadLine();
          return response;
        }
      }
      finally
      {
        pipeClient.Close();
      }
    }
    catch (Exception ex)
    {
      System.Windows.Forms.MessageBox.Show(
        "Windows 7 Task Bar Demo: Pipe " +
        "communication failure: " + ex.Message);
      return null;
    }
  }

The command-line parameter ("Command-A" or "Command-B" in the sample application's case) is simply sent to the pipe, and then a response of "OK" from the server acknowledges that all is well. After this, the client can simply exit.

Conclusion

In this article, you saw how you can use Visual Studio 2008 and the newly-released Windows API Code Pack to get the best out of Windows 7's new features. The sample application accompanying this article demonstrated how you can use C# to implement jump list functionality in your own applications, and along the way let users of Windows 7 benefit from the operating system's new features.

The utility classes in the Windows API Code Pack allow you to easily manipulate the task bar and control your own application's menus. However, to write commands that point back to your own application requires some extra effort, as you need to implement a communication channel between the currently running application instance and the one that Windows 7 starts in response to a jump list selection.

In the sample application, named pipes were used to handle the communications. This is by no means the only option available, but with .NET 3.5's new classes, using pipes is quite convenient. And once you write such code, you can easily use it in many different applications.

Jump lists in Windows 7 provide a quick way to let users access the main features of your application. Although Windows 7 is new, many applications, like Internet Explorer and Messenger already take full use of these new operating system features. So should you.

Happy shell development!
Jani JC$rvinen

About the Author

Jani Jarvinen is a software development trainer and consultant in Finland. He is a Microsoft C# MVP and a frequent author and has published three books about software development. He is the group leader of a Finnish software development expert group at ITpro.fi and a board member of the Finnish Visual Studio Team System User Group. His blog can be found at http://www .saunalahti.fi/janij/. You can send him mail by clicking on his name at the top of the article.

Resources

Windows API Code Pack for Microsoft .NET Framework
Microsoft Windows SDK for Windows 7
Develop for Windows 7



About the Author

Jani Jarvinen

Jani Jarvinen is a software development trainer and consultant in Finland. He is a Microsoft C# MVP, a frequent author and has published three books about software development. He is the group leader of a Finnish software development expert group at ITpro.fi and a board member of the Finnish Visual Studio Team System User Group. His blog can be found at http://www.saunalahti.fi/janij/. You can send him mail by clicking on his name at the top of the article.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • One of the most foolproof ways for an online system to confirm, "Is it really you?" is by adding two-factor authentication. Modern two-factor solutions have evolved to support new, complex technology models that change how we use data, including cloud computing and bring-your-own-device (BYOD). However, not all two-factor authentication solutions are created equal. This guide walks through some of the key area of differentiation between two-factor authentication solutions and provides some concrete criteria …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds