本文示範如何在 Win32 應用程式中使用 GDI+ 實作自繪按鈕,並透過 WM_MOUSEHOVER/WM_MOUSELEAVE 訊息實現滑鼠進入時切換圖片、離開時恢復的視覺回饋效果。
WM_DRAWITEM 負責繪製。SetWindowLongPtr(GWL_WNDPROC) 截獲按鈕訊息,處理滑鼠懸停/離開。TME_HOVER | TME_LEAVE,觸發 WM_MOUSEHOVER/LEAVE。Graphics、Image、LinearGradientBrush、SolidBrush、Pen 實現向量繪圖與 PNG 圖片渲染。// WinMain 內初始化 GDI+
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// WM_CREATE 內建立自繪按鈕
hMainPage = CreateWindow(
L"button", L"", WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
10, 32, 20, 20, hWnd, (HMENU)ID_MAINPAGE, hInst, NULL);
// 子類別化:攔截按鈕訊息
oldBtnProc = (WNDPROC)SetWindowLongPtr(hMainPage, GWLP_WNDPROC, (LONG_PTR)BtnProc);
case WM_DRAWITEM: {
LPDRAWITEMSTRUCT pdis = (LPDRAWITEMSTRUCT)lParam;
Graphics graphics(pdis->hDC);
if (pdis->CtlID == ID_MAINPAGE) {
// 依 btnEntry 旗標切換圖片
Image *img = btnEntry ? L"bitmap2.png" : L"bitmap.png";
graphics.DrawImage(img, 0, 0, 20, 20);
}
// 其他按鈕繪製漸層背景、幾何圖形...
// 焦點矩形
if (pdis->itemState & ODS_FOCUS) {
pdis->rcItem.left += cx/16; // 縮進
DrawFocusRect(pdis->hDC, &pdis->rcItem);
}
return TRUE;
}
LRESULT CALLBACK BtnProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_MOUSEMOVE:
if (!btnEntry) {
TRACKMOUSEEVENT tme = { sizeof(tme), TME_HOVER | TME_LEAVE, 1, hwnd };
TrackMouseEvent(&tme); // 只需註冊一次
}
break;
case WM_MOUSEHOVER:
btnEntry = TRUE;
InvalidateRect(hwnd, NULL, TRUE); // 觸發重繪
UpdateWindow(hwnd);
break;
case WM_MOUSELEAVE:
btnEntry = FALSE;
InvalidateRect(hwnd, NULL, TRUE);
UpdateWindow(hwnd);
break;
default:
return CallWindowProc(oldBtnProc, hwnd, uMsg, wParam, lParam);
}
return 0;
}
bitmap.png (正常態)、bitmap2.png (Hover 態),置於執行檔同目錄。#pragma comment(lib, "Gdiplus.lib"),並定義 _WIN32_WINNT 0x0500 以上。WinMain 結束前呼叫 GdiplusShutdown(gdiplusToken)。| 問題 | 解決/優化 |
|---|---|
| 圖片重複載入導致效能差 | 啟動時 Image::FromFile 載入快取,WM_DESTROY 釋放 |
| 高 DPI 模糊 | Manifest 宣告 dpiAware,或使用 SetProcessDpiAwarenessContext |
| 閃爍 | 雙緩衝:BufferedPaint 或記憶體 DC 繪製後 BitBlt |
| 多按鈕狀態管理 | 封裝 COwnerDrawButton 類別,內部維護 btnEntry、圖片指標 |