restrict window maximum size in secondary monitor - c++

I have a multi monitor setup. When a window is maximized on secondary monitor, I need to restrict the maximum size and position.
In MSDN, the documentation for MINMAXINFO mentions the following:
For systems with multiple monitors, the ptMaxSize and ptMaxPosition members describe the maximized size and position of the window on the primary monitor, even if the window ultimately maximizes onto a secondary monitor. In that case, the window manager adjusts these values to compensate for differences between the primary monitor and the monitor that displays the window. Thus, if the user leaves ptMaxSize untouched, a window on a monitor larger than the primary monitor maximizes to the size of the larger monitor.
So, I tried restriction by doing SetWindowPos in OnSysCommand if nId is SC_MAXIMIZE . It works, when the user clicks on maximize button/double click the title bar.
But, when the user uses Win+Up Arrow key or move the window to top of monitor to maximize, I am not able to handle the maximize restriction.
So, is there any common place to handle my all scenarios?
Is there any way to do trick on receiving WM_GETMINMAXINFO message.

I know this post is old, but I wish to share my code for those who still need a solution.
void CMyDialog::OnWindowPosChanging(WINDOWPOS * pos)
{
//let us do the default processing first
CDialogEx::OnWindowPosChanging(pos);
//We are only interested in setting the window size when our window is in maximized state.
//When maximized, the window will have a WS_MAIMIZE window style set
LONG_PTR lWndStyle = GetWindowLongPtr(this->m_hWnd, GWL_STYLE);
if ((lWndStyle & WS_MAXIMIZE) != WS_MAXIMIZE)
return;
//Use the proposed window from OS to identify the monitor.
//I found that, the MonitorFromWindow() API returns primary monitor info when I restore a minimized window from taskbar.
RECT rectWnd = {pos->x, pos->y, pos->x + pos->cx, pos->y + pos->cy};
HMONITOR hMon = MonitorFromRect(&rectWnd, MONITOR_DEFAULTTONEAREST);
MONITORINFO info;
info.cbSize = sizeof(info);
GetMonitorInfo(hMon, &info);
LONG nMaxWndWidth = (info.rcWork.right - info.rcWork.left);
LONG nMaxWndHeight = (info.rcWork.bottom - info.rcWork.top);
//The window and workspace height can be > or <
if (pos->cy != nMaxWndHeight)
{
pos->cy = nMaxWndHeight;
}
//The window and workspace width can be > or <
if (pos->cx != nMaxWndWidth)
{
pos->cx = nMaxWndWidth;
}
}

Related

WM_DPICHANGED event suggesting bad new position for window

I have two monitors connected to my desktop. A 3840x2160 main monitor on the left set to 150% scaling (144 DPI) and a 1920x1080 monitor on the right set to 100% scaling (96 DPI). The are aligned on the bottom edge. This is on Windows 8.1, but the same issue occurs on Windows 10.
My application is C++ using native Win32 windows. It is set at per-monitor DPI aware.
I have a dialog window that is a borderless (not title bar either) window with a custom title bar inside the client area. The user can drag this custom title bar to move the window, and internally it gets moved using SetWindowPos().
When this window is dragged from the High-DPI left monitor over to the regular monitor on the right, I get a WM_DPICHANGED message. The current RECT for the window is:
curRect = {LeftTop(3527, 1099) RightBottom(4157, 2098) WidthHeight[630 x 999]}
The new suggested RECT that the WM_DPICHANGED message gives me is:
newRect = {LeftTop(3527, 1099) RightBottom(3947, 1765) WidthHeight[420 x 666]}
If I pass the new RECT to SetWindowPos as the documentation for WM_DPICHANGED suggests, then the window is now considered back on my high-DPI monitor, and in the SetWindowPos() I get another WM_DPICHANGED message telling me to change the window pos/size again. This occurs before my original WM_DPICHANGED event has returned.
This makes sense based on the new suggested RECT since the left edge doesn't get moved, but the width is getting reduced.
The WM_DPICHANGED message for the same window doesn't have this issue if I'm using an actual non-client titlebar. The left edge is moved in the new suggested RECT.
Any suggestions on how to handle this? Any way to minimally change the new suggest RECT such that it ensures the window w/h is as suggested, but it's positioned so it doesn't fire another DPICHANGED event (stays on the new monitor).
Thanks!

setwindowplacement doesn't work for maximized window

