標準 Windows 視窗為矩形,透過 CRgn 設定視窗區域 (Window Region) 可建立任意形狀視窗。本文完整實作一個橢圓視窗,包含:橢圓區域建立、漸變背景色彩動畫、標題文字繪製、滑標拖曳移動、右鍵彈出選單 (置頂/自動隱藏)、全域滑鼠鉤子 (DLL) 攔截事件。
class CEllipseDlg : public CDialog {
protected:
CRgn m_rgnWnd;
// ...
BOOL OnInitDialog() {
CDialog::OnInitDialog();
CRect rc; GetClientRect(rc);
// 建立橢圓區域,四邊保留邊距
m_rgnWnd.CreateEllipticRgn(rc.left+32, rc.top+32, rc.Width()-64, rc.Height()-20);
SetWindowRgn((HRGN)m_rgnWnd, TRUE); // TRUE = 系統接管區域生命週期
return TRUE;
}
};
void CEllipseDlg::OnMouseMove(UINT nFlags, CPoint point) {
static CPoint ptLast;
ClientToScreen(&point);
if (nFlags & MK_LBUTTON) {
SetCursor(LoadCursor(NULL, IDC_CROSS));
CRect rc; GetWindowRect(rc);
MoveWindow(rc.left + point.x - ptLast.x,
rc.top + point.y - ptLast.y,
rc.Width(), rc.Height());
}
ptLast = point;
}
於 CWinApp 衍生類別加入 SetBkColor(),以 SetDialogBkColor 設定漸變筆刷,並由 WM_TIMER (20ms) 觸發重繪:
void CEllipseApp::SetBkColor() {
static BYTE r=80, g=40, b=30;
static BOOL fr=TRUE, fg=FALSE, fb=TRUE;
// R: 10~120 震盪
if (fr) { if (++r > 120) fr=FALSE; } else { if (--r < 10) fr=TRUE; }
// G: 20~100 震盪 (步進 2)
if (fg) { g+=2; if (g>100) fg=FALSE; } else { g-=2; if (g<20) fg=TRUE; }
// B: 20~80 震盪 (步進 3)
if (fb) { b+=3; if (b>80) fb=FALSE; } else { b-=3; if (b<20) fb=TRUE; }
SetDialogBkColor(RGB(r,g,b), RGB(r+100,g+120,140));
}
void CEllipseDlg::OnTimer(UINT nIDEvent) {
theApp.SetBkColor();
Invalidate(); // 觸發 WM_PAINT
}
void CEllipseDlg::OnPaint() {
CPaintDC dc(this);
dc.SetBkMode(TRANSPARENT);
dc.SetTextAlign(TA_BASELINE | TA_CENTER);
LOGFONT lf;
SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0);
lf.lfHeight = 60;
lf.lfEscapement = 100; // 10 度傾斜 (0.1度單位)
lf.lfItalic = TRUE;
lf.lfQuality = DRAFT_QUALITY;
CFont font; font.CreateFontIndirect(&lf);
CFont* pOld = dc.SelectObject(&font);
CRect rc; GetClientRect(rc);
dc.TextOut(rc.Width()/2, rc.Height()/2, _T("enjoy!!!"));
dc.SelectObject(pOld);
}
// 資源選單 IDR_RIGHT_CLICK
// 置頂
void CEllipseDlg::OnTopmost() {
CRect rc; GetWindowRect(rc);
SetWindowPos(&wndTopMost, rc.left, rc.top, rc.Width(), rc.Height(), SWP_SHOWWINDOW);
GetSubMenu()->EnableMenuItem(ID_TOPMOST, MF_GRAYED);
GetSubMenu()->EnableMenuItem(ID_ZORDER, MF_ENABLED);
}
// 自動隱藏 (送到底再送到頂,達到「不遮擋其他視窗但可見」效果)
void CEllipseDlg::OnZOrder() {
CRect rc; GetWindowRect(rc);
SetWindowPos(&wndBottom, rc.left, rc.top, rc.Width(), rc.Height(), SWP_SHOWWINDOW);
SetWindowPos(&wndTop, rc.left, rc.top, rc.Width(), rc.Height(), SWP_SHOWWINDOW);
GetSubMenu()->EnableMenuItem(ID_ZORDER, MF_GRAYED);
GetSubMenu()->EnableMenuItem(ID_TOPMOST, MF_ENABLED);
}
#ifndef __FINDER_H__
#define __FINDER_H__
#ifdef FINDER_EXPORTS
#define FINDER_API __declspec(dllexport)
#else
#define FINDER_API __declspec(dllimport)
#endif
FINDER_API HHOOK StartHook();
FINDER_API void StopHook(HHOOK hHook);
#endif
#pragma data_seg("SHARED")
HHOOK g_hHook = NULL;
HWND g_hTargetWnd = NULL; // 接收通知的視窗
#pragma data_seg()
#pragma comment(linker, "/section:SHARED,RWS")
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode == HC_ACTION && g_hTargetWnd) {
// 將滑鼠事件轉發給目標視窗
PostMessage(g_hTargetWnd, WM_USER_MOUSE, wParam, lParam);
}
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
FINDER_API HHOOK StartHook() {
g_hHook = SetWindowsHookEx(WH_MOUSE, MouseProc, AfxGetInstanceHandle(), 0);
return g_hHook;
}
FINDER_API void StopHook(HHOOK hHook) {
UnhookWindowsHookEx(hHook);
}
typedef HHOOK (WINAPI *PFN_STARTHOOK)();
typedef void (WINAPI *PFN_STOPHOOK)(HHOOK);
HMODULE hDll = LoadLibrary(_T("finder.dll"));
PFN_STARTHOOK pStart = (PFN_STARTHOOK)GetProcAddress(hDll, "StartHook");
PFN_STOPHOOK pStop = (PFN_STOPHOOK)GetProcAddress(hDll, "StopHook");
// 設定目標視窗 (需在 DLL 共享資料段中定義 HWND g_hTargetWnd)
// 這裡示範透過額外匯出函式 SetTargetWnd(HWND) 實作
typedef void (WINAPI *PFN_SETTARGET)(HWND);
PFN_SETTARGET pSetTarget = (PFN_SETTARGET)GetProcAddress(hDll, "SetTargetWnd");
pSetTarget(m_hWnd);
m_hHook = pStart(); // 啟動
// 選單項啟用/停用切換...
/section:SHARED,RWS 或 .def SECTIONS SHARED READ WRITE SHARED) 以跨進程共享 g_hHook、g_hTargetWnd。System32) 或應用程式目錄。SetWindowsHookEx 搭配 WH_MOUSE_LL (低階鉤子,無需 DLL)。