Borderless window with Aero Snap too large in maximized state - c++

I am trying to make a borderless window in Qt5.6.0, with aero-snap functionality.
Everything works, except when I maximize the window : it is too big.
My screen resolution is 2560x1440, so the window should be sized 2560x1400 (40 Pixels for the Taskbar), but in the WM_SIZE message, the new size is 2576x1416.
So the window is exactly 8 pixels too big in every direction.
This also means that the window is not aligned in the top-left corner, it is exactly 8 pixels off-screen in both directions.
I can't find a solution for this problem, everything I have tried doesn't work and causes bugs.
The only thing that fixes this is to remove the WS_CAPTION and WS_THICKFRAME styles, but then I lose the areo snap functionality.
I somehow have to tell Qt or DWM to make the window 16 pixels smaller and move it 8 pixels right, and bottom. Does anybody have an idea on how to do that?

I somehow have to tell Qt or DWM to make the window 16 pixels smaller
and move it 8 pixels right, and bottom. Does anybody have an idea on
how to do that?
DWM is Desktop Window Manager? Then the platform is Windows then.
As long as it is about Qt 5.6 and you very likely talking about the widget with Qt::CustomizeWindowHint attribute set then there is a known bug in Qt which is not fixed yet:
https://bugreports.qt.io/browse/QTBUG-4362
I stumbled upon that bug a couple of times and the workaround proposed by BiTOk at the link above worked for me.

My first try, was setting the window geometry to the available geometry:
QRect rect = QApplication::desktop()->availableGeometry();
setGeometry(rect.left() , rect.top(), rect.right(), rect.bottom());
The only Problem is that the window is a pixel too small on the right and bottom side and
setGeometry(rect.left() , rect.top(), rect.right() + 1, rect.bottom() + 1);
gives me an error:
QWindowsWindow::setGeometry: Unable to set geometry 2560x1400+0+0 on QWidgetWindow/'MainWindowWindow'. Resulting geometry: 2576x1416+-8+-8 (frame: 0, 0, 0, 0, custom margin: 0, 0, 0, 0, minimum size: 45x13, maximum size: 16777215x16777215)
Then I looked at the rectangle coordinates of Visual Studio 2015 and they are the same size as my implementation of a borderless window, 8 pixels larger in every direction.
I can give the contents of my window a margin of 8 so it doesn't clip out of the screen if the window is maximized and set the window region:
setContentsMargins({ 8, 8, 8, 8 });
HRGN WinRgn;
RECT winrect;
GetClientRect(hwnd, &winrect);
WinRgn = CreateRectRgn(8, 8, winrect.right - 8, winrect.bottom - 8);
SetWindowRgn(hwnd, WinRgn, true);
When the window gets restored, we need to reset the previous changes.
The result is:
case WM_SIZE:
WINDOWPLACEMENT wp;
wp.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(hwnd, &wp);
if (wp.showCmd == SW_MAXIMIZE) {
setContentsMargins({ 8, 8, 8, 8 });
HRGN WinRgn;
RECT winrect;
GetClientRect(hwnd, &winrect);
WinRgn = CreateRectRgn(8, 8, winrect.right - 8, winrect.bottom - 8);
SetWindowRgn(hwnd, WinRgn, true);
UpdateWindow(hwnd);
is_fullscreen = true;
} else {
if (is_fullscreen) {
setContentsMargins({ 0, 0, 0, 0 });
SetWindowRgn(hwnd, NULL, true);
is_fullscreen = false;
}
}
break;

