Calculate font "size" in DIPs of a system font - c++

I have few BS_OWNERDRAWN buttons and using Direct2D and Direct Write to draw them.
I also need to draw button text within button rectangle, for this I use IDWriteTextFormat which requires to specify "font size" in DIPs (device independent pixels).
I want font size in those buttons to be of same size as other non owner drawn common controls or same as system font that is present in window caption bar.
Following code is "chopped out" version to present my workaround which of course doesn't give expected results because I get LOGFONT structure of a font in caption bar which gives me the width of a font (single character) but not font size that the IDWriteTextFormat expects to specify font size in DIPs.
class CustomControl
{
protected:
/** Caption text format used to draw text */
CComPtr<IDWriteTextFormat> mpTextFormat;
/** Caption font size (button text size) */
float mFontSize;
};
// Calculate caption bar (default) font size of a top level window
void CustomControl::CalculateFontSize()
{
NONCLIENTMETRICSW metrics;
metrics.cbSize = sizeof(NONCLIENTMETRICSW);
SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0);
LOGFONTW font = metrics.lfCaptionFont;
mFontSize = static_cast<float>(font.lfHeight);
}
// Create text format that is of same font size as default system font
HRESULT CustomControl::CreateTextFormat()
{
HRESULT hr = S_OK;
if (!mpTextFormat)
{
CalculateFontSize();
hr = mpWriteFactory->CreateTextFormat(
L"Arial",
NULL,
DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
mFontSize, // <-- Specifies font size in DIP's..
L"en-us",
&mpTextFormat);
}
return hr;
}
Here is test program that shows the differences in font size between default system font in window caption and custom button text below.
I need help in figuring out how to correctly calculate font size for IDWriteTextFormat parameter to be of same size as text in other common controls that are not BS_OWNERDRAW or in this example default font in window caption.
EDIT:
I figured out issue is in my CalculateFontSize() I wrote mFontSize = static_cast<float>(font.lfHeight); but this is negative number so appending - sign gives the expected result:
mFontSize = static_cast<float>(-font.lfHeight);
Why negative? I'm not sure yet but this answer helped:
How to set font size using CreateFontA?
Now my question remains in that how should I update CalculateFontSize() so that it gets font size of common controls that are not BS_OWNERDRAW instead of a window caption bar font size?

I figured out to calculate font size of other common controls to be used for IDWriteTextFormat the formula is simple:
float CustomControl::CalculateFontSize()
{
const long units = GetDialogBaseUnits();
const DWORD height = HIWORD(units);
return static_cast<float>(height);
}
Only problem with this is if you use custom font for common controls, or if your dialog uses different font then you need to update your CalculateFontSize() to take these changes into account.
However for your TextFormat to be truly consistent with native common controls you also need to apply font weight (boldness), for example after you create TextLayout (by using your TextFormat):
std::size_t caption_len = 0;
StringCchLengthW(mCaption, STRSAFE_MAX_CCH, &caption_len);
DWRITE_TEXT_RANGE range = { 0u, caption_len };
mpTextLayout->SetFontSize(mFontSize, range);
mpTextLayout->SetFontWeight(DWRITE_FONT_WEIGHT::DWRITE_FONT_WEIGHT_BOLD, range);

Related

How to get the handle of the active window in win32 using c++?

