第 13 章: Hilo Browser ユーザー インターフェイスの強化

Hilo の最終バージョンの Annotator および Browser アプリケーションでは、強化されたユーザー インターフェイス (UI) 機能が多数提供されます。たとえば、Hilo Browser では、Annotator アプリケーションを起動するボタン、Flickr を通じて写真を共有するためのボタン、および画像のパンやズームを実行するためのタッチ スクリーン ジェスチャが利用できるようになりました。この章では、これらの機能がどのように実装されているかを説明します。

[Share] ダイアログと Hilo Annotator の起動

Hilo Browser アプリケーションでは、オンライン写真共有アプリケーションを通じて、選択した写真を共有することが可能になりました。また、Hilo Annotator アプリケーションを起動して、選択した写真を編集することもできます。Hilo Browser では、これら 2 つのアクションをより簡単に実行できるよう、機能が拡張されています。Browser の最初のバージョンでは、写真をダブルクリック (またはダブルタップ) すると、Annotator が起動され、写真の編集ができるようになっていました。現在の Browser では、ダブルクリック ジェスチャによりスライドショー モードが起動されます。このモードでは、カルーセルは非表示になり、選択した写真がより大きなサイズで表示されます (図 1)。

図 1 Browser のスライドショー モード

Gg248103.00319394-782a-45fd-b2ea-c9603acb8dbb-thumb(ja-jp,MSDN.10).png

Hilo Browser には 2 つのボタンが表示されます。1 つは [Share] ダイアログを起動するボタンで、もう 1 つは Hilo Annotator アプリケーションを起動するボタンです (図 2)。通常、この 2 つのボタンはアイコンとして Browser の右上隅に表示されますが、マウス カーソルでアイコンをポイントすると、ボタンの境界線とキャプションが表示されます。この 2 つのボタンはボタン コントロールでも子ウィンドウでもありません。これらは Direct2D ビットマップ 画像であり、ヒット テストを介してクリック アクションが実行されます。

図 2 Annotator と [Share] ダイアログの起動ボタンが表示された Hilo Browser 画面

Gg248103.2dec7487-4e5d-44bd-b553-7f6b099e90aa-thumb(ja-jp,MSDN.10).png

Browser のクライアント領域の上半分は CarouselPaneMessageHandler クラスによって実装されています。このクラスには、m_annotatorButtonImage および m_sharingButtonImage という 2 つのメンバーがあります。これらは ID2D1Bitmap インターフェイスを実装するビットマップ オブジェクトへの参照です。どちらのメンバーも、CarouselPaneMessageHandler::CreateDeviceResources メソッドで、Browser プロセスにバインドされたビットマップ リソースから画像を読み込むことによって初期化されます (図 3)。

図 3 Browser のボタンのアイコン

Gg248103.22b0dcc7-c617-4fb0-a4f4-253117089bba(ja-jp,MSDN.10).png Gg248103.ee544bf8-bb85-4051-87ab-3e73732e44b5(ja-jp,MSDN.10).png

ウィンドウのサイズが変更されると、メッセージ ハンドラーが CarouselPaneMessageHandler::CalculateApplicationButtonRects メソッドを呼び出して、各ボタンの画像の位置と、選択を示す四角形の位置を特定します。その後、ウィンドウの再描画が実行されます。CarouselPaneMessageHandler::DrawClientArea メソッドが呼び出され、ID2D1RenderTarget::DrawBitmap メソッドにより、計算された位置に画像が描画されます。ただし、画像自体で、ユーザーのフィードバックを表示したり、マウスのクリックを処理したりするわけではありません。

ユーザーのフィードバックは、マウスがボタン上に置かれているかどうかがテストされた後、選択範囲が描画されることにより提供されます。以下の説明は [Annotator] ボタンに関するものですが、[Share] ボタンについても同様です。最初のアクションは、マウスがボタンの上に置かれているかどうかのテストです。このアクションは、マウス移動メッセージに応答して、CarouselPaneMessageHandler::CheckForMouseHover メソッドで実行されます。CheckForMouseHover のコードをリスト 1 に示します。このコードは、マウスの位置がボタンの選択範囲の内部にあるかどうかを簡単にテストする Direct2DUtility::HitTest メソッドを呼び出します。このヒット テストの結果はブール変数 m_isAnnotatorButtonMouseHover に保存され、このコードの後の部分で使用されます。

