Marius Bancila
Marius Bancila

Reputation: 16338

Problems with per-monitor DPI aware MFC MDI app

I'm working on making an MFC app display properly on multiple monitor environments with different DPI scaling. There is one issue I cannot explain which occurs when the primary and secondary monitors are running with different DPIs and the app is on the secondary monitor.

If the primary monitor has 100% DPI scaling (96) and the secondary monitor has the same 100% DPI scaling, all is fine.

If the primary monitor has 100% DPI scaling (96) and the secondary monitor has 125% scaling (120 DPI) or 150% scaling (144 DPI) or any other higher value, when child windows are maximized, part of the child window system bar is visible, as seen here:

125% scaling: enter image description here

150% scaling: enter image description here

If you look carefully, its 7 pixels for 125% and 14 for 150%. Given that the system bar is 29 pixels at 100% scaling, 36 and 125%, and 43 at 150%, those 7 and 14 pixels is the height difference between the bar size at 125% and 150% respectively, compared to the 100% baseline.

Therefore, it appears that the position and size of the bar is computed by the system as it was run on the primary monitor.

When you maximize the child window, there is a series of Windows messages that are sent to the window: WM_GETMINMAXINFO > WM_WINDOWPOSCHANGING > WM_GETMINMAXINFO > WM_NCCALSIZE > WM_WINDOWSPOSCHANGED > WM_MOVE > WM_SIZE. WM_GETMINMAXINFO is sent when the size or position of the window is about to change so that an app can override, for instance, the window's default maximized size and position. There is a note about this:

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.

There is an article by Raymond Chan, explaining this: How does the window manager adjust ptMaxSize and ptMaxPosition for multiple monitors?.

So the ptMaxSize should be filled with the dimensions of the primary monitor. My primary monitor is 2560x1440 pixels and the size of the secondary monitor is 1920x1200. However, the value of the size I get here is 1757x1023 and 1761x1027 (at consecutive calls). This is neither the size of the primary nor the secondary monitor.

I tried to do a dirty trick and handled the WM_NCCALCSIZE message and set the position (left, top) at 0 (relative to the parent).

void CMyMDIChildWnd::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp)
{
   CMDIChildWnd::OnNcCalcSize(bCalcValidRects, lpncsp);   
   if (condition)
   {
      lpncsp->rgrc[0].left = 0;
      lpncsp->rgrc[0].top = 0;
   }
}

Works fine as long as the child window has the focus. If I click on another window and it loses the focus, then the bar is redrawn and shows up at the previous position. This trick is only saying where the client area starts so when the non-client is redrawn I get back to the original problem.

My question is what could the root of this problem be and how can I try to fix it?

Upvotes: 3

Views: 1056

Answers (1)

dicksters
dicksters

Reputation: 131

I came across possibly the same phenomenon, albeit from a different route, and my solution was very similar to yours, with a little extra, maybe this might help you?

void CFixedFrame::OnNcCalcSize ( BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp )
{
    MDIBASEWND::OnNcCalcSize ( bCalcValidRects, lpncsp ) ;

    if ( bCalcValidRects )
    {
        RECT& rcNew = lpncsp->rgrc[0];
        RECT& rcOld = lpncsp->rgrc[1];
        RECT& rcClient = lpncsp->rgrc[2];

        // My problem arose because of the Aero bug (hardwired border widths)
        // And also problems with Theming
        const CNonClientMetrics ncm;
        rcNew.top = ncm.iCaptionHeight + Aero related stuff not relevant to your problem
    }
}

Where CNonClientMetrics is...

class CNonClientMetrics : public NONCLIENTMETRICS
{
public:
    CNonClientMetrics ( )
    {
        cbSize = sizeof ( NONCLIENTMETRICS ) - sizeof ( this->iPaddedBorderWidth ) ;
        SystemParametersInfo ( SPI_GETNONCLIENTMETRICS, sizeof ( NONCLIENTMETRICS ), this, 0 ) ;
    }
} ;

[In a DPI-Aware way] This allowed me to get rid of that annoying border of blue (with buttons). As I understand it, you can no longer disable the DWM that draws that section.

I can't now find my original references, but in my notes for this problem, it only occurs for MFC's MDI Frame Windows.

Still, this link might also be useful?

Upvotes: 0

Related Questions