Creating Windows 7 Jump Lists With The API Code Pack and Visual Studio 2008, Page 2
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.

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();
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.

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.