リスト 1 [Annotator] ボタンのヒット テスト

if (Direct2DUtility::HitTest(m_annotateButtonSelectionRect.rect, mousePosition))
{
   if (!m_isAnnotatorButtonMouseHover)
   {
      needsRedraw = true;
      m_isAnnotatorButtonMouseHover = true;
      CalculateApplicationButtonRects();
   }
}
else
{
   if (m_isAnnotatorButtonMouseHover)
   {
      needsRedraw = true;
      m_isAnnotatorButtonMouseHover = false;
      CalculateApplicationButtonRects();
   }
}

カルーセル ウィンドウが再描画されるときには、m_isAnnotatorButtonMouseHover メンバー変数がチェックされ、マウスがボタン上にあることが示された場合、選択範囲の境界線が純色で表示され、四角形の内部は不透明度が 25% に設定された同じ色で塗りつぶされます (リスト 2 を参照)。マウスがボタンから離れたときにこの四角形を削除するコードはありません。この場合は、何も選択されていない状態のカルーセル ウィンドウ全体が再描画されます。

リスト 2 ボタン 画像の周囲に選択ボックスを描画するコード

// [Annotator] ボタンの選択ボックスを描画する
if (m_isAnnotatorButtonMouseHover)
{
   m_selectionBrush->SetOpacity(1.0f);
   m_renderTarget->DrawRoundedRectangle(m_annotateButtonSelectionRect, m_selectionBrush);

   m_selectionBrush->SetOpacity(0.25f);
   m_renderTarget->FillRoundedRectangle(m_annotateButtonSelectionRect, m_selectionBrush);

   m_renderTarget->DrawTextLayout(
      D2D1::Point2(m_annotateButtonSelectionRect.rect.left, m_annotateButtonSelectionRect.rect.bottom),
      m_textLayoutAnnotate,
      m_fontBrush);
}

m_renderTarget->DrawBitmap(m_annotatorButtonImage, m_annotateButtonImageRect);

マウスのクリックも同様に処理されます。リスト 3 に、WM_LBUTTONUP マウス メッセージのハンドラーの一部を抜粋しています。Browser の履歴スタックが展開されている場合、ボタンのクリックは無視されますが、履歴スタックが展開されていない場合は、マウスがいずれかのボタンの上にあるかどうかがチェックされます。マウスがボタン上にある場合は、MediaPaneMessageHandler::LaunchAnnotator メソッドまたは MediaPaneMessageHandler::ShareImages メソッドが呼び出されます。

リスト 3 Browser のボタンに対するマウス クリックの処理

if (!clickProcessed)
{
   if (m_isHistoryExpanded)
   {
// 他のコード
   }
   else
   {
// ユーザーが [Share] ボタンまたは [Annotator] ボタンをクリックしたかどうかをチェックする
      if (m_isAnnotatorButtonMouseHover || m_isSharingButtonMouseHover)
      {
         ComPtr<IMediaPane> mediaPane;
         hr = m_mediaPane->QueryInterface(&mediaPane);

         if (SUCCEEDED(hr))
         {
            if (m_isAnnotatorButtonMouseHover)
            {
               mediaPane->LaunchAnnotator();
            }
            else
            {
               mediaPane->ShareImages();
            }
         }
      }
   }
}

MediaPaneMessageHandler::LaunchAnnotator については、既にこちらの章で説明しました (Windows API 関数 CreateProcess を呼び出して Annotator アプリケーションを起動する単純なメソッドです)。写真を共有するためのコードは、Browser に実装されています。MediaPaneMessageHandler::ShareImages メソッドは、ShareDialog クラスの静的メソッドを呼び出して、モーダル ダイアログを表示します。ShareDialog クラスについては、第 15 章で説明します。

タッチ スクリーン ジェスチャ

Windows 7 では、タッチ スクリーン コンピューターでのジェスチャをサポートしています。ジェスチャとは、タッチ スクリーン上での 1 本または複数の指の動きです。マウス操作に対応するジェスチャも一部ありますが、マルチタッチ スクリーンは 2 本指の動きへの応答も可能であるため、マウスでは再現できないジェスチャもあります。

