Windows 7 タスク バー関連の開発 - ジャンプ リストの活用 (その 2)

みなさん、こんにちは。Windows 開発統括部の古内です。

昨日お約束したように、今日は Developing for Windows Blog に 2009 年 6 月 25 日に投稿された、Windows 7 タスク バーのジャンプ リストに関する記事 (3 部作) の 2 番目、 「Developing for the Windows 7 Taskbar – Jump into Jump Lists – Part 2」 の翻訳をお届けします。明日は Part 3 の翻訳を投稿します。


Windows 7 タスク バー関連の開発 - ジャンプ リストの活用 (その 2)

これまで Windows 7 タスク バーに関する記事をシリーズでお届けしてきましたが、今回はその中でもタスク バーのジャンプ リストに関する 2 回目の投稿です。前回のブログ記事 「Windows 7 タスク バー関連の開発 - ジャンプ リストの活用 (その 1)」 では、タスク バー ジャンプ リストの構成要素である移動先 (または 「名詞」) とタスク (または 「動詞」) について紹介し、その中で、開発者の皆さんがこうした要素を多岐にわたって制御できるという点をお伝えしました。今回は、タスク バー ジャンプ リストをプログラミングする際に使用できる、さまざまな API について説明したいと思います。

API の説明を始める前に、皆さんに理解していただきたい非常に重要なことをご説明します。それは、[最近使ったもの] カテゴリなど、すべてのカテゴリに含まれる項目 (移動先) について、対象のアプリケーションに対応するファイル ハンドラーをレジストリで登録する必要があるという点です。これは、対象アプリケーションを特定のファイル タイプの既定ハンドラーにしなければならないという意味ではなく、単にジャンプ リストで表示するすべてのファイルに対応するハンドラーを登録しておく必要があるという意味です。つまり、"項目" として表示できるのはファイルのみということです。覚えておいていただきたいのは、ジャンプ リストのいずれかの項目をクリックしたときに、アプリケーションとの関係性に基づいて、ファイルと対応付けられたコマンドが OS によって実行される点です。ファイル ハンドラーを登録する際には、対象のファイルを処理するアプリケーションを指定する以外に、そのアプリケーションに対して、入力されたパラメーターを渡す方法も定義する必要があります。もう 1 つ重要なのが、すべての項目 (ファイル) はローカルに配置されている必要があることです。つまり、ローカル ハード ドライブ上にあり、対象のアプリケーションからアクセス可能でなければなりません。まとめると、ジャンプ リストの移動先に含まれるすべての項目は、対象のアプリケーションにファイル ハンドラーを登録した、アクセス可能なローカル ファイルということになります。

一度ファイル ハンドラーを登録すると、実際には OS によってすべてのファイルが追跡されるようになります。これについては次のセクションで説明します。ファイル ハンドラーの登録については、次回の投稿で説明する予定です。

手順 1 - 標準の Windows エクスペリエンスと既定の動作の使用

既定では、ファイルベースのアプリケーションに対応する [最近使ったもの] カテゴリが、SHAddToRecentDocs 関数を通じてジャンプ リストに自動で作成されます。この関数によって、使用した "項目" (ファイル) がシェルの最近使用されたドキュメントの一覧に追加されます。シェルでは、最近使用したドキュメントの一覧の更新に加えて、ユーザーの Recent ディレクトリにショートカットが追加されます。Windows 7 タスク バーは、この一覧と Recent ディレクトリを使用して、ジャンプ リストに最近使用した項目の一覧を表示しています。

