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);
}
Related
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, ¬ificationWindowRect);
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.
I'm writing a C++ wxWidgets calculator application, and I want the font of my wxTextCtrl's and my custom buttons to scale when I resize the window.
The problems are:
The text in my buttons isn't always precisely in the center, but sometimes slightly off (especially in the green and red buttons)
When I maximize the window, the wxTextCtrl's font size updates, but not when I minimize it, leaving it to cover half the screen until I resize the window, at which point it updates to the correct size.
I'm using this code:
text_controls.cpp
MainText = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER);
MainText->SetForegroundColour(wxColour(55, 55, 55));
MainText->Bind(wxEVT_TEXT, &Main::OnTextChange, this);
MainText->Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) {
evt.Skip();
MainText->SetFont(
wxFontInfo(wxSize(0, MainText->GetSize().y / 1.3))
.Family(wxFONTFAMILY_SWISS)
.FaceName("Lato")
.Bold()
);
});
And I'm using very similar code to generate the font in my custom button class file:
ikeButton.cpp
void ikeButton::render(wxDC& dc)
{
unsigned int w = this->GetSize().GetWidth();
unsigned int h = this->GetSize().GetHeight();
wxColour* bCol;
if (pressed) {
dc.SetBrush(*pressedBackgroundColor);
dc.SetTextForeground(*pressedTextColor);
bCol = pressedBorderColor;
}
else if (hovered) {
dc.SetBrush(*hoveredBackgroundColor);
dc.SetTextForeground(*hoveredTextColor);
bCol = hoveredBorderColor;
}
else {
dc.SetBrush(*backgroundColor);
dc.SetTextForeground(*textColor);
bCol = borderColor;
}
dc.SetPen(*wxTRANSPARENT_PEN);
dc.DrawRectangle(0, 0, w, h);
//bordo
if (borderTh && bCol != NULL)
{
dc.SetBrush(*bCol);
dc.DrawRectangle(0, 0, w, borderTh);
dc.DrawRectangle(w - borderTh, 0, borderTh, h);
dc.DrawRectangle(0, h - borderTh, w, borderTh);
dc.DrawRectangle(0, 0, borderTh, h);
}
//testo
dc.SetFont(
wxFontInfo(wxSize(0, this->GetSize().GetHeight() / 3))
.Family(wxFONTFAMILY_SWISS)
.FaceName("Lato")
.Light()
);
dc.DrawText(text, w / 2 - (GetTextExtent(text).GetWidth()),
h / 2 - (GetTextExtent(text).GetHeight()));
}
I'm really not sure what to do about the second problem. I think setting the font size from the size event when the frame is unmaximized causes something to be done in an unexpected order and temporarily breaks wxWidgets' layout system.
The only way I've found to workaround this so far is to use WinAPI calls to inject an extra Layout call when the window is restored. To do this, add this declaration to your frame class:
#ifdef __WXMSW__
bool MSWHandleMessage(WXLRESULT *result,WXUINT message,
WXWPARAM wParam, WXLPARAM lParam) override;
#endif // __WXMSW__
The body of the MSWHandleMessage method will need some extra constants that are defined in the wrapwin.h header. So in the code file for the frame, have this be the last #include line:
#ifdef __WXMSW__
#include <wx/msw/wrapwin.h>
#endif // __WXMSW__
And then add this body for the MSWHandleMessage
#ifdef __WXMSW__
bool MyFrame::MSWHandleMessage(WXLRESULT *result, WXUINT message,
WXWPARAM wParam, WXLPARAM lParam)
{
if ( message == WM_SYSCOMMAND && wParam == SC_RESTORE )
{
CallAfter([this](){Layout();});
// We still want to do the default processing, so do not set a result
// and return true.
}
return wxFrame::MSWHandleMessage(result, message, wParam, lParam);
}
#endif // __WXMSW__
Obviously change 'MyFrame' to the name of your frame class.
But I can help with the first one.
I think there are 2 small problems with the current calculation of where to draw the text. First, I think GetTextExtent should be called on the dc instead of the window. Second, I think there is a slight problem with order of the math operations. To center the text, I think the calculation for the x coordinate should be (w - GetTextExtent(text).GetWidth()) / 2. A similar change should be made in the calculation for the y coordinate.
I would also store the text extent calculation instead of doing it twice:
wxSize textExtent = dc.GetTextExtent(text);
dc.DrawText(text, (w - textExtent.GetWidth())/ 2,
(h - textExtent.GetHeight())/2);
Feel free to skip this next part.
You can sometimes center text vertically better by basing the calculation on a font metric named ascent instead of the text height. Here's a diagram of what these font metrics mean from Microsoft
The reason ascent might be a better number to use is that the height will include several padding elements that might make the text look slightly uncentered.
wxSize textExtent = dc.GetTextExtent(text);
wxFontMetrics metrics = dc.GetFontMetrics();
dc.DrawText(text, (w - textExtent.GetWidth()) / 2, (h - metrics.ascent) / 2);
I have an edit box that contains text, sometimes many sentences long. The edit box sits at the bottom of its parent dialog (forgive me if I'm saying everything wrong, I don't quite know what I'm doing when it comes to MFC applications). When the dialog that contains my edit box in mind is drawn to the screen, it isn't drawn quite tall enough, and it cuts off a portion of my edit box at the bottom. I was hoping to be able to calculate the height of the text that is used in the edit box, and add a few multiples of that value to the function that determines the height of the parent dialog, for consistency.
I'm not sure if this makes sense, but ultimately I am just trying to find out if it's possible to get text height of text within my edit box. I'm not sure that my fix is even possible given that the edit box is created in a completely different file in the project, but I thought it might be worth asking.
You could calculate the required text height using this basic formula:
CEdit::GetLineCount() * TEXTMETRIC::tmHeight
If the edit control has any of WS_BORDER or WS_HSCROLL styles you have to account for the gap between window size and content size which can be calculated by taking the difference between the heights of the rectangles returned by CEdit::GetWindowRect() and CEdit::GetRect() (thanks Barmak!).
The following is a function to calculate the "ideal" size of an edit control. The returned height is the required window height to fit the content. The returned width equals the original window width. You can use the parameters minLines and maxLines to make sure the returned height is such that the edit control shows at least minLines and at maximum maxLines number of lines without scrolling. Leave them at their defaults to not restrict the height.
CSize GetEditIdealSize( CEdit& edit, unsigned minLines = 0, unsigned maxLines = 0 )
{
if( CFont* pFont = edit.GetFont() )
{
// Get font information.
CClientDC dc( &edit );
auto const pOldFont = dc.SelectObject( pFont );
TEXTMETRICW tm{}; dc.GetTextMetricsW( &tm );
if( pOldFont )
dc.SelectObject( pOldFont );
// Calculate required height for the text content.
int const heightRequired = edit.GetLineCount() * tm.tmHeight;
// Make sure the edit control height stays between the given minimum/maximum.
int idealHeight = std::max<int>( heightRequired, tm.tmHeight * minLines );
if( maxLines > 0 )
idealHeight = std::min<int>( idealHeight, tm.tmHeight * maxLines );
// Get window and content rect.
CRect rcEdit; edit.GetWindowRect( rcEdit );
CRect rcContent; edit.GetRect( rcContent );
// Account for gap between window rect and content rect.
idealHeight += rcEdit.Height() - rcContent.Height();
return { rcEdit.Width(), idealHeight };
}
return {};
}
Use it like this in a member function of the parent window of the edit control to resize the edit control to fit its content:
CSize const idealSize = GetEditIdealSize( m_edit );
m_edit.SetWindowPos( nullptr, 0, 0, idealSize.cx, idealSize.cy, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
This code has been tested under Windows 10 for an edit control with the style ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_BORDER | WS_VISIBLE | WS_CHILD.
The code below calculates the size of a the rect holding the text to a checkbox. Code works fine when i'm using a stationary computer with monitors with different screen resolution. But when I run the exact same code on a laptop with an external monitor connected the box is too small. Laptop screen res is 1680x1050 and the monitor is 1920x1080.
pclRect has the same values no matter which computer I run it on.
Anybody's got an idea how to solve this?
Results:
void CForm::SetSize(CWnd *pCWnd, CRect *pclRect)
{
CDC *pclDC = m_pclPanel->GetDC();
CFont* font = pCWnd->GetFont();
LOGFONT logFont;
font->GetLogFont(&logFont);
CString str;
pCWnd->GetWindowText(str);//Get controller text
CClientDC dc(pCWnd);
dc.SelectObject(font);
int iWidth;
int iHeight;
long lFontSize = -MulDiv(logFont.lfHeight, GetDeviceCaps(pclDC->m_hDC, LOGPIXELSY), 72);
iWidth = dc.GetTextExtent(str).cx; //Get controller text length
iWidth += GetExtraWidth(); //This adds 18 to the width since it's the width of the checkbox itself
iHeight = abs(lFontSize) + GetExtraHeight();
pclRect->bottom = pclRect->top + iHeight;
pclRect->right = pclRect->left + iWidth;
pCWnd->MoveWindow(pclRect);
}
If target window is Vista or higher, use BCM_GETIDEALSIZE to find the minimum size. But check box cannot have multi-line flag (BS_MULTILINE). For example
m_checkBox.SetWindowText(L"long text xxx xxx xxx xxx xxx xxx");
SIZE sz;
if (Button_GetIdealSize(m_checkBox.m_hWnd, &sz) && sz.cx > 0 && sz.cy > 0)
{
m_checkBox.SetWindowPos(0, 0, 0, sz.cx, sz.cy, SWP_NOZORDER|SWP_NOMOVE);
}
else
{
//use another method ...
}
Otherwise, modify your code and instead of supplying 18 pixels for checkbox width, use
GetSystemMetrics to find the check box width (this results in 15 pixels in default DPI, so you have to add few more pixels for text padding).
Use GetThemePartSize if theme is active. For example:
CClientDC dc(this);
SIZE sz;
HTHEME ht = OpenThemeData(m_hWnd, L"Button");
if (ht)
{
GetThemePartSize(ht, dc, BP_CHECKBOX, CBS_CHECKEDNORMAL, NULL, TS_TRUE, &sz);
CloseThemeData(ht);
//sz.cx is 13 pixels in default DPI
}
else
{
sz.cx = GetSystemMetrics(SM_CXMENUCHECK);
//sz.cx is 15 pixels in default DPI
}
Screen resolution is not relevant here. The posted images suggest that both displays have the same DPI settings. Note that if DPI settings changes, and your application is DPI aware then sz.cx will be different.
I have refereed below article to draw a custom frame area with DWM.
Custom Window Frame Using DWM
After removing the standard frame, non client area is not exist in the frame.
void CMainFrame::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp)
{
int nTHight = 30; /*The title bar height*/
RECT * rc;
RECT aRect;
RECT bRect;
RECT bcRect;
if(bCalcValidRects == TRUE)
{
CopyRect(&aRect,&lpncsp->rgrc[1]);
CopyRect(&bRect,&lpncsp->rgrc[0]);
bcRect.left = bRect.left;
bcRect.top = bRect.top - nTHight;
bcRect.right = bRect.right;
bcRect.bottom = bRect.bottom;
CopyRect(&lpncsp->rgrc[0],&bcRect);
CopyRect(&lpncsp->rgrc[1],&bRect);
CopyRect(&lpncsp->rgrc[2],&aRect);
}
else
{
rc = (RECT *)lpncsp;
rc->left = rc->left;
rc->top = rc->top - nTHight;
rc->right = rc->right;
rc->bottom = rc->bottom;
}
CFrameWnd::OnNcCalcSize(bCalcValidRects, lpncsp);
}
Because the entire window is client region, I have to adjust the UI control placement for the frame, but I don't know how to handle this problem.
For example, below red rectangle (all UI component) should be shifted into the original coordinate of the client area before removing the non client part.
CWnd::GetWindowRect gives you the rectangle of the window on screen. The dimensions of the caption, border, and scroll bars, if present, are included.
CWnd::GetClientRect gives you the client rectangel of the window. The left and top members will be 0. The right and bottom members will contain the width and height of the window.
CWnd::ScreenToClientand CWnd::ClientToScreen calculate a point or rectangle from the client area to screen coordinates and back to screen.
AdjustWindowRect calculates the required window rectangle, based on the client rectangle of the window.
Here is afunction which calcualtes the margins of a window:
void CalculateWndMargin( const CWnd &wnd, int &leftM, int &rightM , int &topM, int &bottomM )
{
CRect wndRect;
wnd.GetWindowRect( wndRect );
CRect screenRect;
wnd.GetClientRect( screenRect );
wnd.ClientToScreen( screenRect );
leftM = screenRect.left - wndRect.left;
rightM = wndRect.right - screenRect.right;
topM = screenRect.top - wndRect.top;
bottomM = wndRect.bottom - screenRect.bottom;
}