タッチ スクリーン ジェスチャは、WM_GESTURE メッセージによってアプリケーションに渡されます。このメッセージの lParam パラメーターはハンドルとして GetGestureInfo 関数に渡され、この関数は GESTUREINFO 構造体でジェスチャに関する情報を返します。GetGestureInfo 関数の呼び出し元が GESTUREINFO 構造体を割り当てると、この関数が構造体を設定します。WM_GESTURE メッセージを処理するすべてのコードは、CloseGestureInfoHandle 関数を呼び出してハンドルを閉じる必要があります。使用できる GESTUREINFO 構造体のメンバーを次の表に示します。

メンバー

説明

cbSize

構造体のサイズ (バイト単位)。

dwFlags

開始、慣性、終了などのジェスチャの状態。

dwID

実行されているジェスチャを識別するための識別子。

ptsLocation

ジェスチャに関連付けられた座標を格納する POINTS 構造体。これらの座標は常に画面上の原点を基準に決定されます。

ullArguments

8 バイトのジェスチャの引数を格納する 64 ビットの符号なし整数。これはジェスチャの種類ごとに内容の異なる追加情報であり、メッセージの wParam パラメーターによって渡されます。

ジェスチャの種類は GESTUREINFO 構造体の dwID フィールドによって識別され (これらの値は次の表に示します)、ジェスチャのステータス (ジェスチャが開始または終了したかどうか) は wFlags フィールドによって渡されます。

名前

説明

ullArgument

ptsLocation

GID_ZOOM

ズーム ジェスチャ。

2 点間の距離。

ズームの中心。

GID_PAN

パン ジェスチャ。

2 点間の距離。

パンの現在の位置。

GID_ROTATE

回転ジェスチャ。

GF_BEGIN フラグが設定されている場合の回転の角度。それ以外の場合、回転が開始されてからの角度の変化。

回転の中心。

GID_TWOFINGERTAP

2 本指のタップ ジェスチャ。

指と指の間の距離。

2 本の指の中心。

GID_PRESSANDTAP

プレス アンド タップ ジェスチャ。

最初の指と 2 番目の指の差分。この値は、POINT 構造体の ullArgument の下位 32 ビットに格納されます。

最初の指で押した位置。

Hilo は、GID_PANGID_ZOOM の 2 つのジェスチャのみを処理します。これを行うため、メッセージ ハンドラー クラス階層の基本クラスである WindowMessageHandler クラスに、OnPanOnZoom の 2 つの仮想メソッドが用意されています。WindowMessageHandler::OnMessageReceived メソッドはウィンドウに送信されるメッセージを処理します。リスト 4 に、このメッセージがどのように処理されるかを示します。コードの先頭で、ジェスチャに関する情報を取得するために GetGestureInfo 関数が呼び出され、コードの最後で、メッセージが処理された場合にリソースをクリーンアップする CloseGestureInfoHandle 関数が呼び出されます。

ジェスチャ情報が取得された後、ハンドラー コードは処理できる 2 つのジェスチャをテストします。その後、ハンドラーは仮想メソッドを呼び出す前にパラメーターを適切にデコードし、子ウィンドウのメッセージ ハンドラー クラスがジェスチャに応答できるようにします。

リスト 4 ジェスチャ メッセージを処理するコード

case WM_GESTURE:
   {
      bool handled = false;

      GESTUREINFO info;
      info.cbSize = sizeof(info);
      if (::GetGestureInfo((HGESTUREINFO)lParam, &info))
      {
         switch(info.dwID)
         {
         case GID_PAN:
            {
               D2D1_POINT_2F panLocation = Direct2DUtility::GetPositionForCurrentDPI(info.ptsLocation);
               hr = OnPan(panLocation, info.dwFlags);
               if (SUCCEEDED(hr))
               {
                  if (S_OK == hr)
                  {
                     handled = true;
                  }
               }
               break;
            }

         case GID_ZOOM:
            {
               static double previousValue = 1;
               switch(info.dwFlags)
               {
               case GF_BEGIN:
                     hr = OnZoom(1.0f);
                     break;
               case 0:
                     hr = OnZoom(static_cast<float>(LODWORD(info.ullArguments) / previousValue));
                     break;
               }

               if (SUCCEEDED(hr))
               {
                  previousValue = LODWORD(info.ullArguments);
                  if (S_OK == hr)
                  {
                     handled = true;
                  }
               }

               break;
            }
         }
      }

      if (handled)
      {
         ::CloseGestureInfoHandle((HGESTUREINFO)lParam);
         *result = 0;
      }
      else
      {
         *result = 1;
      }

      break;
   }