またこの処理は、アプリケーションのファイルの種類を登録することで、Windows に代行させることもできます。登録されたハンドラーを持ついずれかのファイルがダブル クリックされるたびに、Windows は対象のアプリケーションを起動する前に、アプリケーションに代わって自動的に SHAddToRecentDocs を呼び出します。これにより、その項目が Windows の最近使ったものの一覧に追加され、最終的にジャンプ リストの [最近使ったもの] カテゴリに表示されます。Windows 共通ファイル ダイアログ (CFD) を使用してマイクロソフトのアプリケーションでファイルを開くと、同様の自動処理が実行されます。Windows Vista タイムフレームに導入されたこの CFD を利用することは、この点からも有効であることがわかりますが、CFD はほかにもライブラリに関して非常に重要な役割を果たします。詳細については、以前投稿した記事「Windows 7 ライブラリの概要 (英語)」を参照してください。

これらのケースではいずれも、ハンドラーが登録済みであり、かつ "最近使ったもの" と "よく使うもの" の一覧とファイルを関連付けるために必要なアプリケーション ID が指定されている場合の Windows の既定の動作が活用されています。また、COM API を使用してこの機能を確実に削除しない限り、Windows によって項目が自動的にジャンプ リストに追加されます。もちろん、ユーザー自身がジャンプ リストから任意の項目を削除することも可能です。ジャンプ リストから明示的に削除した項目は、削除済み項目リストに追加されます。このリストについては後ほど説明します。

手順 2 - 独自のカテゴリの作成

既定の[最近使ったもの] カテゴリや [よく使うもの] カテゴリが開発中のアプリケーションのニーズに合わない場合は、独自にカスタム カテゴリを作成できます。これにはまず、ICustomDestinationList インターフェイスを使用してカスタムの移動先リストを作成する必要があります。

ICustomDestinationList は、移動先とタスクを含むアプリケーションのカスタム ジャンプ リストを作成し、タスク バーに表示するための各種のメソッドを提供します。下の例で使用しているメソッドは次のとおりです。

  • AppendCategory: カスタム カテゴリ、およびそこに含まれるカスタム ジャンプ リストに表示する移動先を定義
  • AppendKnownCategory: [よく使うもの] カテゴリや [最近使ったもの] カテゴリをジャンプ リストに含めるかどうかを指定
  • BeginList: カスタム ジャンプ リストの作成セッションを開始
  • CommitList: BeginList の呼び出しによって開始したジャンプ リストの作成が完了し、表示可能となったことを宣言

次のコード スニペットは、"Custom Lists" という新たなカスタム リストを作成し、いくつかの項目を追加する方法を示しています。

 void CreateJumpList()

{    

    ICustomDestinationList *pcdl;

    HRESULT hr = CoCreateInstance(
                    CLSID_DestinationList, 
                    NULL, 
                    CLSCTX_INPROC_SERVER, 
                    IID_PPV_ARGS(&pcdl));
    if (SUCCEEDED(hr))
    {
        //ジャンプ リストの App ID を設定する際に重要
        hr = pcdl->SetAppID(c_szAppID);
        if (SUCCEEDED(hr))
        {
            UINT uMaxSlots;
            IObjectArray *poaRemoved;
            hr = pcdl->BeginList(
                            &uMaxSlots, 
                            IID_PPV_ARGS(&poaRemoved));
            if (SUCCEEDED(hr))
            {
                hr = _AddCategoryToList(pcdl, poaRemoved);
                if (SUCCEEDED(hr))
                {
                    pcdl->CommitList();
                }
                poaRemoved->Release();
            }
        }
    }
}

ご覧のとおり、最初に標準的な COM の初期化を呼び出しています。この例では、CoCreateInstance を呼び出して ICustomDestinationList オブジェクトを初期化しています (これが COM の醍醐味とも言えるところです)。次に、項目をこのリストに追加できるようにするために、アプリケーション ID を設定しています。

BeginList 関数により、カスタム ジャンプ リストの作成セッションが開始されます。この関数は、所定のジャンプ リストに適した最大項目数を返します。既定値は 10 です。項目の削除パラメーターである IObjectArray *poaRemoved が、BeginList() によって out パラメーターとして返されています。これにより、現在のセッションで、ユーザーがジャンプ リストから削除した特定の項目がすべて保持されます。この削除済み項目リストについては、後ほど説明します。