I have a multi monitor setup. When the window is maximized in secondary monitor, I maximize the window to half of the monitor size in OnSysCommand()
by doing the following:
MONITORINFO monitorInfo = { sizeof(MONITORINFO) };
GetMonitorInfo(hMonitor, &monitorInfo);
CRect rc = monitorInfo.rcMonitor;
rc.left = rc.left / 2;
SetWindowPos(hWnd, rc.left, rc.right, rc.Width(), rc.Height(),0);
This works fine.
But,when the process is stopped and when the window is opened after process startup, the window is maximized to full monitor size, though the window placement is saved on process exit.(flags of WindowPlacement is WPF_RESTORETOMAXIMIZED , showCmd is SW_MAXIMIZE)
In OnShowWindow(), SetWindowPlacement() is used.
Maximized always ignore the size inside the windows placement structure.
This is completely by design.
The windows placement structure holds only the size of the none maximized / normal window. Only the top left corner of the maximized window is saved. And this cords are in screen coordinates to support multiple terminals.
So you get what you want, if you tell the system to restore the window maximized.
If you want to limit the maximization of the window you have to use a different approach. May be WM_GETMINMAXINFO will do it (I am not sure if it is also consulted, when the window is maximized).

How does one resize the form(dialog) in a MFC SDI CFormView application?

I've tried
MoveWindow(50,50,150,200,TRUE) in CMyFormView::OnInitialUpdate();
Also, I have tried following code in
CWinApp::InitInstance();
RECT desktop;
const HWND hDesktop = ::GetDesktopWindow();
::GetWindowRect(hDesktop,&desktop);
MoveWindow(hDesktop,0,0,900,400,TRUE);
I am having no luck resizing the form(dialog).
I would appreciate any suggestions.
In an SDI program the dialog is sized by the mainframe window to fill the client area. Resize the frame window and the dialog will follow. Put this in the formview's OnInitialUpdate
AfxGetMainWnd()->MoveWindow(....);
KEY THINGS:
MINIMUM SIZE - YOU SET - The dialog template - or form view - you make IS the absolute minimum size CFormview will use
MAXIMUM SIZE - AUTOMATICALLY SIZED - The dialog template has no maximum. CFormview dynamically stretches it out to fit the CMainFrame window.
STARTING THE APPLICATION TO THE TEMPLATE SIZE
Naturally you might want the CMainFrame window to be as compact as possible, this is achieved using 3 mandatory lines of code in your CFormView::OnInitialUpdate()
void CSimpleSDIView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
GetParentFrame()->RecalcLayout();
ResizeParentToFit(); // FORCES CMainframe to be as small as the dialog template
}
FORCE CMainFrame to LIMIT MINIMUM SIZE - If necessary. Using WM_GETMINMAXINFO Here's the code:
void CMainFrame::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
{
lpMMI->ptMinTrackSize.x = 500; // absolute minimum width for CMainFrame
lpMMI->ptMinTrackSize.y = 500; // absolute minimum height for CMainFrame
CFrameWnd::OnGetMinMaxInfo(lpMMI);
}
SET TO IDEAL SIZE This can achieved in CFormView ::OnInitialUpdate by using the following code (Step 3 is still required):
AfxGetMainWnd()->MoveWindow(0, 0, 1024, 600, 1);
TWEAK CONTROL POSITIONS With the advent of the ribbon in MFC things are looking good, but using steps 1 - 5 doesn't work without extra tweaking. Basically, the ribbon sends the main window an extra resize as it would seem, so even if you sent a minimum template size, things get a little out of proportion due to the extra resizing -- mainly because a group within a ribbon can be collapsed into a single icon. ** BEFORE DOING STEP 5** you might want to add the following code:
if(::GetSystemMetrics(SM_CXSCREEN) > 1024)
{
RECT r;
CWnd *someControl = GetDlgItem(IDC_SOMECONTROL);
someControl->GetWindowRect(&r);
ScreenToClient(&r);
r.right += 300;//r.right += 30;
r.bottom += 150;
someControl->MoveWindow(&r)
}
EXPLANATION:
The short answer, you don't! Basically, I had the same issue, made a dialog box template for use in a CFormView derived class. When I wanted to stretch the CMainFrame window out like any normal window, I wanted the content of the formview to resize accordingly. That worked fine with some code, but the problem came when I made the window very small. When I did that, I would get these ugly scrollbars. My thought was the scrollbars were there because they are trying to respect the original dialog template size. That thought is correct! For example, you make your dialog box 500 x 500 and you resize small less than that, you will get scrollbars - since CFormview respects that the application MUST BE AT A MINIMUM 500x500. The minimum size is governed by the template you make and you can not dynamically make it smaller.
Usually, child window's size, position is changed in child's init code.
If you change this in parent window code, you always have to check whether the child window handle value is valid.
I input this code WM_CREATE handler of CAboutDlg.
int CAboutDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialogEx::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: Add your specialized creation code here
RECT desktop;
const HWND hDesktop = ::GetDesktopWindow();
::GetWindowRect(hDesktop,&desktop);
MoveWindow(&desktop,TRUE);
return 0;
}