パン ジェスチャの実装

パン ジェスチャは、タッチ スクリーン上で指を滑らせたときに発生します。通常、アプリケーションはこの動きを反映するため、画面上のアイテムにアニメーション効果を適用します。Windows 7 には、アプリケーションが適切に応答できるように、慣性の通知が用意されています。ユーザーがタッチ スクリーンから指を離し、パン ジェスチャが終了すると、Windows 7 は、動きの速度と角度に基づいて軌道を計算します。続いて Windows 7 は、GF_INERTIA フラグが設定された GID_PAN 型の WM_GESTURE メッセージを送信します。Windows 7 は運動を減速しながらこれらのメッセージを送信し、最終的にジェスチャ メッセージを停止します。このようにして、Windows 7 の慣性エンジンは減速するアニメーションの位置情報を提供します。

Browser アプリケーションでは、メディア ウィンドウでタッチ ジェスチャを処理します。パン ジェスチャは写真間を移動するために使用され、MediaPaneMessageHandler::PanImage メソッドによって処理されます。このメソッドは、1 回のパン ジェスチャにつき、写真 1 枚分だけスクロールします。さらにスクロールするには、ジェスチャを繰り返す必要があります。PanImage メソッドは、開始位置を格納することによってパン ジェスチャの開始を処理します。ジェスチャの次のメッセージを受け取ると、PanImage メソッドは前の位置を使用して写真をどれくらいパンするかを決定します。

Browser は、パン ジェスチャを、その方向の次の写真に移動する動作として解釈します。いったんジェスチャが開始されると、指の動きが変更されても、Browser は常に開始されたアクションを最後まで実行します。たとえば、ユーザーが左に向かってパンし、指を離す前に右にパンしても、Browser は必ず左の写真にパンします。

パンの動きはアニメーションを通じて、ジェスチャが完了した後に実行されます。Browser で一度にパンできるのは写真 1 枚分のみであるため、Browser はジェスチャの慣性メッセージには応答せず、ジェスチャが終了したことを示すメッセージとして処理します。Windows 7 の慣性エンジンが指の動きの速度に応じて慣性応答を計算することにより、複数の写真分のスクロールを実行してしまう場合があるためです。パン ジェスチャが終了したこと (または慣性メッセージを生成していること) を示すメッセージが受信されると、メディア ウィンドウでは、加速と減速のアニメーションをレンダリングし、現在位置から最終位置まで減速していく動きを生成して、写真のスクロールを完了します。

ズーム ジェスチャの実装

ズーム ジェスチャは 2 本の指でつまむ動作です。指の間の距離が狭まると、アイテムのサイズの縮小 (ズーム アウト) が意味され、指の間の距離が広がると、アイテムのサイズの拡大 (ズーム イン) が意味されます。

リスト 4 では、ズーム ジェスチャを開始すると、dwFlags パラメーターが GF_BEGIN になることが示されています。これは、指の間の現在の距離を保存することによって処理されます。保存された値は、それ以降のズーム ジェスチャ メッセージを処理し、画像のサイズの変更を決定するために使用されます。このジェスチャの後続のメッセージでは、dwFlags パラメーターの値は 0 になります。これらの後続メッセージは、直前のメッセージ以降の指の間隔の変化に比例した倍率にズームすることによって処理されます。

まとめ

Hilo Browser には、複数の UI 機能が用意されています。この章では、Browser において、Annotator アプリケーションと Share アプリケーションの起動ボタンおよびタッチ スクリーン ジェスチャがどのように実装されているかを説明しました。次の章では、Hilo アプリケーションで表示できる、ジャンプ リストとタスク バーのサムネイル画像について説明します。

前へ | 次へ | ホーム