DestroyWindow() is not destroying child window - c

WndProc is the window procedure of the main window. ChildProc is the window procedure of the child window. ChildProc is not receiving WM_DESTROY. What am I doing wrong?
EDIT: If I remove the WS_CHILD window style from hChild = CreateWindowExW(...); so it's hChild = CreateWindowExW(..., WS_VISIBLE, ...); I do get WM_DESTROY in ChildProc.
Also, I'm using Windows 10 and Visual Studio 2008
#include <windows.h>
HINSTANCE g_hInst;
LRESULT CALLBACK ChildProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_PAINT:
{
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint(hwnd, &ps);
if(hdc)
{
RECT rc;
GetClientRect(hwnd, &rc);
SetBkMode(hdc, TRANSPARENT);
FillRect(hdc, &rc, GetSysColorBrush(COLOR_GRAYTEXT));
TextOut(hdc, 0, 0, TEXT("Child"), 5);
EndPaint(hwnd, &ps);
}
}
break;
case WM_DESTROY:
MessageBoxW(0, L"Child WM_DESTROY", 0, MB_OK);
break;
default:
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static HWND hChild;
switch(msg)
{
case WM_CREATE:
{
WNDCLASSEXW wc;
SecureZeroMemory(&wc, sizeof(WNDCLASSEXW));
wc.cbSize = sizeof(WNDCLASSEXW);
wc.hCursor = LoadCursorW(0, IDC_ARROW);
wc.hInstance = g_hInst;
wc.lpfnWndProc = ChildProc;
wc.lpszClassName = L"Childclass////";
if(!RegisterClassExW(&wc)) return -1;
hChild = CreateWindowExW(0, L"Childclass////", 0, WS_VISIBLE | WS_CHILD,
0, 0, 200, 100, hwnd, 0, g_hInst, 0);
if(!hChild) return -1;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEXW wc;
HWND hwnd;
MSG msg;
SecureZeroMemory(&wc, sizeof(WNDCLASSEXW));
wc.cbSize = sizeof(WNDCLASSEXW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hCursor = LoadCursorW(0, IDC_ARROW);
wc.hIcon = LoadIconW(0, IDI_APPLICATION);
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = L"Mainclass";
if(!RegisterClassExW(&wc)) return 0;
g_hInst = hInstance;
hwnd = CreateWindowExW(0, L"Mainclass", L"Main window", WS_OVERLAPPEDWINDOW, 240, 240, 400, 200, 0, 0, hInstance, 0);
if(!hwnd) return 0;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while(GetMessageW(&msg, 0, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return (int)msg.wParam;
}

when you call DestroyWindow (assume with valid window handle) - all child windows of course will be destroyed. and all child windows of course received WM_DESTROY
ChildProc is not receiving WM_DESTROY.
this is false. i absolute sure it receive it.
What am I doing wrong?
debug diagnostic and call PostQuitMessage from wrong place.
you decide that ChildProc is "not receiving" WM_DESTROY only because you not view message box. but it will be just closed, even before shown, if you call PostQuitMessage(0); before it.
when a window is being destroyed WM_DESTROY is sent first to the owned windows (if any), then to window being destroyed and finally to the child windows (if any).
so in case you use child window - first parent window received WM_DESTROY and you call PostQuitMessage then child window call MessageBox which just returned without show due previous PostQuitMessage call.
if you use owned window - it receive WM_DESTROY first and show MessageBox normal. and only after you close it parent window receive WM_DESTROY finally and you call PostQuitMessage
for fix this, at first need call PostQuitMessage from WM_NCDESTROY - the parent window receive this message after all owned and child windows.
at second the MessageBox not the best for debug diagnostic. much better use DbgPrint, OutputDebugString or breakpoints in debugger
thank #RemyLebeau for link to Raymond Chen blog - why MessageBox() does not show anything if PostQuitMessage() was already called beforehand:
The other important thing about modality is that a WM_QUIT message
always breaks the modal loop.
So, if PostQuitMessage() is called before MessageBox(), the latter will receive the WM_QUIT message, cancel its UI, re-post WM_QUIT, and exit.

Related

Make only part of a window scrollable in win32 api

I have created a working application with pure win32 APIs and C. It has a shared "status area" on top. Rest of the area displays device information (multiple fields per device).
How do I make the device information area only scrollable?
All my widgets are currently attached to single main window handle. See here the picture about what I want :
There must be a simple way to like group the widgets of bottom page and attach scrollbar to only the group, but I cannot seem to find a working technique with google. I guess my problem is I do not know hot to create groups of widgets or something. Attaching scrollbar to to the full window works fine but I want only partial.
For those interested: I'm using Dev-C++ 5.11 with TDM-GCC 4.9.2 . I don't have and won't have a resource editor. It is all in C code.
Help!
Edit1: Ok, I believe the correct term I need to search for is "child windows" per https://learn.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows
Edi2: This old scavenged code worked somewhat: https://cboard.cprogramming.com/windows-programming/94197-creating-child-window-parent-window-post676608.html#post676608 . It creates ugly window inside window but it is what I need to get started.
Ok, i reduced the code into a minimum: https://gist.github.com/usvi/b39713e270f1f75880698baacd1774b4 or
#include <windows.h>
LRESULT CALLBACK HelloWndProc (HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCMLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("HelloApplication");
HWND hwnd,hwnd2;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = HelloWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = sizeof(long);
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows 95/98/NT"),
szAppName, MB_ICONERROR);
return 0;
}
hwnd = CreateWindow(szAppName,
TEXT("Hello World for Windows"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
400,
400,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
hwnd2 = CreateWindow(szAppName,
TEXT("Hello World"),
WS_CHILD | WS_VISIBLE | WS_VSCROLL,
CW_USEDEFAULT,
CW_USEDEFAULT,
300,
300,
hwnd,
NULL,
hInstance,
NULL);
ShowWindow(hwnd2, iCmdShow);
UpdateWindow(hwnd2);
while (GetMessage(&msg, NULL, 0, 0)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK HelloWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (message)
{
case WM_CREATE:
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (message){
case WM_CREATE:
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawText(hdc, TEXT("Hello, Windows"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
And it works, see:
My problem basically was that child window creation CreateWindow() first parameter was omitted or otherwise wrong. It needs to match named and registered WNDCLASS.
NOTE: This is a rough rough rough sketch, I might need to make 2 classes and second message handler. But this is a good start.

Cannot Create Window-based Application in Non-main Thread

In one of my projects, I need to create a window in a non-main thread. I have never done that so I don't much experience on that.
According to the MSDN documentation and the SO question, I should be able to create a window in other thread, but I cannot succeed. Even though, in thread start routine, I register a window class, create a window and provide a message loop, the thread starts and exits immediately. In addition, I cannot debug the thread start routine so I cannot hit the break points inside it.
Is there something I am missing? I hope I don't miss anything silly.
Please consider the following demo. Thank you for taking your time.
#include <Windows.h>
#include <tchar.h>
HANDLE hThread;
DWORD WINAPI OtherUIThreadFunc(LPVOID args);
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
HWND m_hwnd;
MSG msg;
WNDCLASSEX m_wcx;
const int MESSAGE_PROCESSED = 0;
const TCHAR* m_szClassName = _T("DemoWndCls");
const TCHAR* m_szWindowTitle = _T("Demo Window");
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int nCmdShow)
{
hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)OtherUIThreadFunc, hInstance, 0, NULL);
/*MSG msg;
ZeroMemory(&m_wcx, sizeof(m_wcx));
m_wcx.cbSize = sizeof(m_wcx);
m_wcx.style = CS_VREDRAW | CS_HREDRAW;
m_wcx.hInstance = hInstance;
m_wcx.lpszClassName = m_szClassName;
m_wcx.lpfnWndProc = WndProc;
m_wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
m_wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
m_wcx.hbrBackground = (HBRUSH)COLOR_WINDOW;
if (!RegisterClassEx(&m_wcx))
return false;
m_hwnd = CreateWindowEx(0, m_wcx.lpszClassName, m_szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 480, 360, NULL, NULL, hInstance, NULL);
if (!m_hwnd)
return false;
ShowWindow(m_hwnd, SW_NORMAL);
UpdateWindow(m_hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;*/
}
DWORD WINAPI OtherUIThreadFunc(LPVOID args)
{
HINSTANCE hInstance = (HINSTANCE)args;
ZeroMemory(&m_wcx, sizeof(m_wcx));
m_wcx.cbSize = sizeof(m_wcx);
m_wcx.style = CS_VREDRAW | CS_HREDRAW;
m_wcx.hInstance = hInstance;
m_wcx.lpszClassName = m_szClassName;
m_wcx.lpfnWndProc = WndProc;
m_wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
m_wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
m_wcx.hbrBackground = (HBRUSH)COLOR_WINDOW;
if (!RegisterClassEx(&m_wcx))
return false;
m_hwnd = CreateWindowEx(0, m_wcx.lpszClassName, m_szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 480, 360, NULL, NULL, hInstance, NULL);
if (!m_hwnd)
return false;
ShowWindow(m_hwnd, SW_NORMAL);
UpdateWindow(m_hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
return MESSAGE_PROCESSED;
case WM_DESTROY:
PostQuitMessage(0);
return MESSAGE_PROCESSED;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
Window creation succeeds (in theory, anyway). The issue is that the primary thread moves one to return, which causes the runtime to terminate the process.
To solve the issue you will have to keep the primary thread alive. A call to WaitForSingleObject, or a message loop are possible options.
This is mostly a result of following the conventions of C and C++. In either case returning from the main function is equivalent to calling the exit() function. This explains why returning from the primary thread tears down the entire process.
Bonus reading: If you return from the main thread, does the process exit?

Why are my WINAPI messages not reaching my window procedure?

I am using a winapi dialog as my root HWND for a window class. The program compiles and displays perfectly, but when I click on the buttons, nothing is responsive, almost like the messages aren't reaching my window procedure, or said procedure is never being called. How can I ensure that the messages are working correctly and I'm handling them properly for what I'm trying to do?
Simplified WinMain in main.c:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
static char szAppName[] = TEXT ("AppName"); // Does not display, remove later
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = DlgProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = DLGWINDOWEXTRA;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MAIN));
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
wndclass.lpszMenuName = MAKEINTRESOURCE(IDC_MAINMENU);
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL,
TEXT("This program requires Windows NT!"),
szAppName,
MB_ICONERROR);
return 0;
}
hwnd = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_MAIN), 0, NULL);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
Simplified DlgProc (written in C++ and registered for use in C):
LRESULT CALLBACK DlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
switch(Message)
{
case WM_INITDIALOG:
MessageBeep(0); // Does not sound, even if I change the case to WM_CREATE
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
// Handle buttons/statics inside the dialog, they do nothing
}
case WM_CLOSE:
EndDialog(hwnd, 0);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return FALSE;
}
return TRUE;
}
dialog in resource file:
IDD_MAIN DIALOG DISCARDABLE 0, 0, 207, 156
STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
CAPTION "AppTitle"
FONT 8, "MS Sans Serif"
BEGIN
// Internal statics and buttons
END
Any help is appreciated. Thanks!
Follow the Using the code which is in Microsoft GitHub repository steps to complete your program. You should set your dialog Class Name = AppName in resource file and Change WM_INITDIALOG back to WM_CREATE in WndProc that you call DlgProc.

Why is the window painted strangely (without visual styles) after sending WM_NCDESTROY?

I get strange window painting after I send WM_NCDESTROY manually. This only happens when visual styles are on. When 'Classic style' is on it does not seem to affect the window. I do not pass WM_NCDESTROY to DefWindowProc() when I send it manually, but the window still gets painted strangely. It seems like SendMessage() is processing WM_NCDESTROY. Why is WM_NCDESTROY getting processed even though I do not pass it to DefWindowProc()?
#include <windows.h>
HINSTANCE g_hInst;
LRESULT CALLBACK WndProc2(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static BOOL bProcessMsg = FALSE;
switch(msg)
{
case WM_RBUTTONUP:
SendMessage(hwnd, WM_NCDESTROY, 0, 0);
//Size window manually after this message is processed to see the effects
break;
case WM_DESTROY:
bProcessMsg = TRUE;
break;
case WM_NCDESTROY:
if(!bProcessMsg) return 0;
MessageBox(0, L"Message processed", 0, MB_OK);
return DefWindowProc(hwnd, msg, wParam, lParam);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
LRESULT CALLBACK WndProc1(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_MBUTTONUP:
{
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof(WNDCLASSEX);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hInstance = g_hInst;
wc.lpfnWndProc = WndProc2;
wc.lpszClassName = L"Testclass2";
if(!RegisterClassEx(&wc)) return 0;
CreateWindowEx(0, L"Testclass2", L"Test2", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 40, 40, 200, 200, hwnd, 0, g_hInst, 0);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc = { 0 };
HWND hwnd;
MSG msg;
wc.cbSize = sizeof(WNDCLASSEX);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc1;
wc.lpszClassName = L"Testclass";
if(!RegisterClassEx(&wc)) return 0;
g_hInst = hInstance;
hwnd = CreateWindowEx(0, L"Testclass", L"Test1", WS_OVERLAPPEDWINDOW, 0, 0, 200, 200, 0, 0, hInstance, 0);
if(!hwnd) return 0;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg, 0, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
Before right clicking on Test2
After right clicking on Test2
The WM_NCDESTROY message is rather special, it is guaranteed to be the last message that a window ever receives before it is destroyed. It is generated by the DestroyWindow() function.
Being last gives it a rather exalted status. It signals "stop doing what you've been doing". For example, you always use it when you subclass a window; this message tells you to stop subclassing it. And you'd always use it in a C++ wrapper class for a window, where it tells you when the C++ object needs to be destroyed. And it is pretty likely to be the notification that the Visual Styles renderer uses to stop making a window look different because it isn't around anymore.
Oops.
Messages like that are notifications that something interesting happened. As opposed to the kind of messages that are intended to make something interesting happen, like WM_LBUTTONDOWN, WM_KEYDOWN, WM_COMMAND. Most obvious in the WM_CLOSE vs WM_DESTROY message. WM_CLOSE is "please close the window". You can monkey with that and refuse to close the window, traditionally with the "Data not saved, are you sure" message. WM_DESTROY is "it is closed". That's a rock, it really did get destroyed, no point in ever monkeying with that one.
If you generate a fake notification, then you should be prepared to get a fake outcome. Don't mess with the important ones.

Why doesn't the window set cursor correctly?

As MSDN describes:
When the mouse moves over a window, the window receives a WM_SETCURSOR message (unless another window has captured the mouse).
If the application passes WM_SETCURSOR to DefWindowProc, the DefWindowProc function uses the following algorithm to set the cursor image:
If the window has a parent, forward the WM_SETCURSOR message to the parent to handle.
Otherwise, if the window has a class cursor, set the cursor to the class cursor.
If there is no class cursor, set the cursor to the arrow cursor.
And here's my source code:
#include <tchar.h>
#include <Windows.h>
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInsTance, LPTSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wcex = { 0 };
HWND hWnd;
BOOL ret;
MSG msg;
wcex.cbSize = sizeof(wcex);
wcex.lpfnWndProc = WndProc;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
// wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.lpszClassName = TEXT("MainWindow");
wcex.hIconSm = wcex.hIcon;
RegisterClassEx(&wcex);
hWnd = CreateWindow(wcex.lpszClassName, TEXT("Test"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while (ret = GetMessage(&msg, NULL, 0, 0))
{
if (ret == -1)
{
return EXIT_FAILURE;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(EXIT_SUCCESS);
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
When I move my cursor quickly over the window, the cursor doesn't change to an arrow, it become a resize arrow; if I set wcex.hCursor to LoadCursor(NULL, IDC_ARROW), everything will be all right. My question is: why doesn't my code work as MSDN says?
I mean, if I don't set wcex.hCursor, and I don't handle the WM_SETCURSOR message, the DefWindowProc should "set the cursor to the arrow cursor", but it seems it doesn't. Why is that?
See description of the hCursor member of the WNDCLASSEX:
A handle to the class cursor. This member must be a handle to a cursor
resource. If this member is NULL, an application must explicitly set
the cursor shape whenever the mouse moves into the application's
window.
WNDCLASSEX structure

Resources