滑鼠移動
當滑鼠移動時,Windows 會張貼 WM_MOUSEMOVE 訊息。 根據預設,WM_MOUSEMOVE 會移至包含游標的視窗。 您可以藉由 擷取 滑鼠來覆寫此行為,下一節會說明擷取滑鼠。
WM_MOUSEMOVE 訊息包含與按兩下滑鼠的訊息相同的參數。 最低 16 位的 lParam 包含 x 座標,而接下來的 16 位則包含 y 座標。 使用 GET_X_LPARAM 和 GET_Y_LPARAM 巨集,從 lParam 解壓縮座標。 wParam 參數包含旗標的位 OR,表示其他滑鼠按鈕的狀態加上 SHIFT 和 CTRL 鍵。 下列程式代碼會從 lParam取得滑鼠座標。
int xPos = GET_X_LPARAM(lParam);
int yPos = GET_Y_LPARAM(lParam);
請記住,這些座標以像素為單位,而不是與裝置無關的圖元(DIP)。 在本主題稍後,我們將探討兩個單位之間轉換的程序代碼。
如果游標的位置相對於視窗而變更,視窗也可以接收 WM_MOUSEMOVE 訊息。 例如,如果游標位於視窗上方,而且使用者隱藏視窗,即使滑鼠未移動,視窗仍會收到 WM_MOUSEMOVE 訊息。 此行為的結果之一是滑鼠座標可能不會在 WM_MOUSEMOVE 訊息之間變更。
擷取視窗外的滑鼠移動
根據預設,如果滑鼠移至工作區邊緣,視窗就會停止接收 WM_MOUSEMOVE 訊息。 但對於某些作業,您可能需要追蹤超過這個點的滑鼠位置。 例如,繪圖程式可能會讓使用者將選取範圍矩形拖曳到窗口邊緣,如下圖所示。
若要接收超過視窗邊緣的滑鼠移動訊息,請呼叫 SetCapture 函式。 呼叫此函式之後,只要使用者至少按住一個滑鼠按鈕,即使滑鼠在視窗外移動,視窗仍會繼續接收 WM_MOUSEMOVE 訊息。 擷取窗口必須是前景視窗,而且一次只能有一個窗口成為擷取視窗。 若要釋放滑鼠擷取,請呼叫 ReleaseCapture 函式。
您通常會以下列方式使用 SetCapture 和 ReleaseCapture。
- 當使用者按下滑鼠左鍵時,請呼叫 SetCapture 開始擷取滑鼠。
- 回應滑鼠移動訊息。
- 當使用者放開滑鼠左鍵時,請呼叫 ReleaseCapture。
範例:繪製圓形
讓我們讓使用者使用滑鼠繪製圓形,從 模組 3 擴充 Circle 程式。 從 Direct2D 圓形範例 程序開始。 我們將修改此範例中的程序代碼,以新增簡單的繪圖。 首先,將新的成員變數新增至 MainWindow
類別。
D2D1_POINT_2F ptMouse;
當使用者拖曳滑鼠時,此變數會儲存滑鼠向下位置。 在 MainWindow
建構函式中,初始化 橢圓形 和 ptMouse 變數。
MainWindow() : pFactory(NULL), pRenderTarget(NULL), pBrush(NULL),
ellipse(D2D1::Ellipse(D2D1::Point2F(), 0, 0)),
ptMouse(D2D1::Point2F())
{
}
拿掉 MainWindow::CalculateLayout
方法的主體;此範例不需要此範例。
void CalculateLayout() { }
接下來,宣告左按鈕向下、左按鈕向上和滑鼠移動訊息的訊息處理程式。
void OnLButtonDown(int pixelX, int pixelY, DWORD flags);
void OnLButtonUp();
void OnMouseMove(int pixelX, int pixelY, DWORD flags);
滑鼠座標以實體圖元提供,但 Direct2D 預期裝置無關的圖元(DIP)。 若要正確處理高 DPI 設定,您必須將圖元座標轉譯成 DIP。 如需 DPI 的詳細資訊,請參閱 DPI 和 Device-Independent 像素。 下列程式代碼顯示將圖元轉換成 DIP 的協助程式類別。
class DPIScale
{
static float scale;
public:
static void Initialize(HWND hwnd)
{
float dpi = GetDpiForWindow(hwnd);
scale = dpi/96.0f;
}
template <typename T>
static D2D1_POINT_2F PixelsToDips(T x, T y)
{
return D2D1::Point2F(static_cast<float>(x) / scale, static_cast<float>(y) / scale);
}
};
float DPIScale::scale = 1.0f;
在您建立 Direct2D Factory 對象之後,請在 WM_CREATE 處理程式中呼叫 DPIScale::Initialize。
case WM_CREATE:
if (FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pFactory)))
{
return -1; // Fail CreateWindowEx.
}
DPIScale::Initialize(hwnd);
return 0;
若要從滑鼠訊息取得 DIP 中的滑鼠座標,請執行下列動作:
- 使用 GET_X_LPARAM 和 GET_Y_LPARAM 巨集來取得圖元座標。 這些巨集是在 WindowsX.h 中定義的,因此請記得在專案中包含該標頭。
- 呼叫
DPIScale::PixelsToDips
,將像素轉換成 DIP。
現在,將訊息處理程式新增至窗口程式。
case WM_LBUTTONDOWN:
OnLButtonDown(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (DWORD)wParam);
return 0;
case WM_LBUTTONUP:
OnLButtonUp();
return 0;
case WM_MOUSEMOVE:
OnMouseMove(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (DWORD)wParam);
return 0;
最後,實作訊息處理程式本身。
左按鈕向下
針對左按鈕向下訊息,請執行下列動作:
- 呼叫 SetCapture 開始擷取滑鼠。
- 將滑鼠按下的位置儲存在 ptMouse 變數。 這個位置會定義橢圓形周框方塊的左上角。
- 重設省略號結構。
- 呼叫 InvalidateRect。 此函式會強制重新繪製視窗。
void MainWindow::OnLButtonDown(int pixelX, int pixelY, DWORD flags)
{
SetCapture(m_hwnd);
ellipse.point = ptMouse = DPIScale::PixelsToDips(pixelX, pixelY);
ellipse.radiusX = ellipse.radiusY = 1.0f;
InvalidateRect(m_hwnd, NULL, FALSE);
}
滑鼠移動
針對滑鼠移動訊息,請檢查滑鼠左鍵是否關閉。 如果是,請重新計算省略號並重新繪出視窗。 在 Direct2D 中,橢圓形是由中心點和 x 和 y 弧度所定義。 我們想要繪製一個橢圓形,以符合滑鼠向下點所定義的周框方塊(ptMouse)和目前游標位置(x,y),因此需要一點算術來尋找橢圓形的寬度、高度和位置。
下列程式代碼會重新計算省略號,然後呼叫 InvalidateRect 重新貼上視窗。
void MainWindow::OnMouseMove(int pixelX, int pixelY, DWORD flags)
{
if (flags & MK_LBUTTON)
{
const D2D1_POINT_2F dips = DPIScale::PixelsToDips(pixelX, pixelY);
const float width = (dips.x - ptMouse.x) / 2;
const float height = (dips.y - ptMouse.y) / 2;
const float x1 = ptMouse.x + width;
const float y1 = ptMouse.y + height;
ellipse = D2D1::Ellipse(D2D1::Point2F(x1, y1), width, height);
InvalidateRect(m_hwnd, NULL, FALSE);
}
}
左按鈕向上
針對左側按鈕訊息,只要呼叫 ReleaseCapture 即可釋放滑鼠擷取。
void MainWindow::OnLButtonUp()
{
ReleaseCapture();
}