最近遇到了一個問題,duilib工程中使用了Static控件,然後需要更改Static控件的背景色。
最開始的想法是在CreateWindowEx中能否指定畫刷之類的信息,發現沒有。
然後在網上找了一些信息,發現這篇文章可以解決。https://blog.csdn.net/softn/article/details/51718352
不過這個需要依賴向父窗口發送的WM_CTLCOLORSTATIC消息來解決。那實現起來就不夠通用,需要和父窗口耦合了。
所以也否決了這個做法。
再繼續找了下,沒有什麼好方法,不過找到了一些子類化的資料,想想之前也項目中也有使用子類化的方法,所以就採用了這個方法來解決了。
代碼很簡單,通過CreateWindowEx創建Static控件,然後通過SetWindowLong設置新的窗口過程,在新的窗口過程中響應WM_PAINT消息進行繪製即可。關鍵代碼如下:
WNDPROC OldProc = NULL;
LRESULT CALLBACK StaticControlProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (msg == WM_PAINT)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hWnd, &ps);
RECT rect;
::GetClientRect(hWnd, &rect);
DWORD dwColor = RGB(255, 0, 0);
HBRUSH hBrush = ::CreateSolidBrush(dwColor);
HPEN hPen = ::CreatePen(PS_SOLID, 1, dwColor);
HBRUSH hOldBrush = (HBRUSH)::SelectObject(hdc, hBrush);
HPEN hOldPen = (HPEN)::SelectObject(hdc, hPen);
::Rectangle(hdc, 0, 0, rect.right, rect.bottom);
::SelectObject(hdc, hOldBrush);
::SelectObject(hdc, hOldPen);
::DeleteObject(hBrush);
::DeleteObject(hPen);
EndPaint(hWnd, &ps);
return 0;
}
else
{
return ::CallWindowProc(OldProc, hWnd, msg, wParam, lParam);
}
}
HWND hwnd = ::CreateWindowExW(dwStyleEx, L"Static", NULL, dwStyle, left, top, width, height,
hParentWnd, NULL, hInstance, NULL);
OldProc = (WNDPROC)SetWindowLong(hwnd, GWL_WNDPROC, (LONG)StaticControlProc);
子類化有兩種實現方式,具體參考下面msdn的文章介紹。
https://docs.microsoft.com/en-us/windows/win32/controls/subclassing-overview
(1)使用SetWindowLong設置新的窗口過程。
(2)使用SetWindowSubclass來設置子類化窗口過程。
需要注意的是子類化這些函數調用都不能跨線程!!!
Warning You cannot use the subclassing helper functions to subclass a window across threads.
第一種方式就是前面的代碼所示。
第二種方式也很簡單,同樣的邏輯代碼調整如下:
LRESULT CALLBACK StaticControlProc(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
if (uMsg == WM_PAINT)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hWnd, &ps);
RECT rect;
::GetClientRect(hWnd, &rect);
DWORD dwColor = RGB(255, 0, 0);
HBRUSH hBrush = ::CreateSolidBrush(dwColor);
HPEN hPen = ::CreatePen(PS_SOLID, 1, dwColor);
HBRUSH hOldBrush = (HBRUSH)::SelectObject(hdc, hBrush);
HPEN hOldPen = (HPEN)::SelectObject(hdc, hPen);
::Rectangle(hdc, 0, 0, rect.right, rect.bottom);
::SelectObject(hdc, hOldBrush);
::SelectObject(hdc, hOldPen);
::DeleteObject(hBrush);
::DeleteObject(hPen);
EndPaint(hWnd, &ps);
return 0;
}
else
{
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
}
HWND hwnd = ::CreateWindowExW(dwStyleEx, L"Static", NULL, dwStyle, left, top, width, height,
hParentWnd, NULL, hInstance, NULL);
SetWindowSubclass(hwnd, StaticControlProc, 0, 0);
相對來說更簡單一些了。
雖然msdn說ComCtl32.dll在版本6之前使用第一種方式。版本6以及之後使用第二種方式。
Subclassing Controls Prior to ComCtl32.dll version 6
Subclassing Controls Using ComCtl32.dll version 6
不過目前兩種方式從msdn上來看xp都已經支持了。建議使用第二種方式。
第二種方式可以解決第一種方式的一些缺點:
Disadvantages of the Old Subclassing Approach
The following list points out some of the disadvantages of using the previously described approach to subclassing a control.
- The window procedure can only be replaced once.
- It is difficult to remove a subclass after it is created.
- Associating private data with a window is inefficient.
- To call the next procedure in a subclass chain, you cannot cast the old window procedure and call it, you must call it by using the CallWindowProc function.
順便提一下,使用第一種方式的時候msdn提過可以使用SetProp函數給窗口設置數據,這是一個好的方法,第二種方式,或者其他場景都是可以使用的。
msdn文檔:
https://docs.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-setpropa
https://docs.microsoft.com/zh-cn/windows/win32/winmsg/using-window-properties
函數原型:
BOOL SetPropA(
HWND hWnd,
LPCSTR lpString,
HANDLE hData
);
最後使用msdn介紹子類化的文檔來總結吧。
如果一個控件的特性能爲你所用,但是你還想添加一點特性,你可以通過子類化來添加這些特性。子類化可以使你使用控件的所有特性,並且包含你給他添加的特性。
突然發現子類化是個好東西,之前一直沒有這麼深刻的理解。有一個面試的時候面試官還問我了子類化,我還沒有答上來,還在和他確認是不是繼承一個控件類,想起來真實尷尬。所以還是要多寫博客總結做過的事情,影響纔會深刻。加油body!
補充:
MFC子類化參考資料:https://www.cnblogs.com/just-bg/p/3929044.html
WTL子類化參考資料:https://www.cnblogs.com/wdhust/archive/2010/09/18/1830097.html
順便提一下窗口超類化:
超類化根據已有的(windows系統中已經註冊過的)窗口類,比如“Edit”,”Button”等,複製其WNDCLASS(EX)結構,構造一個新類,並提供額外的功能和行爲。
換句話說就是註冊一個新的窗口類,然後也需要更改窗口過程來定製一些特性。
下面這段代碼就是超類化的實現。
BOOL CMyEdit2::RegisterMe()
{
WNDCLASS wc;
if (!GetClassInfo(NULL,_T("Edit"),&wc)) {
return FALSE;
}
wc.lpszClassName = CLASS_NAME;
m_oldWndProc = wc.lpfnWndProc;
wc.lpfnWndProc = MyWndProc;
return RegisterClass(&wc);
}
MFC超類化參考資料:https://www.cnblogs.com/just-bg/p/3929044.html
WTL超類化參考資料:https://www.cnblogs.com/wdhust/archive/2010/09/18/1830097.html