MessageBox、FileDialog、FontDialog 等系統對話框美化向來困難。Hook 技術雖可行但效率低。本文介紹「替換視窗程序法」:攔截對話框建立後的 OnInitDone 虛函式,以 SetWindowLong(GWL_WNDPROC) 替換視窗程序,實現檔案對話框換膚,並支援目錄選擇模式。
LONG SetWindowLong(HWND hWnd, int nIndex, long dwLongNewProc);
// nIndex = GWL_WNDPROC 時設定新視窗程序
// 回傳值為舊視窗程序位址
此為 CFileDialog 內部虛函式,對話框建立後自動呼叫,作為替換視窗程序的切入點。
static LRESULT CALLBACK WindowProcNew(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
WNDPROC m_MyWndProc; // 全域/成員變數保存舊程序位址
void CMyFileDialog::OnInitDone()
{
CWnd* pDialog = GetParent();
m_MyWndProc = (WNDPROC)SetWindowLong(pDialog->m_hWnd, GWL_WNDPROC, (long)WindowProcNew);
}
LRESULT CALLBACK CMyFileDialog::WindowProcNew(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_NOTIFY: case WM_MOUSEMOVE: case WM_MOVE:
case WM_NCACTIVATE: case WM_NCPAINT: case WM_NCMOUSEMOVE:
case WM_PAINT: case WM_COMMAND: case WM_SIZE:
// 此處加入自訂繪製/處理代碼
break;
default: break;
}
// 呼叫舊視窗程序處理其餘訊息
return CallWindowProc(m_MyWndProc, hwnd, message, wParam, lParam);
}
編譯為 DLL(含 Debug/Release 版),專案加入 .lib .dll .h:
#include "MyFileDialog.h"
#pragma comment(lib, "user.lib")
// 開啟檔案對話框
CMyFileDialog dlg(TRUE, 1, _T("對話框標題"));
dlg.DoModal();
CString path = dlg.GetPathName();
// 目錄選擇模式:第二參數 TRUE
CMyFileDialog dirDlg(TRUE, TRUE, _T("選擇目錄"));
dirDlg.DoModal();
建構子第二參數為 TRUE 時即為目錄選擇模式,解決 MFC 目錄選擇對話框難美化的痛點。
詳細原始碼請下載本文附帶範例專案。