ユーザーによる入力: 拡張の例
ユーザーによる入力について学んだことをすべて組み合わせて、簡単な描画プログラムを作成しましょう。 プログラムのスクリーンショットを次に示します。
ユーザーは、複数の異なる色で省略記号を描画し、省略記号を選択、移動、または削除できます。 UI をシンプルに保つために、プログラムではユーザーが省略記号の色を選択することはできません。 代わりに、プログラムは色の定義済みリストを自動的に循環します。 プログラムでは、省略記号以外の図形はサポートされていません。 明らかに、このプログラムはグラフィックス ソフトウェアの賞を受賞することはないでしょう。 それでも、学ぶ上で役立つ例です。 完全なソース コードは、 Simple Drawing Sample からダウンロードできます。 このセクションでは、いくつかのハイライトについて説明します。
省略記号は、省略記号データ (D2D1_ELLIPSE) と色 (D2D1_COLOR_F) を含む構造体によってプログラムで表されます。 この構造体では、省略記号を描画するメソッドとヒット テストを実行するメソッドといった 2 つのメソッドも定義されています。
struct MyEllipse
{
D2D1_ELLIPSE ellipse;
D2D1_COLOR_F color;
void Draw(ID2D1RenderTarget *pRT, ID2D1SolidColorBrush *pBrush)
{
pBrush->SetColor(color);
pRT->FillEllipse(ellipse, pBrush);
pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Black));
pRT->DrawEllipse(ellipse, pBrush, 1.0f);
}
BOOL HitTest(float x, float y)
{
const float a = ellipse.radiusX;
const float b = ellipse.radiusY;
const float x1 = x - ellipse.point.x;
const float y1 = y - ellipse.point.y;
const float d = ((x1 * x1) / (a * a)) + ((y1 * y1) / (b * b));
return d <= 1.0f;
}
};
このプログラムでは、同じ純色ブラシを使用して、すべての省略記号の塗りつぶしと輪郭を描画し、必要に応じて色を変更します。 Direct2D では、単色ブラシの色を変更することは効率的な操作です。 そのため、純色ブラシのオブジェクトは、 SetColor メソッドをサポートします。
省略記号は、STL list コンテナーに格納されます。
list<shared_ptr<MyEllipse>> ellipses;
Note
shared_ptr は、TR1 で C++ に追加され、C++0x で形式化されたスマート ポインター クラスです。 Visual Studio 2010 では、 shared_ptr やその他の C++0x 機能のサポートが追加されました。 詳細については、MSDN マガジンの記事「 Visual Studio 2010 の新しい C++ および MFC 機能の概要を参照してください。
このプログラムには、次の 3 つのモードがあります。
- 描画モード。 ユーザーは新しい省略記号を描画できます。
- 選択モード。 ユーザーは省略記号を選択できます。
- ドラッグ モード。 ユーザーは、選択した省略記号をドラッグできます。
ユーザーは、「Accelerator Tables」で説明されているのと同じキーボード ショートカットを使用して、描画モードと選択モードを切り替えることができます。 選択モードから、ユーザーが省略記号をクリックすると、プログラムはドラッグ モードに切り替わります。 ユーザーがマウス ボタンを離すと、選択モードに戻ります。 現在の選択範囲は、省略記号の一覧に反復子として格納されます。 ヘルパー メソッド MainWindow::Selection
は、選択した省略記号へのポインター、または選択されていない場合は値 nullptr を返します。
list<shared_ptr<MyEllipse>>::iterator selection;
shared_ptr<MyEllipse> Selection()
{
if (selection == ellipses.end())
{
return nullptr;
}
else
{
return (*selection);
}
}
void ClearSelection() { selection = ellipses.end(); }
次の表は、3 つの各モードでのマウス入力の効果をまとめたものです。
マウス入力 | 描画モード | Selection Mode | ドラッグ モード |
---|---|---|---|
左ボタンの下 | マウス キャプチャを設定し、新しい省略記号の描画を開始します。 | 現在の選択範囲を解除し、ヒット テストを実行します。 省略記号がヒットした場合は、カーソルをキャプチャし、省略記号を選択して、ドラッグ モードに切り替えます。 | NO ACTION |
マウスの移動 | 左ボタンが下にある場合は、省略記号のサイズを変更します。 | NO ACTION | 選択した省略記号に移動します。 |
左ボタンの上 | 省略記号の描画を停止します。 | NO ACTION | [選択] モードに切り替えます |
MainWindow
クラスの次のメソッドは、WM_LBUTTONDOWN を処理します。
void MainWindow::OnLButtonDown(int pixelX, int pixelY, DWORD flags)
{
const float dipX = DPIScale::PixelsToDipsX(pixelX);
const float dipY = DPIScale::PixelsToDipsY(pixelY);
if (mode == DrawMode)
{
POINT pt = { pixelX, pixelY };
if (DragDetect(m_hwnd, pt))
{
SetCapture(m_hwnd);
// Start a new ellipse.
InsertEllipse(dipX, dipY);
}
}
else
{
ClearSelection();
if (HitTest(dipX, dipY))
{
SetCapture(m_hwnd);
ptMouse = Selection()->ellipse.point;
ptMouse.x -= dipX;
ptMouse.y -= dipY;
SetMode(DragMode);
}
}
InvalidateRect(m_hwnd, NULL, FALSE);
}
マウスの座標は、このメソッドにピクセル単位で渡された後、DIP に変換されます。 これら 2 つのユニットを混同しないことが重要です。 たとえば、 DragDetect 関数ではピクセルが使用されますが、描画とヒット テストでは DIP が使用されます。 一般的なルールは、ウィンドウまたはマウス入力に関連する関数はピクセルを使用し、Direct2D と DirectWrite では DIP を使用します。 常に高い DPI 設定でプログラムをテストし、プログラムを DPI 対応としてマークすることを忘れないでください。 詳細については、「DPI とデバイスに依存しないピクセル」を参照してください。
WM_MOUSEMOVE メッセージを処理するコードを次に示します。
void MainWindow::OnMouseMove(int pixelX, int pixelY, DWORD flags)
{
const float dipX = DPIScale::PixelsToDipsX(pixelX);
const float dipY = DPIScale::PixelsToDipsY(pixelY);
if ((flags & MK_LBUTTON) && Selection())
{
if (mode == DrawMode)
{
// Resize the ellipse.
const float width = (dipX - ptMouse.x) / 2;
const float height = (dipY - ptMouse.y) / 2;
const float x1 = ptMouse.x + width;
const float y1 = ptMouse.y + height;
Selection()->ellipse = D2D1::Ellipse(D2D1::Point2F(x1, y1), width, height);
}
else if (mode == DragMode)
{
// Move the ellipse.
Selection()->ellipse.point.x = dipX + ptMouse.x;
Selection()->ellipse.point.y = dipY + ptMouse.y;
}
InvalidateRect(m_hwnd, NULL, FALSE);
}
}
省略記号のサイズを変更するロジックについては、「例: 描画サークル」セクションで説明しました。 また、InvalidateRect の呼び出しにも注意してください。 これにより、ウィンドウが再描画されます。 次のコードは、WM_LBUTTONUP メッセージ を処理します。
void MainWindow::OnLButtonUp()
{
if ((mode == DrawMode) && Selection())
{
ClearSelection();
InvalidateRect(m_hwnd, NULL, FALSE);
}
else if (mode == DragMode)
{
SetMode(SelectMode);
}
ReleaseCapture();
}
ご覧のように、マウス入力のメッセージ ハンドラーには、現在のモードに応じて分岐コードがあります。 これは、この非常に単純なプログラムで許容される設計です。 ただし、新しいモードが追加されると、すぐに複雑になる可能性があります。 大規模なプログラムの場合、モデル ビュー コントローラー (MVC) アーキテクチャの方が優れた設計になる場合があります。 この種のアーキテクチャでは、ユーザーによる入力を処理する controller は、アプリケーション データを管理する model から分離されます。
プログラムがモードを切り替えると、カーソルが変更され、ユーザーにフィードバックが送信されます。
void MainWindow::SetMode(Mode m)
{
mode = m;
// Update the cursor
LPWSTR cursor;
switch (mode)
{
case DrawMode:
cursor = IDC_CROSS;
break;
case SelectMode:
cursor = IDC_HAND;
break;
case DragMode:
cursor = IDC_SIZEALL;
break;
}
hCursor = LoadCursor(NULL, cursor);
SetCursor(hCursor);
}
最後に、ウィンドウが WM_SETCURSOR メッセージを受信したときにカーソルを設定することを忘れないでください。
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT)
{
SetCursor(hCursor);
return TRUE;
}
break;
まとめ
このモジュールでは、マウスとキーボードの入力を処理する方法、キーボード ショートカットを定義する方法、プログラムの現在の状態を反映するようにカーソル イメージを更新する方法を学習しました。。