October 24, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

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

  • December 15, 2009
  • By Jani Järvinen
  • Send Email »
  • More Articles »

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

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.


Tags: API, Microsoft, Visual Studio, Jump Lists



Page 2 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel