japan.internet.com
japan.internet.com メンバーID
Twitter
Facebook
RSS
ピックアップ
2010年3月16日 10:00

API Code PackとVisual Studio 2008でWindows 7のジャンプリストを作成する

著者Jani Jarvinenオリジナル版を読む海外海外発

はじめに

 Windows 7が全国の販売店に出回るようになった今、この新しいオペレーティングシステムが家庭でも企業でも主流になるのは時間の問題です。特に、新しいコンピュータへの買い換えが進めばその流れにはずみが付くでしょう。

 開発者の観点からすると、Windows 7はさまざまな可能性をもたらします。すぐに気が付く目立った新機能の1つが、進化したタスクバーです。タスクバーからはアプリケーションの起動だけでなく、管理もできるようになっています。本稿では、ジャンプリストと呼ばれる機能に焦点を当てます。ジャンプリストは、タスクバーのアイコンを右クリックすると表示されるミニ[スタート]メニューと考えることができます(図1)。

図1 基本的なジャンプリスト
図1 基本的なジャンプリスト
 ジャンプリストは、タスクバーのアイコンを右クリックしたときだけでなく、[スタート]メニューの初期画面でアプリケーションアイコンをポイントしたときにも表示されます(図2)。つまり、ジャンプリストを表示する方法は2種類あります。ただし、メニューに表示される項目が異なります。[スタート]メニューのジャンプリストには、最近使ったドキュメントが表示されます。一方、タスクバーから開いたジャンプリストには、アプリケーションとそのウィンドウを管理するためのオプションも表示されます。

図2 [スタート]メニューのジャンプリスト
図2 [スタート]メニューのジャンプリスト
 この2種類のジャンプリストは、1つの実装で実現できます。Windowsシェルの他の多くの機能と同様に、ジャンプリストも舞台裏でCOMベースのAPIを使用しています。これらのCOMインターフェースにアプリケーションから直接アクセスすることも可能ですが、もっと簡単な方法があります。それは、Microsoftが.NET開発者向けに提供しているWindows API Code Pack(図3)という無料パッケージを利用することです。このパッケージには、Windows 7のジャンプリストなど、Windowsの機能にアクセスするための既製のクラスが数多く含まれています。

図3 Windows API Code Packのダウンロードページ
図3 Windows API Code Packのダウンロードページ
 以降では、WinFormsサンプルアプリケーション(図4)を例に、Visual Studio 2008とC#を使って独自の.NETアプリケーションでカスタムジャンプリストを有効にする方法を見ていきます。Visual Studio 2010 Beta 2でも同様の方法を使うことができます。ただし、Visual Studio 2010では、WPF 4.0のWindows 7シェル統合の新しいサポートを使うこともできます。それらのクラスは本稿で扱う内容の対象外ですが、近いうちに別の記事で取り上げる予定です。

図4 サンプルアプリケーション
図4 サンプルアプリケーション

ジャンプリストの機能の概要

 Windows 7のジャンプリストは、Windowsの簡素化されたミニバージョンの[スタート]メニューと考えることができます。しかも、ジャンプリストの内容はアプリケーションごとに変化します。Windows 95以降のすべてのバージョンのWindowsと同様に、タスクバーのアイコンを右クリックするとポップアップメニューが表示されます。Windows 7のジャンプリストは、このメニューを進化させたものです。

 実際のところ、何も特別なことをしなくても、Windows 7ではタスクバーにアプリケーションのアイコンがあれば、そのアプリケーションに対応するポップアップメニューが表示されます(図1を再度参照)。アプリケーションをタスクバーに常に表示する(固定する)ように設定してある場合、デフォルトのメニューにはその設定を解除するオプションや、アプリケーションを起動するオプションが表示されます。アプリケーションが実行中の場合、デフォルトのメニューには、プログラムをタスクバーに常に表示するオプション、ウィンドウを閉じるオプション、または別のインスタンスを起動するオプションが表示されます。

 レジストリでアプリケーションに特定のファイルの種類(ファイルの拡張子)が関連付けられている場合(例えばWordなら.doc、.docxなど)、Windowsはそのアプリケーションで最近開かれたファイルのリストを収集します。この処理は、アプリケーションでWindowsのコモンファイルダイアログを使ってファイルを開いたとき、またはAPI関数SHAddToRecentDocsによってファイルのオープンがWindowsに通知されたときに自動的に行われます。また、使用頻度が非常に高いファイルもWindowsによって追跡されています。

 アプリケーションでは2種類の既知のリストからファイルを開くことができます。1つは「最近使ったファイル」のリスト、もう1つは「頻繁に使われているファイル」のリストです。両者の違いは、「最近使ったファイル」のリストには最近開いたファイルが(例えば5個)表示されるのに対し、「頻繁に使われているファイル」のリストには過去に最も頻繁に使用したファイル群が表示されることです。どちらのリストをジャンプリストに表示するか、あるいはどちらも表示しないかは、アプリケーションで制御できます。

 これらの既知のリストには、目的地(名詞)が表示されます。目的地は常にファイルです。従って、そのファイルの種類に対応するファイルハンドラが登録されている必要があります。Windowsは、ファイルをリストに表示する前に、ファイルが利用可能かどうかも確認します。

 ジャンプリストのもう1つの重要な部分であり、本稿の主題となっているのが、動詞のサポートです。動詞を使うと、アプリケーションでジャンプリストにカスタムコマンドを自由に追加することができ、ユーザーは実行中のアプリケーションをすばやく操作できるようになります。名詞と異なり、動詞ではファイルの拡張子をアプリケーションに関連付ける必要はありません。

 動詞はアプリケーションの機能を呼び出すファイルベースのコマンドで、特定のコマンドを実行するようにアプリケーションに指示します。例えば、販売アプリケーションを作成した場合に、ジャンプリストの動詞で新しい注文を開始することや、顧客を検索することなどができます。これらのリストはアプリケーションごとにカスタマイズされるので、すべてのアプリケーションが独自の動詞のセットを持つことになります。

Windows API Code Packの使用

 自由にダウンロードできるWindows API Code Packは、さまざまな種類のアセンブリにコンパイルされた.NETクラスのセットです。各自のプロジェクトでこれらを簡単に参照し、クラスを利用できます。

 Code Packを使用するには、まずファイルをダウンロードする必要があります。Code Packのファイルは通常のzip形式なので、Visual Studioプロジェクトフォルダの下など、使いやすい場所に展開します。次に、展開先のフォルダ内に移動し、Visual StudioでShell.slnという名前のソリューションを開きます。ソリューション全体をビルドし、生成されたDLLファイルの場所を覚えておきます。通常はbin\Debugフォルダの下に生成されます。

 ビルドが正常に終了すると、出力フォルダにMicrosoft.WindowsAPICodePack.Shell.dllとMicrosoft.WindowsAPICodePack.dllの2つのアセンブリが作成されます。後者のファイルは自動的に作成されます。ShellプロジェクトはCoreというプロジェクトに依存しているからです。CoreプロジェクトもCode Packの一部です。

 次に、Visual Studioで新しいプロジェクトを開始し(または既にプロジェクトがある場合はそれを開き)、Code Packの2つのDLLをプロジェクトへの参照として追加します(図5)。適切な参照を設定したら、コードの作成を開始できます。アセンブリのファイル名が適切な名前空間を暗に示しています。Code Packをジャンプリストに使用するには、次のusingステートメントをC#コードに追加します。

using Microsoft.WindowsAPICodePack.Shell;
using Microsoft.WindowsAPICodePack.Taskbar;
図5 サンプルプロジェクトを参照先にする
図5 サンプルプロジェクトを参照先にする
 後者の名前空間内に、JumpListという名前のクラスがあります。ジャンプリストの機能を見て回るうえで、このクラスが有効な出発点になります。例えば、ジャンプリストのカスタム動詞をアプリケーションのメニューに追加する方法を見てみましょう。

 最初に、JumpListクラスのインスタンスを生成します。その後で、ジャンプリストに追加するカスタム動詞ごとに、IJumpListTaskインターフェースをサポートするオブジェクトを作成する必要があります。これを行うには、JumpListLinkクラスを利用します。このクラスもMicrosoft.WindowsAPICodePack.Taskbar名前空間に属しています。

 例えば、メモ帳や電卓をすばやく起動するコマンドをアプリケーションのジャンプリストに追加するとします。これを行うには、次のようなコードを使います。

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!");
 ここでは、新しいジャンプリストオブジェクトを生成し、JumpListLinkクラスを使って2つの動詞を作成しています。コンストラクタにはファイルベースのコマンドが必要で、この例ではWindows\System32ディレクトリにあるNotepad.exe(およびCalc.exe)のフルパス名がそのコマンドになります。この処理にはSystem.IO.Pathクラスを利用しています。また、IconReferenceクラスを使ってメニューアイコンを追加しています。このクラスには、フルパスとアイコンインデックスを指定します。0はファイル内の最初のアイコンを意味し、通常はこれがメインアイコンになっています。

 最後に、JumpListクラスのAddUserTasksメソッドを呼び出して、メニューに新しい動詞を追加しています。コマンドは[タスク]汎用グループの下に作成されるので、ここに追加される動詞はタスクと呼ぶこともできます。作成したタスクの記録はWindowsに保持されます。従って、タスクは一度作成するだけで済みます。

独自アプリケーションのコマンドの呼び出し

 ジャンプリストから外部アプリケーションを起動することは簡単ですが、ジャンプリストを使って独自アプリケーションのコマンドを呼び出したい場合はあまり役に立ちません。現在のところ、ジャンプリストのコマンドはディスク上のファイル、つまりハンドラアプリケーションが登録されたドキュメントファイル、またはオプションのコマンドラインパラメータが指定された実行可能ファイルを参照している必要があります。

 つまり技術的に言うと、現在実行中のアプリケーションインスタンスにコマンドを実行させるには、ジャンプリストを表示するアプリケーションインスタンス(「サーバ」)と、ジャンプリストからコマンドが選択されたときにWindowsが起動するアプリケーションインスタンス(「クライアント」)の間に何らかのプロセス間通信(IPC)を実装する必要があるということです。

 例えば、アプリケーションに2つのカスタムコマンドを実装する必要があるとします。話を単純にするために、コマンドの名前をCommand AおよびCommand Bとします。これらのコマンドに対応するジャンプリストのタスクを作成するには、実行可能ファイルへのフルパスを作成し、さらにどのコマンドが実際に選択されたかをアプリケーションに通知するコマンドラインパラメータを後ろに追加する必要があります。例えば、パスはC:\MyApps\Win7JumpListDemo.exeのようになります。

 このパスを使ってWindowsは実行可能ファイルを起動し、指定されたパラメータを渡します。アプリケーションはコマンドラインパラメータが存在するかどうかをチェックし、正しいパラメータが指定されている場合は、既に実行中のインスタンスにパラメータを通知して直ちに終了します。

 最初に、独自アプリケーションのコマンドを呼び出すジャンプリストのタスクを作成する方法を見てみましょう。このコードは以前に示したコードと似ています。

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);
 これらのタスクを追加すると、ジャンプリストは図6のように表示されます。アプリケーションインスタンスへのパスはSystem.Environment.CommandLineプロパティから読み込まれますが、このパスは引用符で囲まれています。ジャンプリストのタスクが正しく処理されるように、引用符を削除する必要があります。そうしないと、アプリケーションが起動するときにエラーが表示されます。

図6 アプリケーションのジャンプリストにカスタムタスクが追加されている
図6 アプリケーションのジャンプリストにカスタムタスクが追加されている
 もう1つの重要な部分は、Argumentsプロパティを使ってコマンドラインパラメータを指定することです。本稿の執筆時点では、Windows API Code PackのドキュメントファイルにはArgumentsプロパティに関する記述がまったくありません。それでも、コードからこのプロパティを利用することは可能です。

 ユーザーがアプリケーションのジャンプリストから[Command A]を選択すると、Windowsは次のコマンドを(もちろん実際のランタイムパスを使って)実行します。

C:\MyApps\Win7JumpListDemo.exe Command-A
 アプリケーションの起動時に、パラメータをチェックする何らかのコードがProgram.csファイルに必要となります。サンプルアプリケーションでは、アプリケーションのmainメソッドが次のようになっています。

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());
  }
}
 CommandLineParametersという名前のカスタムクラスを使って、コマンドラインパラメータがあるかどうかをチェックしています。パラメータがある場合は、同じクラスでパラメータを処理します。パラメータがない場合は、アプリケーションが普通に起動して、ユーザーインターフェースが表示されます。

パイプを使った通信

 同一マシン上で実行されている2つのアプリケーション間でメッセージのやり取りが必要になることはよくあります。Windowsではこれをプロセス間通信(IPC)と呼んでおり、IPCを実装する方法がいくつか用意されています。例えば、メモリマップドファイル、TCP/IPソケット、または名前付きカーネルオブジェクトを使うなどの方法があります。また、いわゆる名前付きパイプを使う方法もあります。名前付きパイプは、セキュリティやファイアウォールの設定をいじらずにメッセージを送信できる便利で直接的な方法です。.NETのパイププログラミングも、.NET 3.5の新しいSystem.IO.Pipes名前空間によってさらに簡単になっています。

 サンプルアプリケーションでは、パイプが次のように通信に使われています。サンプルアプリケーションがパラメータなしで起動されると、アプリケーションは普通にユーザーインターフェースを開いて、パイプ接続を待機するスレッドを開始します。

 一方、ユーザーがジャンプリストからコマンドを選択すると、アプリケーションはコマンドラインパラメータ付きで起動されます。この場合、アプリケーションは既存のサーバーアプリケーションのパイプへのパイプ接続を開き、既に実行中のインスタンスにコマンドラインパラメータを送信します。これを受けて、サーバはコマンドに対応する処理を自由に実行できます。

 パイプ処理はPipeCommunicationsカスタムクラスに実装されています。本稿の主題はジャンプリストであって、パイプ通信ではありませんが、ジャンプリストを操作するときにIPCが必要になる場面が多いので、コードをひととおり見ておいて損はありません。サンプルアプリケーションがパラメータなしで起動されると、メインウィンドウのコンストラクタが次のコードを実行します。

PipeCommunications pipes = new PipeCommunications(
  (cmd) =>
  {
     communicationLogTextBox.Invoke(
     new LogMessageToTextBoxDelegate(
        LogMessageToTextBox),cmd);
   });
pipes.CreateListenPipeThread();
 ここでは、パイプ経由で受け取ったコマンドを実際に処理するデリゲートを指定して、PipeCommunicationsクラスを初期化しています。パイプ通信は独立したスレッドで行われるので、スレッド処理の問題を起こさずにユーザーインターフェース要素に正しくアクセスするために、WinFormsコントロールのInvokeメソッドを呼び出す必要があります。ラムダ式を使ってイベントハンドラを作成しています。実際の処理はメインフォームのLogMessageToTextBoxメソッドで行われます。

public delegate void LogMessageToTextBoxDelegate(string cmd);
...
private void LogMessageToTextBox(string cmd)
{
 communicationLogTextBox.Text =
  DateTime.Now.ToLongTimeString() +
  ": Got command: " + cmd + "\r\n" +
  communicationLogTextBox.Text;
}
 この実装では、単に受け取ったコマンドを画面上に記録するだけですが(図7)、独自アプリケーションで何らかの処理を実際に行うことも簡単にできます。例えば、データベースにアクセスしたり、ドキュメントを印刷したり、画面上にダイアログボックスを開いたりできます。

図7 サーバーアプリケーションがパイプ経由でコマンドを受け取っている
図7 サーバーアプリケーションがパイプ経由でコマンドを受け取っている
 メインフォームのコンストラクタによって呼び出されるCreateListenPipeThreadメソッドは、次のコードを実行します。

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;
}
 このコードは、サーバ側のパイプ通信を処理する新しいスレッドを作成します。また、自動リセットイベントを1つ作成します。アプリケーションが終了しようとするときに、Program.csファイルによってこのイベントが通知(設定)されます。このようなイベントを使わないと、メインフォームは正常に閉じても、パイプスレッドが実行されたままになります。従って、アプリケーションを完全に閉じるためにスレッドを正しく終了する必要があります。

 サーバ側のパイプ通信の実際の処理は、次の2つのメソッドで行われます。

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();
  }
}
 このコードはNamedPipeServerStreamクラスのインスタンスを生成し、接続を非同期に待機します。待機は、パイプ接続とアプリケーション終了イベントの両方に対して同時に行われます。どちらが先に発生するかによって、パイプスレッドがそのまま終了するか、受け取ったパイプコマンドの処理が(ProcessPipeCommandメソッドで)開始されるかが決まります。待機は、System.Threading名前空間に属するWaitHandleクラスを使って行われます。

 次に、クライアントアプリケーションの動作を見てみましょう。コマンドラインパラメータ付きで(つまり、ジャンプリストのタスク経由で)アプリケーションが起動されると、次のコードが実行されます。

internal static void ProcessCommandLine(string[] args)
{
    string command = args[0];
    PipeCommunications pipes = new PipeCommunications();
    string result = pipes.SendCommandToServer(command);
}
 ここでは、同じPipeCommunicationsクラスのインスタンスを生成していますが、これはクライアント側のパイプの実装なので(データはアプリケーションから既に実行中のアプリケーションインスタンスに流れる)、PipeCommunicationsクラスにデリゲートを指定する必要はありません。

 パイプ経由のコマンドの送信は、SendCommandToServerメソッドで実装されます。

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;
  }
}
 コマンドラインパラメータ(サンプルアプリケーションの場合は「Command-A」または「Command-B」)がパイプに送信され、サーバからの「OK」という応答で受信が確認されます。これが終わると、クライアントはそのまま終了します。

まとめ

 本稿では、Visual Studio 2008と、新たにリリースされたWindows API Code Packを使って、Windows 7の新機能を有効に活用する方法を説明しました。本稿に付属するサンプルアプリケーションを例に、C#を使って独自アプリケーションにジャンプリストの機能を実装しながら、Windows 7のユーザーにオペレーティングシステムの新機能を活用してもらうための方法を紹介しました。

 Windows API Code Packのユーティリティクラスを利用すると、タスクバーの操作や、独自アプリケーションのメニューの制御を簡単に行うことができます。ただし、アプリケーションのコマンドを呼び出すには、少々手間をかける必要があります。現在実行中のアプリケーションインスタンスと、ジャンプリストの選択を受けてWindows 7が起動するアプリケーションインスタンスの間の通信チャネルを実装する必要があるからです。

 サンプルアプリケーションでは、通信の処理に名前付きパイプを使っています。もちろん名前付きパイプ以外にも選択肢はありますが、.NET 3.5の新しいクラスではパイプを使うと非常に便利です。そうしたコードを一度書いておけば、さまざまなアプリケーションで簡単に利用できます。

 Windows 7のジャンプリストは、アプリケーションの主要な機能にすばやくアクセスするための手段です。Windows 7は登場したばかりですが、Internet ExplorerやMessengerをはじめ、このオペレーティングシステムの新機能を最大限に活用するアプリケーションが既に数多く作成されています。あなたも乗り遅れるわけにはいきません。

 それではどうぞ楽しいシェル開発を!

参考資料

著者紹介

Jani Jarvinen(Jani Jarvinen)
フィンランドのソフトウェア開発トレーナー兼コンサルタント。Microsoft C# MVPの受賞者で、投稿も多数。ソフトウェア開発に関する著書も3冊出版している。ITpro.fiというフィンランドのソフトウェア開発エキスパートグループのグループリーダー。ブログのアドレスはhttp://www.saunalahti.fi/janij/

関連テーマ
プリンター用
記事を転送
この記事をクリップ!
厳選した九州のお野菜とお米をお届け
厳選した九州のお野菜とお米をお届け 野菜の木では、老舗料亭 沙羅の木が厳選した九州のお野菜とお米をお届けします。 毎週、隔週での定期のご購入も可能です。 入会費、年会費、送料、荷造手数料は無料です。
注目のトピックス
Copyright 2012 internet.com K.K. (Japan) All Rights Reserved.