MFC PreCreateWindow to create a window that fill the screen except for the taskbar

I would like to create a window that fills the entire space of the desktop without including the Windows start menu, and without maximizing the window.
I think the code should be something like this :
BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)
{
cs.cx = ::GetSystemMetrics(SM_CXSCREEN);
cs.cy = ::GetSystemMetrics(SM_CYSCREEN); // minus start menu height please
return CMDIFrameWnd::PreCreateWindow(cs);
}
But how do I get the height of the start menu ? Thanks.
To avoid the taskbar and any appbars, you want to fill the work area. Use the GetMonitorInfo function and look at the rcWork member. (Note that each monitor has a different work area, so you need to know which monitor you care about.)

Automatically display vertical scrollbar in multiline text edit control

On a windows mobile device I have a mutliline text edit control that is set to read-only and has some static text displayed during it's display lifetime. I would like to only display a vertical scrollbar when it's actually useful (i.e. the text is larger than the display).
I can't easily figure out if the text is to large to display because of two reasons.
There is no horizontal scroll bar displayed so the text wraps.
Under windows mobile, the win32 routines to calculate the size of what text will display does not work correctly. They return a incorrect rectangle.
The edit control must tell the scroll bar what it's scroll range is at some point. I was wondering if I could get in between then and hide the scroll bar if it's not going to be used.
This is how I solved this problem.
First off:
It only works with read-only mode of a edit control (as you don't want the text to change often).
I think is specific to Windows Mobile MFC, big windows can handle this a lot better.
The solution is very very hacky.
Solution:
I have a standard CEdit bound to the control.
CEdit m_Message;
DDX_Control(pDX, IDC_MESSAGE,
m_Message);
During the InitDialog and OnSize calls, turn on the display of the scroll bar and setup a timer message.
m_Message.ShowScrollBar(SB_VERT,
TRUE);
SetTimer(DO_ADJUST_DISPLAY_STATE, 50,
0);
In the timer handling code, use the scroll information to determine if the scroll bar needs to be displayed.
If not being displayed, turn off the scroll bar and force the window to redisplay.
void CMessageDlg::OnTimer( UINT_PTR nIDEvent )
{
switch(nIDEvent)
{
case DO_ADJUST_DISPLAY_STATE:
KillTimer(nIDEvent);
// deselect all text
m_Message.SetSel(0, 0);
SCROLLINFO info;
m_Message.GetScrollInfo(SB_VERT, &info);
if(info.nPage > (UINT)info.nMax)
{
// need to re-display the non scroll bar version
m_Message.ShowScrollBar(SB_VERT, FALSE);
// I could not find any other way to force a redisplay
// correctly without display problems...
// first move the window to a know invisible area
// then wait (using a timer) for the window to move
// then move the window back to it's original position
RECT rt;
rt.left = 0;
rt.right = 5;
rt.top = 0;
rt.bottom = 5;
m_Message.MoveWindow(&rt);
SetTimer(DO_REDISPLAY_MESSAGE, 50, 0);
}
break;
case DO_REDISPLAY_MESSAGE:
KillTimer(nIDEvent);
// m_MessagePosition holds the original position
// worked out dynamically during the WM_SIZE
// processing
m_Message.MoveWindow(&m_MessagePosition);
break;
}
}
I hope that helps out other people who may have similar requirements.
I was wondering if I could get in between then and hide the scroll bar if it's not going to be used.
I highly doubt it. But I bet you could call SetScrollInfo some time after the text box is created, and send your own scroll parameters.
EDIT: Wrong link, my bad. Here's the one for the Windows CE