使用GetWindowLongPtr获取GWL_STYLE时,并不能返回所有的窗口样式
我尝试使用GetWindowLongPtr() 获取所有样式,但遇到了一个问题:该函数返回的并不是WS_BORDER。
HWND input = CreateWindow(
L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT,
300, 100, 100, 20,
hwnd, NULL, GetModuleHandle(NULL), NULL
);
LONG_PTR style = GetWindowLongPtr(input, GWL_STYLE);
在调试程序时,样式是0x50000000(只是WS_CHILD | WS_VISIBLE | ES_LEFT)。我在想,系统可能不会把WS_BORDER保存到窗口样式在内存的那一部分,而是保存在其他部分。
测试用例:
#include <windows.h>
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t* pCmdLine, int nCmdShow) {
WNDCLASSW wc = {};
wc.lpfnWndProc = DefWindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"Window";
RegisterClassW(&wc);
HWND hwnd = CreateWindow(
L"Window", L"Test", WS_OVERLAPPEDWINDOW,
// Size and pos
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);
HWND input = CreateWindow(
L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT,
100, 220, 100, 20,
hwnd, NULL, hInstance, NULL
);
ShowWindow(hwnd, nCmdShow);
LONG_PTR style = GetWindowLongPtr(input, GWL_STYLE);
MSG msg = {};
while(GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
解决方案
根据文档,大多数窗口样式在窗口创建后并非设计为可修改,下面列出的例外除外。如果某个样式未被列为可修改,那么设置它时的行为没有文档说明,应该避免这么做。
https://learn.microsoft.com/en-us/windows/win32/winmsg/window-styles
在窗口创建完成后,这些样式不能被修改,除非另有说明
适用于所有窗口的例外如下:
WS_GROUP、WS_TABSTOP可以通过SetWindowLongPtr设置WS_VISIBLE可以通过ShowWindow或SetWindowPos进行修改,且WS_DISABLED可以通过EnableWindow进行修改。
对于编辑控件,除了所有窗口通用样式外,还有一些特定的样式:
https://learn.microsoft.com/en-us/windows/win32/controls/edit-control-styles
在这些之中,以下样式在创建后可以修改:
ES_LOWERCASE、ES_NUMBER、ES_OEMCONVERT、ES_UPPERCASE、ES_WANTRETURN可以通过SetWindowLongPtr设置ES_READONLY可以通过发送EM_SETREADONLY消息来改变,配合SendMessage使用ES_PASSWORD可以通过发送EM_SETPASSWORDCHAR消息来改变,配合SendMessage使用
讨论
为什么通过 GetWindowLongPtrW 获取样式时,传给 CreateWindowExW 的样式看起来不可见?
对不同窗口的行为,窗口管理器提供的内容并不多——基本只包括大小、位置(包括嵌套窗口)、可见性和所有权。几乎所有其他内容,包括边框,都是由由窗口类定义的窗口过程来提供。
大多数窗口类将“大部分”功能委托给 DefWindowProcW,它为所有消息提供默认实现,以及基于样式变化的视觉行为等默认值。但这一切都可以被覆盖,因此不能就窗口的行为给出一般性的结论——它们可以随心所欲地做任何事。
若要说清楚的“构造器”意义上的东西,是通过按以下顺序发送的消息来实现的:WM_NCCREATE、WM_NCCALCSIZE 和 WM_CREATE:
它们可以随心所欲地去做,并且没有义务在 GWL_STYLE 字中保存 WS_ 样式标志位。创建时保存哪些样式位,以及稍后修改样式时的行为,完全由窗口过程定义,且对每个窗口类可能不同。
通常是 WM_NCCALCSIZE 通过返回“客户端矩形”来定义边框、标题等。传递 WS_BORDER 将得到一个比实际客户端区小1 像素的客户端矩形。窗口框架由 WM_NCPAINT 绘制,通常会委托给 DefWindowProcW,但也可以被覆盖。
此外,还有消息 WM_STYLECHANGING 和 WM_STYLECHANGED,允许窗口对样式的变化作出响应。特别是,WM_STYLECHANGING 可以自行决定要做什么——设置的样式不保证一定是你请求的样式,这取决于窗口过程。
综合起来
没有理由认为任何行为是所有窗口类共同拥有的。
从你的测试中可以看出,传给 CreateWindowExW 的并非所有样式位都保存在 GWL_STYLE 槽中,因此也并非都能被 GetWindowLongPtrW(hWnd, GWL_STYLE) 取回。
文档并不特别清楚——它既没有明确说明它们是否全部被保存,也没有明确说明它们是否全部被保存。确实如此,因为任何窗口类都可以任意处理。
因此——你必须逐个查阅每个窗口类的文档。