Other posts have already answered the question, but I would just like to add that it might be a good idea to use GetSystemMetrics rather than a hard-coded value of 8.
Example
#include <Windows.h>
void MyWindow::changeEvent(QEvent* ev) {
if (ev->type() == QEvent::WindowStateChange) {
const auto state = windowState();
if(state & Qt::WindowMaximized) {
const int x = GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
const int y = GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
setContentsMargins({x, y, x, y});
}
else {
setContentsMargins({0, 0, 0, 0});
}
}

Related

How to align window to windows' taskbar

I'm trying to position my Win32 API window alongside (e.g. left-sided vertical) taskbar. My display has 2560x1600 resolution and 144 DPI. I had some problems with DPI-aware apps previously, so maybe I still don't undestand some DPI-related things. For example, now I set DPI-awarness both programmatically - setting the DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 option through the Win32 API, and adding two lines (to support Windows 7-10) to the project's manifest file:
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
Here is a code snippet, that shows how I'm setting a window's position:
RECT taskbarRect = {0, 0, 0, 0};
HWND taskbarHandle = FindWindow(L"Shell_TrayWnd", NULL);
if (taskbarHandle) {
GetWindowRect(taskbarHandle, &taskbarRect);
} else {...}
RECT notificationWindowRect; // Here is the RECT for window, I'm trying to reposition.
GetWindowRect(notificationWindowHandle, &notificationWindowRect);
LONG newX = 0;
LONG newY = 0;
bool taskbarIsVertical = (taskbarRect.Height() > taskbarRect.Width());
if (taskbarRect.left == 0 && taskbarIsVertical) { // left vertical taskbar
newX = taskbarRect.right;
newY = taskbarRect.bottom - notificationWindowRect.Height();
} else {...}
SetWindowPos(notificationWindowHandle, NULL, newX, newY, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
When system scaling is set to 100%, it's almost working - taskbarRect has a width of 63, but still there is a gap of a few pixels between the taskbar's right side and my window's left side. Note, that my window has the popup style and has no borders.
However, the main problem happens, when I set Windows' scaling to 150%. From the one hand, the taskbarRect's width becomes equal to 94, which I suppose is correct because 63 * 1.5 == 94. On the other hand, my window becomes hidden a little bit from the left side by the taskbar. To handle that, I need to add 65 pixels:
newX = taskbarRect.right + 65;
I don't understand where this 65-pixel shift appears from, and why it is exactly 65 pixels.

MFC DrawText, vertical, DT_CALCRECT with lf_escapement = 900

I'm working on a MFC project with some GDI drawings.
I use DC.DrawText to draw a vertical text into a DC using a LOGFONT with lfEscapement = 900.
The text is output when i use DT_NOCLIP in the desired vertical formatting.
However to center this text i used a call to DC.DrawText with the DT_CALCRECT argument.
I recognized that, despite the text is indeed drawn vertically, the CRect has a larger width
than height.
My intuition says me that a vertical drawn text should have a larger height than width.
I did not include the calculation for centering the text. The question is just about what i can rely upon when i implement that vertical centering.
Does DC.DrawText with DT_CALCRECT ignore escapement?
void CMFCFontTestDlg::OnPaint()
{
CPaintDC dc(this); // Gerätekontext zum Zeichnen
if (IsIconic())
{
...
}
else
{
CDialogEx::OnPaint();
CRect clTextRect;
CFont myFont;
myFont.CreateFont(12, 0, 900, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _T("Tahoma"));
CFont* oldFont = dc.SelectObject(&myFont);
dc.DrawText(_T("000000"), clTextRect, DT_CALCRECT);
clTextRect.MoveToXY(100, 100);
dc.DrawText(_T("000000"), clTextRect, DT_NOCLIP);
dc.SelectObject(oldFont);
}
}
I found out about it in the remarks to the DrawTextEx function
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-drawtextexa.
However, please note that neither the documentation of DrawText does say anything about this nor the documentation of the DT_CALCRECT flag.
This is likely to be overseen if one uses DrawText and not DrawTextEx.
I informed MS about this via "Is this page helpful?" feedback possibility.
Remarks
The DrawTextEx function supports only fonts whose escapement and
orientation are both zero.
The text alignment mode for the device context must include the
TA_LEFT, TA_TOP, and TA_NOUPDATECP flags.
Considering this the solution is to use some trigonometric calculation starting from the size determined for escapement = 0 and then calculate the topleft and bottomright points of the rotated rect.

Windows: Getting a window title bar's height

I was trying to get the height of the title bar of a specific window on Windows. You can replicate it with Notepad. I'm using C++ and none of the codes I found online yielded the correct result. Using e.g. Screenpresso I measured 31 pixels for my window bar height.
The functions I tried are the following:
TitleBarHeight.h:
#pragma once
#include <windows.h>
inline int get_title_bar_thickness_1(const HWND window_handle)
{
RECT window_rectangle, client_rectangle;
GetWindowRect(window_handle, &window_rectangle);
GetClientRect(window_handle, &client_rectangle);
return window_rectangle.bottom - window_rectangle.top -
(client_rectangle.bottom - client_rectangle.top);
}
inline int get_title_bar_thickness_2(const HWND window_handle)
{
RECT window_rectangle, client_rectangle;
GetWindowRect(window_handle, &window_rectangle);
GetClientRect(window_handle, &client_rectangle);
return (window_rectangle.right - window_rectangle.left - client_rectangle.right) / 2;
}
Results:
auto window_handle = FindWindow("Notepad", nullptr);
auto a = get_title_bar_thickness_1(window_handle); // 59
auto b = get_title_bar_thickness_2(window_handle); // 8
auto c = GetSystemMetrics(SM_CXSIZEFRAME); // 4
auto d = GetSystemMetrics(SM_CYCAPTION); // 23
Getting the system metrics with GetSystemMetrics() does not work because windows can have different title bar heights obviously and there is no argument for the window handle.
How can I really get the result of 31?
Assuming that you don't have menu bar, you can map points from client coordinate system to screen one
RECT wrect;
GetWindowRect( hwnd, &wrect );
RECT crect;
GetClientRect( hwnd, &crect );
POINT lefttop = { crect.left, crect.top }; // Practicaly both are 0
ClientToScreen( hwnd, &lefttop );
POINT rightbottom = { crect.right, crect.bottom };
ClientToScreen( hwnd, &rightbottom );
int left_border = lefttop.x - wrect.left; // Windows 10: includes transparent part
int right_border = wrect.right - rightbottom.x; // As above
int bottom_border = wrect.bottom - rightbottom.y; // As above
int top_border_with_title_bar = lefttop.y - wrect.top; // There is no transparent part
Got 8, 8, 8 and 31 pixels (96DPI aka 100% scaling setting)
You should also take into account DPI awareness mode. Especially GetSystemMetrics is tricky because it remembers state for System DPI when your application was launched.
Send a message WM_GETTITLEBARINFOEX to the window, and you will get the bounding rectangle of the title bar.
TITLEBARINFOEX * ptinfo = (TITLEBARINFOEX *)malloc(sizeof(TITLEBARINFOEX));
ptinfo->cbSize = sizeof(TITLEBARINFOEX);
SendMessage(hWnd, WM_GETTITLEBARINFOEX,0, (LPARAM)ptinfo);
int height = ptinfo->rcTitleBar.bottom- ptinfo->rcTitleBar.top;
int width = ptinfo->rcTitleBar.right - ptinfo->rcTitleBar.left;
free(ptinfo);
First, make sure your application is high DPI aware so that the system doesn't lie to you.
Options:
Trust GetSystemMetrics. Nearly any top-level window that actually has a different caption size is doing custom non-client area management which is going to make it (nearly) impossible. The obvious exception is a tool window (WS_EX_TOOLWINDOW) which probably has a SM_CYSMCAPTION height if the WS_CAPTION style is also set.
Get the target window rect and the target window's style. Use AdjustWindowRectEx to determine the size differences with the WS_CAPTION style toggled. I'm not sure if this will work because there may be some interaction between on whether you can have a caption without some kind of border.
Get the target window rect and send WM_HITTEST messages for coordinates that move down the window. Count how many of those get HT_CAPTION in return. Bonus points if you do this with a binary search rather than a linear search. This is probably the hardest and the most reliable way to do it, assuming the window has a rectangular caption area.
If I've understood correctly, it looks like you want to take the border size of the window (which we should be able to gather from the width as there is no title bar) and subtract it from the the verticle size minus the client window...
inline int get_title_bar_thickness(const HWND window_handle)
{
RECT window_rectangle, client_rectangle;
int height, width;
GetWindowRect(window_handle, &window_rectangle);
GetClientRect(window_handle, &client_rectangle);
height = (window_rectangle.bottom - window_rectangle.top) -
(client_rectangle.bottom - client_rectangle.top);
width = (window_rectangle.right - window_rectangle.left) -
(client_rectangle.right - client_rectangle.left);
return height - (width/2);
}

OpenCv border top and left OpenGL frame c++

I'm using a self compiled of OpenCv 3.3 with OPENGL and CUDA enabled on Windows 7.
I'm having trouble to display an image in fullscreen mode without any border.
I use the following minimal example for my test:
// Name of window
std::string name = "Test Window";
// Create window
cv::namedWindow(name, CV_WINDOW_OPENGL | cv::WINDOW_NORMAL);
cvSetWindowProperty(name.c_str(), CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
// Create a frame at resolution
cv::Size size = cv::Size(1920, 1080);
cv::cuda::GpuMat emptyFrame;
cv::Mat frame(size, CV_8UC(3));
// Fill it in blue
cv::rectangle(frame, cv::Rect(0, 0, size.width, size.height), cv::Scalar(255, 0, 0), CV_FILLED);
emptyFrame.upload(frame);
// Size window to full resolution
cv::resizeWindow(name, size.width, size.height);
while(1)
{
// Display an empty frame
cv::imshow(name, emptyFrame);
cv::waitKey(40);
}
This code show me a full screen windows paint in blue, however it remain a ONE pixel border on top and left border:
Grey left and top border
The border seem not to be the border as explained here:
https://stackoverflow.com/a/38494752/1570628
In fact it's the background of the main window created by OpenCv.
Digging into OpenCv code, it effectivelly create 2 windows inside cvNamedWindow function:
mainhWnd = CreateWindow( "Main HighGUI class", name, defStyle | WS_OVERLAPPED, rect.x, rect.y, rect.width, rect.height, 0, 0, hg_hinstance, 0 );
if( !mainhWnd )
CV_ERROR( CV_StsError, "Frame window can not be created" );
ShowWindow(mainhWnd, SW_SHOW);
//YV- remove one border by changing the style
hWnd = CreateWindow("HighGUI class", "", (defStyle & ~WS_SIZEBOX) | WS_CHILD, CW_USEDEFAULT, 0, rect.width, rect.height, mainhWnd, 0, hg_hinstance, 0);
if( !hWnd )
CV_ERROR( CV_StsError, "Frame window can not be created" );
So the 'border' we saw is the mainhWnd (Main HighGUI class) color.
However, it mean that my displayed image in blue is shifted by one pixel to the rigth and bottom of my screen, so I loose 1 line of pixel on bottom and right side because they overflow the screen.
I can see that it's the case because on a dual screen I can see the right line of pixel overflow on my second screen. More over, if I draw an horizontal line to the last line of my image, it doesn't appear, same occur on vertical line for last column of my image.
For testing solution, I tried to change style of mainhWnd and hWnd directly in OpenCv code by using many combination of flags, also testing using WS_POPUP, but anyway I always have this top and left border.
I also tried solution here but it do not remove the border:
https://stackoverflow.com/a/6512315/1570628
Do anyone have a clue for my problem?
Regards.
Hey this worked for me (at least it did on python, and since you just have to change a flag, i believe this will work for you too)
Change this flag "CV_WINDOW_OPENGL | cv::WINDOW_NORMAL)" to this flag "WINDOW_FREERATIO"
And voila! Problem Solved

Why is hovering over a static Win32 control increasing memory and removing my GUI?

Windows API resources can cause memory leaks if not taken care of properly. Whether that's the case or not in this issue, I'm assuming it's related. Although I show how I pinned down to where the issue is coming from, I haven't been able to solve it.
I have two types of static class controls using the Win32 API, which is abstracted in my classes:
Label
LinkLabel 
The issue: Whenever I add these two controls, Visual Studio 2017's Diagnostic Tools shows Process Memory (MB) increasing from 3MB to 11MB when I have either both setFont() or setHoverColor() lines enabled, and eventually everything in my GUI space disappears -- gone from existence, like some well-known bookstores.
This code is fine (3MB stays the same constant rate in Process Memory):
// Linked Label
myLinkLabel.init("http://www.google.com", 50, 450);
myLinkLabel.setColor(0, 0, 255);
myLinkLabel.onClick(lbl_Click);
myLinkLabel.setFont("Arial", 40, true);
//lbl.setHoverColor(255, 0, 0); 
// label
myLabel.init("A regular static label", 0, 0);
myLabel.setColor(0, 255, 0);
myLabel.setFont("Arial", 40);
//myLabel.setHoverColor(255, 0, 0);
This next code uncomments the last line. After hovering over myLabel, and the red highlight color appears, Process Memory's 3MB increases to 7MB+. It sits for a bit, then goes up to 9MB+. So, something is wrong in it. 
// Linked Label
myLinkLabel.init("http://www.google.com", 50, 450);
myLinkLabel.setColor(0, 0, 255);
myLinkLabel.onClick(lbl_Click);
myLinkLabel.setFont("Arial", 40, true);
//lbl.setHoverColor(255, 0, 0); 
// label
myLabel.init("A regular static label", 0, 0);
myLabel.setColor(0, 255, 0);
myLabel.setFont("Arial", 48);
myLabel.setHoverColor(255, 0, 0);
So, let's dig into my setHoverColor():
void Label::setHoverColor(const BYTE red, const BYTE blue, const BYTE green)
{
 m_hoverColorEnabled = true;
 m_hoverColor = RGB(red, green, blue);
}
Okay, nothing too amazing in the code above. This tells me to look in WndProc. 
The events this static control uses is WM_SETCURSOR. 
case WM_SETCURSOR:
{
HWND m_handle = (HWND)wParam;
// Label
for (int i = 0; i < frm.getLabelControlCount(); i++)
{
if (frm.getLabelControl(i).getHandle() == m_handle)
{
if (frm.getLinkLabelControl(i).isLink())
{
// Set hover color to link 
if (frm.getLabelControl(i).isHoverColorEnabled())
frm.getLabelControl(i).setColor(frm.getLabelControl(i).getHoverColor());
// Update cursor to hand
SetClassLongPtr(frm.getLabelControl(i).getHandle(), GCLP_HCURSOR, (LONG_PTR)frm.getLabelControl(i).getHoverCursor());
}
}
else
{
// Set link to blue and use default arrow 
if (frm.getLabelControl(i).isHoverColorEnabled())
frm.getLabelControl(i).setColor(0, 0, 255);
SetClassLongPtr(frm.getLabelControl(i).getHandle(), GCLP_HCURSOR,
(LONG_PTR)LoadCursor(NULL, IDC_ARROW));
}
}
 
When commenting this section of code, Process Memory stays constant at 3MB. When uncommenting this section, Process Memory increases. So, this is the main code that's causing the problem apparently. 
This section of code is basically updating the label's text color based on its current mouse hovering state. It's blue when not hovered over, and it's red when hovered over.
setColor() is the following code:
void Label::setColor(const COLORREF color)
{
 m_foreColor = color;
 setFont(m_fontName, m_fontSize, m_bold, m_italic, m_underlined);
}
Which is also calling setFont() to update it:
bool Label::setFont(const std::string &fontName, const int size, const bool bold,
 const bool italic, const bool underlined)
{ 
 DWORD dwItalic;
 DWORD dwBold;
 DWORD dwUnderlined;
 SIZE linkSize;
 dwItalic = (italic) ? TRUE : FALSE;
 dwBold = (bold) ? FW_BOLD : FW_DONTCARE;
 dwUnderlined = (underlined) ? TRUE : FALSE;
 m_font = CreateFont(size, 0, 0, 0, dwBold, dwItalic, dwUnderlined, FALSE,
  ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
  DEFAULT_PITCH | FF_SWISS, fontName.c_str());
 SendMessage(m_handle, WM_SETFONT, WPARAM(m_font), TRUE);
 
 // Calculate the correct width and height size 
 HDC hDC = GetDC(m_handle);
 SelectFont(hDC, m_font);
 GetTextExtentPoint32(hDC, m_text.c_str(), (int) m_text.length(), &linkSize);
 setSize(linkSize.cx, size); 
 
 // Store font information
 m_fontName = fontName;
 m_fontSize = size;
 m_bold = bold;
 m_underlined = underlined;
 m_italic = italic;
 
 return true;
}
My guess is this is a lot of updating for creating a font and re-creating it based on every hover. My reasoning was it wouldn't update the font color unless setting the font again. Although I see room for this in the near future, am I forgetting to delete a resource or something here? Any thoughts are welcomed. Pretty sure this will solve LinkLabel as well. 
Your basic problem is that you keep generating new fonts and never releasing the old ones.
Each time setfont is called you allocate and select a new font. But when you select the NEW font into the HDC you never clean up the old font.
SelectFont returns the previously selected font which you need (unless it is a stock font) to do a DeleteFont on.
Additionally you have a bigger resource leak on the GetDC call - the MS documentation for getDC suggests that you use a releaseDC when you have completed the usage.
As far as I understand it is NOT required to reset the font just to reset the color.