次に、ヘルパー関数の _AddCategoryToList() を呼び出し、実際にカスタム カテゴリに項目を追加する作業を実行します。

 // 実際にコレクションに項目を追加するヘルパー関数
// object HRESULT _AddCategoryToList(ICustomDestinationList *pcdl,
// IObjectArray *poaRemoved)
{
    IObjectCollection *poc;
    HRESULT hr = CoCreateInstance
                    (CLSID_EnumerableObjectCollection, 
                    NULL, 
                    CLSCTX_INPROC_SERVER, 
                    IID_PPV_ARGS(&poc));
    if (SUCCEEDED(hr))
    {
        for (UINT i = 0; i < ARRAYSIZE(c_rgpszFiles); i++)
        {
            IShellItem *psi;
            if (SUCCEEDED(SHCreateItemInKnownFolder(
                                FOLDERID_Documents, 
                                KF_FLAG_DEFAULT, 
                                c_rgpszFiles[i], 
                                IID_PPV_ARGS(&psi)))
                )
            {
                if(!_IsItemInArray(psi, poaRemoved))
                {
                    poc->AddObject(psi);
                }

                psi->Release();
            }
        }

        IObjectArray *poa;
        hr = poc->QueryInterface(IID_PPV_ARGS(&poa));
        if (SUCCEEDED(hr))
        {
            pcdl->AppendCategory(L"Custom category", poa);
            poa->Release();
        }
        poc->Release();
    }
    return hr;
}

もう 1 つの新しいインターフェイスとして、IObjectCollection を使用しています。これは、IUnknown をサポートするオブジェクトのコレクションを表します。このコレクションには、IShellItem が追加されます。ここでは、各項目 (ファイル) をジャンプ リストに追加するために IShellItem 形式を使用しました。上記のコードを実行すると、既知のフォルダー (Documents) 内にあるファイルごとに、シェル項目オブジェクトが作成されます。ただし、実際にコレクションに新たな項目を追加する前に、対象の項目をユーザーが既に削除していないかどうか判断する必要があります。ユーザーがジャンプ リストから明示的に項目を削除した場合、その項目は "削除済み項目リスト" (これも AppID と関連付けられている) 内に入れられており、開発者としては、ユーザーの要求を尊重して対象の項目をジャンプ リストに追加しないようにする必要があります。この削除済み項目のリスト IObjectArray *poaRemoved は、新しいリストの作成を開始する目的で既に BeginList(...) 関数を呼び出した際に取得されています。

この段階で、ユーザーがジャンプ リストへの表示を希望するシェル項目のコレクションの準備ができました。次にそのコレクションを ICustomDestinationList オブジェクトに追加し、"Custom category" という名前の新しいカテゴリを作成するために、pcdl->AppendCategory (L"Custom category", poa); を実行します。

これでタスク バーに "Custom category" という新しいカテゴリが作成され、そのカテゴリ内に 4 つの項目が追加されました。しかしながら、処理はこれで終わりではありません。CreateJumpList 関数の最後のステップとして、CommitList() を呼び出し、BeginList() の呼び出しから開始された "トランザクション" を終了します。CommitList() を呼び出すことで初めて、新しいカテゴリと新しい項目が表示されるようになります。Calling CommitList() を実行すると、保存してあった削除済み項目のリストがクリアされ、削除済み項目リストが新たに開始されます。ICustomDestinationList インターフェイスは、"トランザクション ベース" の API を提供します。

エンド ユーザーの快適なエクスペリエンスを保証するために、新たに入力されたリストの正常なファイルが完備かつすぐに利用できる状態になっていること、そしてタスク バーでは新しいリストにポインターを切り替えるだけで済むようになっていることを確認してください。最終的には、次のような状態になります。

イメージ

Windows API Code Pack を使用すると、同じアプリケーションをマネージ コードを使用して記述できます。

