In GetClientRect rect.bottom rect.right not correct - winapi

I'm creating a program in which I want to place a button near to bottom-right corner of the Window. I'm using GetClientRect to get top, bottom, right and left of the window. Top and left are working fine but bottom and right are not working. Here's my code:
WNDCLASSEX Program;
/*Class declaration*/
hWndMain = CreateWindowEx (WS_EX_APPWINDOW,
"Program",
"Program",
WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX,
GetSystemMetrics(SM_CXSCREEN)/2-210,
GetSystemMetrics(SM_CYSCREEN)/2-135,
420,270,
HWND_DESKTOP,
NULL,hInstance,NULL);
//Window Procedure
WM_CREATE:
{
RECT MaxSize;
GetClientRect(hWndMain,&MaxSize);
/*Menu declaration using CreateMenu, AppendMenu etc*/
HWND hCalculate = CreateWindowEx(0,WC_BUTTON,
"Calculate",
WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_DEFPUSHBUTTON | 0x00000001,
MaxSize.right-156,MaxSize.bottom-51,140,30,
hWnd,(HMENU)IDC_BUTTON1,
GetModuleHandle(NULL), 0);
}
After compiling the code and running the program I'm not able to see the button. Please help.

This assignment to hWndMain looks like it's in your mainline code:
hWndMain = CreateWindowEx (WS_EX_APPWINDOW,
While this is in the WndProc:
//Window Procedure
WM_CREATE:
{
...
GetClientRect(hWndMain,&MaxSize);
However, the WM_CREATE message is received and processed within the call to CreateWindow, so it hasn't yet returned and so the assignment to hWndMain hasn't yet taken place. So you're likely calling GetClientRect() with an invalid or NULL hWndMain, and it's likely failing and returning an error which you're ignoring. Instead, use the hwnd parameter that's passed to the WndProc.

Related

Going Fullscreen in win32 without 2 WM_SIZE message

So I am creating a fullscreen function in win32 c++ doing:
uint8_t isFullscreen = 0;
RECT winRect; //Current Window Rect
RECT nonFullScreenRect; //Rect Not In Full Screen Position (used to restore window to not full screen position when coming out of fullscreen)
uint32_t screen_width = DEFAULT_SCREEN_WIDTH;
uint32_t screen_height = DEFAULT_SCREEN_HEIGHT;
void Fullscreen( HWND WindowHandle )
{
isFullscreen = isFullscreen ^ 1;
if( isFullscreen )
{
//saving off current window rect
nonFullScreenRect.left = winRect.left;
nonFullScreenRect.right = winRect.right;
nonFullScreenRect.bottom = winRect.bottom;
nonFullScreenRect.top = winRect.top;
SetWindowLongPtr( WindowHandle, GWL_STYLE, WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE ); //causes a resize msg
HMONITOR hmon = MonitorFromWindow(WindowHandle, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi = { sizeof( mi ) };
GetMonitorInfo( hmon, &mi );
screen_width = mi.rcMonitor.right - mi.rcMonitor.left;
screen_height = mi.rcMonitor.bottom - mi.rcMonitor.top;
MoveWindow( WindowHandle, mi.rcMonitor.left, mi.rcMonitor.top, (int32_t)screen_width, (int32_t)screen_height, FALSE );
}
else
{
SetWindowLongPtr( WindowHandle, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE );
screen_width = nonFullScreenRect.right - nonFullScreenRect.left;
screen_height = nonFullScreenRect.bottom - nonFullScreenRect.top;
MoveWindow( WindowHandle, nonFullScreenRect.left, nonFullScreenRect.top, (int32_t)screen_width, (int32_t)screen_height, FALSE );
}
}
However when it goes fullscreen, the function generates 2 WM_SIZE messages. While when it goes windowed, it generates only 1.
Why is that the case? And how can I make it generate only 1 WM_SIZE message for the proper full screen size?
How can I update an HWND's style and position atomically? asks about it but no one answers it
The reasons I need this is because I am using DirectX12 and on WM_SIZE I wait for all the signals at the end of the command queues before resizing all the swap chain back buffers. And I don't want to have to resize the swap chain twice when switching to fullscreen mode.
case WM_SIZE:
{
screen_width = LOWORD( LParam );
screen_height = HIWORD( LParam );
//DirectX stuff here
}break;
Thanks in advance!
Updated Answer:
The Win32 API allows you to modify parameters of the window one at a time. When a parameter is modified, the API may or may not update the window and trigger a WM_SIZE that will be the size of the window given the current parameters.
Since to have a complete full screen window you need to make at least 2 calls, one to update GWL_STYLE and another to update GWL_EXSTYLE, you have a big chance of getting 2 WM_SIZE calls. One of them will give you the window size without the menu, and the other the full screen window size. It depends in which order you call SetWindowLongPtr, but you'll probably get 2 WM_SIZE and only the second one is "correct", i.e. the one you want in the end.
The more reliable solution to your problem is to use a variable at the top of Main.cpp:
int isTogglingFullScreen = false;
Then inside your full screen toggle code (note where isTogglingFullScreen is being set):
case WM_SYSKEYDOWN:
if (wParam == VK_RETURN && (lParam & 0x60000000) == 0x20000000)
{
// Implements the classic ALT+ENTER fullscreen toggle
if (s_fullscreen)
{
isTogglingFullScreen = true;
SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
isTogglingFullScreen = false;
SetWindowLongPtr(hWnd, GWL_EXSTYLE, 0);
int width = 800;
int height = 600;
if (game)
game->GetDefaultSize(width, height);
SetWindowPos(hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED);
ShowWindow(hWnd, SW_SHOWNORMAL);
}
else
{
isTogglingFullScreen = true;
SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST);
SetWindowLongPtr(hWnd, GWL_STYLE, WS_POPUP);
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
isTogglingFullScreen = false;
ShowWindow(hWnd, SW_SHOWMAXIMIZED);
}
s_fullscreen = !s_fullscreen;
}
break;
Finally, inside WM_SIZE, change
else if (!s_in_sizemove && game)
{
game->OnWindowSizeChanged(LOWORD(lParam), HIWORD(lParam));
}
to
else if (!s_in_sizemove && game && !isTogglingFullScreen)
{
game->OnWindowSizeChanged(LOWORD(lParam), HIWORD(lParam));
}
This will give you a single call to OnWindowSizeChanged() when you toggle full screen, and the call will be with the correct final size.
--
Old Answer:
If you only want a single WM_SIZE to trigger, when you switch to full screen then you should go for something like this:
SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST);
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
ShowWindow(hWnd, SW_SHOWMAXIMIZED);
Any SetWindowLongPtr call to GWL_STYLE will trigger a WM_SIZE, so make sure it's only called with GWL_EXSTYLE. For example, if you both set GWL_EXSTYLE to what you want, and reset GWL_STYLE to 0, you'll trigger WM_SIZE twice.
To make it clearer:
Don't use GWL_STYLE in SetWindowLongPtr because it triggers a useless WM_SIZE
ShowWindow will trigger WM_SIZE
The above code will ultimately only trigger WM_SIZE once.
It turns out that YMMV. It's entirely possible that the first time you switch fullscreen, you'll get 2 WM_SIZE. One will be with the original size, and the other with the new size. Subsequent calls will trigger only one WM_SIZE.
Hence, the really bulletproof solution (which I was using anyway before playing around with the SetWindowLongPtr to answer this question, is to validate that the window size has actually changed. Because one thing that I can guarantee in the above call is that you'll never get more than 1 call with the new size. At most you'll get a WM_SIZE call with the old size, which you'll discard by checking that it's the same as the current size.
If you use the DevicesResources template for DX12, you'll see that there's a check in OnWindowSizeChanged() that does nothing if the size hasn't changed.

How to prevent moving of a window without titlebar but with WS_SYSMENU style?

I want to prevent moving of a window which I created before using CreateWindowEx.
HWND hWnd = CreateWindowEx(0, wc.lpszClassName, "Title", WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_OVERLAPPEDWINDOW, 0, 0, rect.right - rect.left, rect.bottom - rect.top, nullptr, nullptr, nullptr, nullptr);
The most common solution suggested for this is changing the windows style to have no titlebar i.e.
SetWindowLongPtr(hWnd, GWL_STYLE, GetWindowLongPtr(hWnd, GWL_STYLE) & ~(WS_CAPTION | WS_THICKFRAME));
While this solves the problem for drag- & drop at the titlebar, the window is still movable from the control menu (if the window is active press ALT, LEFT, DOWN and select "Move"). I tried using EnableMenuItem(GetSystemMenu(hWnd, FALSE), SC_MOVE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED); because this is how the disabling for the Close entry works (using SC_CLOSE instead of SC_MOVE), but it did not disable the Move option.
Is there any way using win32 api to disable only the "Move" option from the context menu without removing the WS_SYSMENU style completly?
The clue to disabling the SC_MOVE item is in the description of the GetSystemMenu function:
The system automatically grays items on the standard window menu, depending on the situation. The application can perform its own checking or graying by responding to the WM_INITMENU message that is sent before any menu is displayed.
So even though you're disabling the menu item initially, the system is re-enabling it when the menu is displayed. To fix it, you need to handle WM_INITMENU or WM_INITMENUPOPUP yourself and override the system's behaviour. For example,
in your window procedure:
case WM_INITMENUPOPUP:
if (wParam == reinterpret_cast<WPARAM>(GetSystemMenu(hWnd, FALSE)))
{
// override handling of the system menu
EnableMenuItem(reinterpret_cast<HMENU>(wParam), SC_MOVE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
return 0;
}
// if WM_INITMENUPOPUP isn't for the system menu, fall through to
// default processing
return DefWindowProc(hWnd, uMsg, wParam, lParam);

Prevent generation of WM_MOUSEMOVE after popup window is shown/hidden

I have encountered an annoying problem. When the mouse pointer is positioned over my main window and the owning popup window is shown (see example below) or is made invisible a WM_MOUSEMOVE message is generated each time even if the mouse has not been moved. For several reasons it can't be tolerated in my case.
hWnd = CreateWindowEx(0, wcx.lpszClassName, L"Demo", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, 0, hInstance, nullptr);
HWND hWndPopupTest = CreateWindowEx(WS_EX_NOACTIVATE | WS_EX_TOPMOST, L"Static", L"DemoPopup", WS_POPUP | WS_VISIBLE, 10, 10, 100, 100, hWnd, 0, hInstance, nullptr);
ShowWindow(hWnd, SW_SHOW);
ShowWindow(hWndPopup, SW_SHOWNOACTIVATE);
Sleep(1000);
ShowWindow(hWndPopup, SW_HIDE);
The same behavior occurs when ReleaseCapture is called. Is this a feature that can be disabled? Is it a known "problem" or is there a workaround?
Edit: Dirty Workaround
In (main) window procedure we could test if the mouse position has changed since last WM_MOUSEMOVE. If the position has not changed it must be because wither a popup window was shown/hidden or a some window capture was released.
Based on the information provided here (thank you #IInspectable) my general solution would be to detect if the given point is a real point by looking in mouse position history with GetMouseMovePointsEx. If no point is found it means no valid movement occurred.
POINT CurrentPoint{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
MapWindowPoints(hWnd, nullptr, &CurrentPoint, 1);
MOUSEMOVEPOINT mmpi = {
CurrentPoint.x, CurrentPoint.y, GetTickCount(), 0
};
MOUSEMOVEPOINT mmpo = {0};
if (GetMouseMovePointsEx(sizeof(mmpi), &mmpi, &mmpo, 1, GMMP_USE_DISPLAY_POINTS) > 0) {
MyInstance->HandleMouseMove(POINT{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}, wParam);
} else {
// No mouse point found in history, so couldn't be a valid point
}

Is it possible to add control to another process's window?

I've created a MFC window and, from another project, I wanted to add a button to that window via its handle (using FindWindow). The handle is correct, but nothing happens. Is this unachievable or am I doing something wrong?
Here's my code:
HWND hWnd = FindWindow(NULL, "MFCtest");
if(hWnd)
{
printf("Found it\n");
HWND hwndButton = CreateWindow(
"BUTTON",
"OK", // Button text
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
0, // x position
0, // y position
100, // Button width
100, // Button height
hWnd, // Parent window
NULL, // No menu.
(HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE),
NULL); // Pointer not needed.
if(!hwndButton)
printf("GetLastError: %d\n", GetLastError());
}
Yes. BUT!
The problem is that a window always belongs to a thread that creates it. So the messages for such a control messages will arrive in the thread that creates such a window.
This might work, but because messages form the parent window will be sent to the child will take the long way through the message queue and may cause locks if the message can't be retrieved and handled directly.
So at the end I would suggest to: Don't do it!

winapi dialogbox doesn't show

I have the problem with my dialog, that when i invoke the function "DialogBoxA" the Dialog doesn't appear. I only get the "Busy Cursor" and the the cursor is also trapped inside of a small rectangle (the size of the dialog). After the press of (almost) any button it magically appears.
Dialog box call
DialogBoxA(hInstance,MAKEINTRESOURCEA(IDD_ORACLE_DIALOG),0,reinterpret_cast<DLGPROC>(DlgProc));
Dialog resource code
IDD_ORACLE_DIALOG DIALOGEX 0, 0, 155, 71
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,43,50,50,14
PUSHBUTTON "Cancel",IDCANCEL,98,50,50,14
COMBOBOX IDC_COMBOSERVER,53,6,94,30,CBS_DROPDOWN | CBS_SORT | CBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
EDITTEXT IDC_EDITLOG,54,25,94,14,ES_AUTOHSCROLL
LTEXT "Server",IDC_STATIC,7,7,47,13
LTEXT "Log",IDC_STATIC,7,25,47,13
END
DialogProc
LRESULT CALLBACK DlgProc(HWND hWndDlg, UINT Msg, WPARAM wParam,LPARAM lParam){
switch(Msg){
case WM_INITDIALOG:
for(auto it = vServerList.begin();it!=vServerList.end();++it){
SendMessageA(GetDlgItem(hWndDlg,IDC_COMBOSERVER),CB_ADDSTRING,0,reinterpret_cast<LPARAM>(it->c_str()));
}
if(vServerList.size()>0){
SendMessage(GetDlgItem(hWndDlg,IDC_COMBOSERVER),CB_SETCURSEL,0,0);
SendMessage(GetDlgItem(hWndDlg,IDC_EDITLOG),WM_NCLBUTTONDOWN,HTCAPTION,0);
}else{
SendMessage(GetDlgItem(hWndDlg,IDC_COMBOSERVER),WM_NCLBUTTONDOWN,HTCAPTION,0);
}
#ifdef _DEBUG
SendMessageA(GetDlgItem(hWndDlg,IDC_EDITLOG),WM_SETTEXT,0,reinterpret_cast<LPARAM>("q40log"));
#endif
return TRUE;
case WM_COMMAND:
switch(wParam){
case IDOK:
size_t tServer,tLog;
tServer = GetWindowTextLength(GetDlgItem(hWndDlg,IDC_COMBOSERVER))+1;
tLog = GetWindowTextLength(GetDlgItem(hWndDlg,IDC_EDITLOG))+1;
char *pszServer,*pszLog;
pszServer = static_cast<char*>(calloc(tServer,sizeof(char)));
pszLog = static_cast<char*>(calloc(tLog,sizeof(char)));
GetDlgItemTextA(hWndDlg,IDC_COMBOSERVER,pszServer,tServer);
GetDlgItemTextA(hWndDlg,IDC_EDITLOG,pszLog,tLog);
szServer = pszServer;
szUser = pszLog;
free(pszServer);
free(pszLog);
EndDialog(hWndDlg,0);
return TRUE;
case IDCLOSE:
case IDCANCEL:
EndDialog(hWndDlg,0);
return FALSE;
}
break;
}
return FALSE;
}
The obvious failure modes are:
The dialog procedure is faulty.
The dialog resource is invalid.
The dialog resource is linked incorrectly and so cannot be loaded.
The instance handle is incorrect.
Of these options, the cast that you used in the dialog procedure parameter, it seems very likely that the dialog procedure is faulty.
I expect that you added the cast because the code would not compile. And it would not compile because the dialog procedure has an incorrect signature. The cast does not change that fact. It is just a way for you to suppress the compiler and tell it that you know better than it does. Generally speaking, the compiler tends to be right. If your dialog procedure has the wrong signature you need to correct it. Refer to the documentation to find out the correct signature.
The other big problem with your code is that you ignore the return value of DialogBox. If the function fails then the documentation tells you that a value of 0 or -1. In that scenario you can call GetLastError to get extended error information. You must get into the habit of checking for errors when using Win32 API functions.

Resources