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.
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 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);
I'm currently using 5MP Camera, so I convert BYTE* to GDI+ Bitmap object and uses Graphics object to draw on picture control (all GDI+ objects)
and I want to draw a string on it and when I do so, resolution (quality or whatsoever) gets strange. here're the images.
this is the original image
this is the image with the text on it
And here's my code. it uses MFC's WM_MOUSEMOVE. and when mouse pointer gets on CRect(dispRC[array]), it renders string "aa" on the Bitmap object.
and when I do so, quality of image gets lower or I don't exactly know it changes the IMAGE. (You might not notice because those are captured images, but latter image's quality gets lower.)
void CSmall_StudioDlg::OnMouseMove(UINT nFlags, CPoint point)
{
CPoint insidePoint;
// MAXCAM is the number of bitmap objects.
for (int i = 0; i < MAXCAM; i++)
{
// m_pBitmap[MAXCAM] is array of Bitmap* which contains address of Gdiplus::Bitmap objects.
if (m_pBitmap[i] != NULL)
{
// m_rcDisp[MAXCAM] are CRect objects which has information of picture control.
// i.e. GetDlgItem(IDC_BIN_DISP)->GetWindowRect(m_rcDisp[BINARY_VID]);
if (point.x > m_rcDisp[i].TopLeft().x && point.y > m_rcDisp[i].TopLeft().y)
{
if (point.x < m_rcDisp[i].BottomRight().x && point.y < m_rcDisp[i].BottomRight().y)
{
StringFormat SF;
insidePoint.x = point.x - m_rcDisp[i].TopLeft().x;
insidePoint.y = point.y - m_rcDisp[i].TopLeft().y;
Graphics textG(m_pBitmap[i]);
textG.SetTextRenderingHint(TextRenderingHintSingleBitPerPixel);
Gdiplus::Font F(L"Palatino Linotype Bold", 10, FontStyleBold, UnitPixel);
RectF R(insidePoint.x, insidePoint.y, 20, 100);
SF.SetAlignment(StringAlignmentCenter);
SF.SetLineAlignment(StringAlignmentCenter);
SolidBrush B(Color(0, 0, 0));
textG.DrawString(_T("aa"), -1, &F, R, &SF, &B);
// m_pGraphics[MAXCAM] is made like this
// i.e.
// static CClientDC roiDc(GetDlgItem(IDC_ROI_DISP));
// m_hDC[ROI_VID] = roiDc.GetSafeHdc();
// m_pGraphics[ROI_VID] = Graphics::FromHDC(m_hDC[ROI_VID]);
m_pGraphics[i]->DrawImage(m_pBitmap[i], 0, 0, m_vidwidth[i], m_vidheight[i]);
}
}
}
}
CDialogEx::OnMouseMove(nFlags, point);
}
Hope I get a helpful answer.
Thanks!
I had a similar problem and solved it by creating the GDI+ font from a Windows font like this:
Gdiplus::Font font(hDc, hFont);
where hDc is a DC handle and hFont is a font handle.
Can you try if this helps?
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);
}
Can you measure the width of a string more exactly in WIN32 than using the GetTextMetrics function and using tmAveCharWidth*strSize?
Try using GetTextExtentPoint32. That uses the current font for the given device context to measure the width and height of the rendered string in logical units. For the default mapping mode, MM_TEXT, 1 logical unit is 1 pixel.
However, if you've changed the mapping mode for the current device context, a logical unit may not be the same as a pixel. You can read about the different mapping modes on MSDN. With the mapping mode, you can convert the dimensions returned to you by GetTextExtentPoint32 to pixels.
I don't know for certain, but it seems that:
HDC hDC = GetDC(NULL);
RECT r = { 0, 0, 0, 0 };
char str[] = "Whatever";
DrawText(hDC, str, strlen(str), &r, DT_CALCRECT);
might work.
Graphics::MeasureString ?
VOID Example_MeasureString(HDC hdc)
{
Graphics graphics(hdc);
// Set up the string.
WCHAR string[] = L"Measure Text";
Font font(L"Arial", 16);
RectF layoutRect(0, 0, 100, 50);
RectF boundRect;
// Measure the string.
graphics.MeasureString(string, 12, &font, layoutRect, &boundRect);
// Draw a rectangle that represents the size of the string.
graphics.DrawRectangle(&Pen(Color(255, 0, 0, 0)), boundRect);
}
Depending on how you are using this, you can use DrawText with DT_CALCRECT specified and it will (its always done it fairly accurately for me) calculate the size of the required rectangle based on the text/font/etc.
For Builder C++ first make new TLabel dynamicly and then change font attributes.Set your TLabel as autosize.Then you can get you TLabel width witch represents your string width in pixels.
int WidthPixels (String font, int size, String text)
{
TLabel* label = new TLabel(Form1); // dynamic TLabel
label->AutoSize = true;
label->Font->Name = font; // your font
label->Font->Size = size; // your font size
label->Caption = text; // your string
return label->Width;
}
int width = WidthPixels("Times New Roman", 19 , "Hey");