ここでは、すべてのタスク バー要素で同じ AppID を使用するので、次のコード スニペットにより、作業中のボタンに関してタスク バー ジャンプ リストのインスタンスを作成できます。このコード スニペットは、メイン アプリケーション ウィンドウの CTOR の一部です。

 // アプリケーション固有の ID を設定
Taskbar.AppId = appId;
// タスク バー ジャンプ リストを取得
jumpList = Taskbar.JumpList;
category1 = new CustomCategory("Custom Category 1");
category2 = new CustomCategory("Custom Category 2");
// カスタム カテゴリを追加
jumpList.CustomCategories.Add(category1);
jumpList.CustomCategories.Add(category2);
// ジャンプ リストの既定値
comboBoxKnownCategoryType.SelectedItem = "Recent";

上記のコードでは、AppId プロパティを使用して AppID を設定し、静的プロパティの Taskbar.JumpList を使用して、タスク バー ジャンプ リストのインスタンスを作成しています。また、Custom Category 1 および Custom Category 2 という 2 つのカテゴリを作成した後、それらのカテゴリをジャンプ リストのカスタム カテゴリ コンテナーに追加しました。最後に、このタスク バー ジャンプ リストの既知のカテゴリを Recent (最近使ったもの) に設定しています。このカテゴリは、既に説明した要領で自動的に構成されます。

カスタム カテゴリのセットアップが完了したら、次はそこにコンテンツを格納します。これには、Add 関数を呼び出して、JumpListItemJumpListCollection に追加すれば済みます。JumpListItemCollection は (<IJumpListItem> の) 汎用のコレクションで、IJumpListItem 項目を保持します。IJumpListItem 項目は基本的に、ネイティブ IShellItem のいずれかの種類のラッパーです。

 // シェル項目へのパスを指定
string path = String.Format("{0}\\test{1}.txt",
                            executableFolder,
                            category1.JumpListItems.Count);
// カスタム カテゴリへのシェル項目を追加
category1.JumpListItems.Add(new JumpListItem(path));

最初に、ジャンプ リストに表示するファイルへのパスを設定する必要があります。Add 関数を呼び出せるのは、このファイルがローカルにあり、ユーザーによるアクセスが可能な場合のみです。上記のコード (および、今後の投稿で説明する予定のいくつかのメソッド) により、次のようなタスク バー ダイアログが表示されます。

イメージ

最後に、Taskbar.JumpList.RefreshTaskbarList() 関数を呼び出す必要があります。これは、ネイティブ実装のジャンプ リストと同様に、ジャンプ リストに対して行った変更の "確定" が必要となるためです。この Refresh 関数 (Code Pack API 内に含まれている) を見てみると、AppendCustomCategories 関数を呼び出すことで、すべてのカスタム カテゴリがタスク バー ボタンのジャンプ リストに付加されています。この関数内では、先ほどのネイティブ コードがマネージ コードとして実装されていることがわかります。さらにこの関数では、前述の AppendCategory ネイティブ関数のラッパーとして、AppendCateogry 関数が呼び出されます。

 IObjectCollection categoryContent =
    (IObjectCollection)new CEnumerableObjectCollection();

// オブジェクト配列に、各リンクのシェル表現を追加
foreach (IJumpListItem link in category.JumpListItems)
    categoryContent.AddObject(link.GetShellRepresentation());

// 現在のカテゴリを移動先リストに追加
HRESULT hr = customDestinationList.AppendCategory(
    category.Name,
    (IObjectArray)categoryContent);

このように、Windows 7 のタスク バー機能は簡単に導入できます。ほとんどの処理は Windows によって自動的に実行され、独自のカテゴリを作成する必要がある場合でも、作業はとても簡単です。

次回の投稿では、ジャンプ リストに新しいタスクを追加する方法と、ファイル ハンドラーを登録する方法について説明します。