Work at SourceForge, help us to make it a better place! We have an immediate need for a Support Technician in our San Francisco or Denver office.

Close

Bug: multimonitor support is broken

2009-06-16
2013-05-28
  • Todor Totev
    Todor Totev
    2009-06-16

    The problem is within CWnd::CenterWindow. It uses SystemParametersInfo(SPI_GETWORKAREA), which according to MSDN works only with the primary monitor. If the main frame is on the 2nd monitor and you open a dialog, it goes to the 1st monitor. MSDN suggest using SystemParametersInfo instead, though I'm not sure if it is the best option.

     
    • Todor Totev
      Todor Totev
      2009-06-16

      Oops, a copy-paste bug (rofl). The second SystemParametersInfo should read GetMonitorInfo.

       
    • David
      David
      2009-06-17

      Hi Todor,

      I think CWnd::CenterWindow is fine the way it is. Currently it centers the window over the parent window , or centers the window over the primary display if the window has no parent. The primary display is the best guess that CentreWindow could choose to display the window in the absence of other information.

      I will be changing CDialog slightly.  Currently CenterWindow is called in DialogProcDefault, where as it should be in CDialog::OnInitDialog.  Users will now need to add CenterWindow (or some other window positioning code) to OnInitDialog if/when it is overridden.

      As a reminder, the parent of a dialog can be set before the dialog is created. The parent can be set either in its constructor, or using SetDlgParent. The CenterWindow function would then be able to position the dialog over the frame irrespective of which monitor the frame window is displayed on.

      Regards.
      David

       
      • Todor Totev
        Todor Totev
        2009-06-17

        No it doesn't because it has a bug :) When I run CenterWindow() step-by-step in the debugger, I see this:
        The debugger shows CRects in the format {top,bottom,left,right}, I'll just put the numbers in their right places

        rc = {162,536,1382,1918}
        rcDesktop = {0,994,0,1280}
        rcParent = {139,867,1378,2338}

        With these numbers, later in the code x gets 1590, iWidth is 1280 and the condition

        if (x > iWidth - rcWidth()) 

        clips the window on the wrong monitor (because 1590 > 1280).

        Note that if the second display has negative coordinates, which happens when the primary monitor is the right one,
        then the condition (x<0) will position the window on the wrong monitor.

        Also, you wrote "The parent can be set either in its constructor". But the parent window cannot be set in dialog's contructor because the dialog has not a WHND yet. As you see from the numbers I provided, with current code CenterWindow knows very well dialog's parent, it's just not where it expects it to be.

        Also,
        why is m_hDlgParent private and not protected?

         
    • Todor Totev
      Todor Totev
      2009-06-23

      Please consider this implementation of CenterWindow:

          inline void CWnd::CenterWindow() const
          {
              // Centers this window over it's parent

              CRect rc = GetWindowRect();
              CRect rcParent;
              CRect rcDesktop;

              // Get screen dimensions excluding task bar
          HMONITOR active_monitor = MonitorFromWindow(*this, MONITOR_DEFAULTTONEAREST);
          MONITORINFO mi = { sizeof(mi), 0};
          if(GetMonitorInfo(active_monitor, &mi))
            rcDesktop = mi.rcWork;

              // Get the parent window dimensions (parent could be the desktop)
              if (GetParent() != NULL)
            ::GetWindowRect(GetParent(), &rcParent);
              else rcParent = rcDesktop;

              // Calculate point to center the dialog on the parent window
              int x = rcParent.left + (rcParent.Width() - rc.Width())/2;
              int y = rcParent.top + (rcParent.Height() - rc.Height())/2;

              // Keep the dialog wholly on the desktop
              if (x > rcDesktop.right - rc.Width())
            x = rcDesktop.right - rc.Width();
              if (y > rcDesktop.bottom - rc.Height())
                  y = rcDesktop.bottom - rc.Height();
              if (x < rcDesktop.left)
            x = rcDesktop.left;
              if (y < rcDesktop.top)
            y = rcDesktop.top;

              ::SetWindowPos(m_hWnd, HWND_TOP, x, y, 0, 0,  SWP_NOSIZE);
          }

      It works on WIndows 98 and up, if you want to support WIn95, you should dynamically import MonitorFromWindow and GetMonitorInfo. This code has these nice features:
      * it centers the window to the parent regardless of the monitotor used by the parent
      * it clips the window to the monitor which contains the bigger part of the parent - i.e. if most of the visible part of the parent is on monitor 2, the whole child window will be on monitor 2 too
      * it keeps the upper-left part of the child visible in all cases - even when the parent is partially offscreen.

      Hope this will be useful to you.

       
    • David
      David
      2009-07-09

      Hi Todor,

      I've submitted a fix for CWnd::CenterWindow. Its somewhat simpler than your suggestion, but a simpler implementation is not necessarily a bad thing. Also CenterWindow is now virtual, allowing it to be overridden should that be deemed necessary.

      By the way, it seems you misunderstand the role of m_hDlgParent. It's not actually the parent of the dialog, rather it is what the parent will be after the dialog is created. The comments I made previously about setting the dialog's parent are actually correct.

      You said ...
      >> why is m_hDlgParent private and not protected?
      This was done to avoid misuse. The GetParent function should be used to retrieve the actual (current) parent of the dialog. m_hDlgParent does not attempt to track possible changes to the dialog's parent.

      The code has just undergone some fairly major changes particularly to CDC, but there are no bugs I'm currently aware of. Revision 869 should be a good version to update to. Be mindful that you might see minor incompatibilities with existing code. These are listed in changes.txt within the Win32++ directory.

      Regards,
      David

       
      • Todor Totev
        Todor Totev
        2009-07-09

        I've just updated to [869] but the only change I could see is making CenterWindow virtual.

        Thanks for the clarification about  m_hDlgParent.

         
        • David
          David
          2009-07-10

          The new CenterWindow function is smaller, and doesn't have the "multimonitor" bug.

           
    • David
      David
      2009-07-10

      I've submitted another update to CenterWindow I think you'll like. It has all the features of your suggested code plus:
      * Win95 support
      * Centers the window over the portion of the parent displayed on the monitor

      The SVN version for this update is 870.