3

Our 3d application window needs to cover the whole desktop of the user, including the task bar (not resizable, not maximized, not full screen as seen in video games, just a window with the [X] in the top-right corner, the title bar, and the status bar).

To achieve this, we used the following code:

void
CTheApp::SetFullScreenMode()
{
  HWND hwnd = getHandle();

  long style = ::GetWindowLong(hwnd, GWL_STYLE);
  style &= ~(WS_MAXIMIZEBOX|WS_MINIMIZEBOX|WS_THICKFRAME);
  ::SetWindowLong(hwnd, GWL_STYLE, style);

  // get screen dimensions
  int width  = ::GetSystemMetrics(SM_CXSCREEN);
  int height = ::GetSystemMetrics(SM_CYSCREEN);

  ::MoveWindow(hwnd, 0, 0, width, height, TRUE);
}

It worked for our needs... until Windows 10 arrived.

With Windows 10, there is a small gap on the left, right and bottom that the window does not cover. (More precisely: 9 pixels on the left, 9 pixels on the right, and 9 pixels on the bottom.)

It seems that Windows 10 adds this transparent style to the border of non-maximized windows.

How could I make it so that the window would stretch visually correctly on the whole screen in Windows 10 (leave no apparent gap), while keeping the feature valid for previous versions of Windows (Windows 7 - Windows 8.1)? Is there a clean way to set this "transparency" to zero?


I thought about hacking it and expanding the sizes by 9 pixels on each side, but this could cause issues when the user has another Theme set up (e.g. High contrast), or when the app will be using multiple windows on multiple monitors.

Vaillancourt
  • 1,380
  • 1
  • 11
  • 42

2 Answers2

3

I found two ways to achieve this.

By using the DwmGetWindowAttribute function

With this approach, we MoveWindow twice. The first time we move it will position the window and calculate all its size information. This will allow us to get two aspects of its dimensions. And the second time, we'll take into account the "drop shadow" border. For this to work, we need to ShowWindow( SW_SHOW ); before we MoveWindow.

void MyCFrameWndMain::applyWindowSizeAndShow()
{
  ShowWindow( SW_SHOW );
  
  int targetWidth = ::GetSystemMetrics( SM_CXSCREEN );
  int targetHeight = ::GetSystemMetrics( SM_CYSCREEN );

  MoveWindow( 0, 0, targetWidth, targetHeight, TRUE );
  
  RECT decoratedRect;
  // This gets the rectangle "with the decoration":
  GetWindowRect( &decoratedRect ); 
  RECT leanRECT;
  // This gets the rectangle "without the decoration":
  ::DwmGetWindowAttribute( m_hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &leanRECT, sizeof( leanRECT ) ); 
  
  int leftDecoration   = leanRECT.left        - decoratedRect.left;
  int rightDecoration  = decoratedRect.right  - leanRECT.right;
  int topDecoration    = leanRECT.top         - decoratedRect.top;
  int bottomDecoration = decoratedRect.bottom - leanRECT.bottom;
  
  // Apparently, the "rectangle without the decoration" still has a 1 pixel border on the sides and 
  // at the bottom, so we use the GetSystemMetrics to figure out the size of that border, and 
  // correct the size.
  int xBorder = ::GetSystemMetrics( SM_CXBORDER );
  int yBorder = ::GetSystemMetrics( SM_CYBORDER );

  MoveWindow( 
    0 - leftDecoration - xBorder
    , 0 - topDecoration - yBorder
    , targetWidth + leftDecoration + rightDecoration + 2*xBorder
    , targetHeight + topDecoration + bottomDecoration + 2*yBorder
    , TRUE );
}

This approach seemed to work better than the other.

By handling the WM_NCCALCSIZE message

Using this method, the idea is to MoveWindow before it is shown.

void MyCFrameWndMain::applyWindowSizeAndShow()
{
  int targetWidth = ::GetSystemMetrics( SM_CXSCREEN );
  int targetHeight = ::GetSystemMetrics( SM_CYSCREEN );

  MoveWindow( 0, 0, targetWidth, targetHeight, TRUE );

  ShowWindow( SW_SHOW );
}

and then handling the WM_NCCALCSIZE message:

LRESULT MyCFrameWndMain::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message)
  {
  
  // ...
  
  case WM_NCCALCSIZE:
  { // https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize?redirectedfrom=MSDN
    int titleBarHeight = ::GetSystemMetrics( SM_CYCAPTION );
    if ( wParam == TRUE )
    {
      NCCALCSIZE_PARAMS* params = reinterpret_cast<NCCALCSIZE_PARAMS*>( lParam );
      params->rgrc[0].top = titleBarHeight;
      return WVR_REDRAW;
    }
    else //if ( wParam == FALSE )
    {
      RECT* rect = reinterpret_cast<RECT*>( lParam );
      rect->top = titleBarHeight;
      return 0;
    }
  }
  break;

  // ...

  }
}

The drawbacks I found using this method:

  • there was a one pixel line between the top edge of the window and the top edge of the screen
  • when I had two monitors that did not use the same scaling (e.g. the main one being at 150% and the second being at 100%), when I dragged the window using the title bar from one monitor to the other, the title bar would vanish and everything would be all messed up. I have not been able to reproduce this issue when both were at 100%. This should not be an issue when the typical usage is to not move the window (which is our case).
  • I suppose I'm not too sure how this all works, but it seemed that some of the window elements were a bit out of place.

If I had not been able to find the first method, this second method would have worked well enough for our needs, even if it has a couple of issues.


I've used those answers (among other resources):

Vaillancourt
  • 1,380
  • 1
  • 11
  • 42
1

This is handled by the Desktop Window Manager and can be overridden. The chief step is WM_NCCALCSIZE (window message, non client) which you override to return 0,0

MSalters
  • 173,980
  • 10
  • 155
  • 350