[![enter image description here][1]][1]I am trying to capture the active window in Win32 using C++. With the BitBlt function I am able to capture, but once another window opens, the same window which I have already captured should only be captured. I don't want the other window which I have opened, it should be black. Can someone help with a solution?
https://www.codeproject.com/Articles/20367/Screen-Capture-Simple-Win32-Dialog-Based
void CaptureActiveWindow(void)
{
RECT ActWndRect;
WCHAR buf [100],buf1[20];
int xSrc=0,ySrc=-19;
int DepcWidth=10, DepcHeight=5;
OutputDebugString(L"Start capture act window ");
HDC ActWndDC = GetDC(hWndActWnd); //DC for the window you have clicked on
MemDC = CreateCompatibleDC(ActWndDC); //Memory DC Compatible with Above DC
GetWindowRect(hWndActWnd,&ActWndRect); //Will Store the Windows Are in Rectangle
wsprintf(buf,L"x1 = %d , y1 = %d, x2 = %d y2 =%d",ActWndRect.left,ActWndRect.top,ActWndRect.right,ActWndRect.bottom);
OutputDebugString(buf);
int Width = ActWndRect.right-ActWndRect.left; //Width of the Window
int Height =ActWndRect.bottom-ActWndRect.top; //Hight of the Window
if(GetWindowText(hWndActWnd,buf1,20) >0)
{
OutputDebugString(buf1);
}
if(CaptureControl)
{
ySrc= DepcWidth = DepcHeight = 0;
}
HBITMAP hBitmap = CreateCompatibleBitmap(DlgDC,Width-DepcWidth,Height-DepcHeight);//Will Create Bitmap Comatible With Our Window
SelectObject(MemDC,hBitmap);
BitBlt(MemDC,0,0,Width,Height,ActWndDC,xSrc,ySrc,SRCCOPY);//Will Copy the Window into MemDC
//BitBlt(DeskDC,110,110,Width,Height,MemDC,Begpt.x,Begpt.y,SRCCOPY);
SaveBitmap(MemDC, hBitmap,"Sample.bmp"); // will Save DC into .bmp File
ShowImage(); //Will Show u the .bmp File in MSPAINT.
}
Hook the mouse event Before sending active message to the window. Use WindowFromPoint to get the specified window(Hwnd). Then use GetWindowRect to get the window rect area. In this area, call WindowFromPoint for all the point in the rect, compare it with Hwnd(if it is a child window or not), and get the overlap RECT. After getting the bitmap of the capture window and then overwrites the black on the covered rect.
PS: I encounter BITMAPINFO error: Run-Time Check Failure #2 - Stack around the variable was corrupted.
Here provide a solution.
You can't capture the image of Chrome using BitBlt(), unless disable the Hardware Acceleration option of Chrome. But PrintWindow() works with PW_RENDERFULLCONTENT flag. When use it, the image in center will have a black border. While using PrintWindow (hWndActWnd,ActWndDC,0x00000003) align the image to the left.Then modify cx and cy of CreateCompatibleBitmap(), you can remove the border easily.

Any way to retrieve text height, in pixels, of text within a CEditBox?

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.

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);
}

Different result on laptop and stationary computer

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.

MFC: Dynamically change control font size?

I have a CListCtrl class that I'd like to be able to easily change the font size of. I subclassed the CListCtrl as MyListControl. I can successfully set the font using this code in the PreSubclassWindow event handler:
void MyListControl::PreSubclassWindow()
{
CListCtrl::PreSubclassWindow();
// from http://support.microsoft.com/kb/85518
LOGFONT lf; // Used to create the CFont.
memset(&lf, 0, sizeof(LOGFONT)); // Clear out structure.
lf.lfHeight = 20; // Request a 20-pixel-high font
strcpy(lf.lfFaceName, "Arial"); // with face name "Arial".
font_.CreateFontIndirect(&lf); // Create the font.
// Use the font to paint a control.
SetFont(&font_);
}
This works. However, what I'd like to do is create a method called SetFontSize(int size) which will simply change the existing font size (leaving the face and other characteristics as is). So I believe this method would need to get the existing font and then change the font size but my attempts to do this have failed (this kills my program):
void MyListControl::SetFontSize(int pixelHeight)
{
LOGFONT lf; // Used to create the CFont.
CFont *currentFont = GetFont();
currentFont->GetLogFont(&lf);
LOGFONT lfNew = lf;
lfNew.lfHeight = pixelHeight; // Request a 20-pixel-high font
font_.CreateFontIndirect(&lf); // Create the font.
// Use the font to paint a control.
SetFont(&font_);
}
How can I create this method?
I found a working solution. I'm open to suggestions for improvement:
void MyListControl::SetFontSize(int pixelHeight)
{
// from http://support.microsoft.com/kb/85518
LOGFONT lf; // Used to create the CFont.
CFont *currentFont = GetFont();
currentFont->GetLogFont(&lf);
lf.lfHeight = pixelHeight;
font_.DeleteObject();
font_.CreateFontIndirect(&lf); // Create the font.
// Use the font to paint a control.
SetFont(&font_);
}
The two keys to getting this to work were:
Removing the copy of the LOGFONT, lfNew.
Calling font_.DeleteObject(); before creating a new font. Apparently there can't be an existing font object already. There is some ASSERT in the MFC code that checks for an existing pointer. That ASSERT is what was causing my code to fail.