How to find CStatic (text) bounding rect of actual content? - mfc

When I have a mfc CStatic control, I can read the control's window rect by
CStatic textStatic = GetDlgItem(IDC_TEXT_STATIC);
CRect rect;
textStatic.GetWindowRect(&rect);
ScreenToClient(rect);
However, this gives me the bounding rect of the control independent from the actual displayed text. If the text is longer than this, then the text is truncated, and if the text is shorter than this, it does not give me the 'right border' of the text.
For some dynamic resizing of dialogs, I would like to find the bounding rect (or at least the width) of the actually drawn text, not the control itself.
How do I get to this (programatically)?

You can call CDC::DrawTextEx with the DT_CALCRECT formatting option (see DrawTextEx). An appropriate device context is returned from a call to CWnd::GetDC:
CRect GetTextSize(int ctrlId) {
CWnd* pControl = GetDlgItem(ctrlId);
CString text;
pControl->GetWindowText(text);
CDC* pDC = pControl->GetDC();
CRect textRect;
pDC->DrawTextEx(text, &textRect, DT_CALCRECT, NULL);
return textRect;
}

IInspectable's answer above got me started, but was missing the bit to select the appropriate font (to deal with system font DPI setting). The following method now does what I need:
CRect GetControlTextRect(CWnd *pWnd)
{
CString text;
pWnd->GetWindowText(text);
CDC* pDC = pWnd->GetDC();
CFont* pFont = pWnd->GetParent()->GetFont();
pDC->SelectObject( pFont );
CRect textRect;
pWnd->GetWindowRect(&textRect);
pDC->DrawTextEx(text, &textRect, DT_CALCRECT, NULL);
return textRect;
}
Called f.e. like here:
CWnd * txtCtrl = GetDlgItem(IDC_STATIC); // IDC_STATIC is the resource ID of the control
CRect rect = GetControlTextRect( txtCtrl );
ScreenToClient(rect);
Or alternatively like:
CStatic txtCtrl = GetDlgItem(IDC_STATIC); // IDC_STATIC is the resource ID of the control
CRect rect = GetControlTextRect( &txtCtrl );
ScreenToClient(rect);

Related

Windows api get client dc bitmap size

I use the following code to get the size of the bitmap bound with Windows MFC View window's client area DC:
void CView::OnDraw(CDC* )
{
CDC *pDc = GetDC();
BITMAP bmpHeader;
memset( &bmpHeader, 0, sizeof(BITMAP));
HGDIOBJ hbmp = GetCurrentObject(pDc->m_hDC, OBJ_BITMAP);
GetObject(hbmp,sizeof(BITMAP), &bmpHeader);
int bmpWidth = bmpHeader.bmWidth;
int bmpHeight = bmpHeader.bmHeight;
...
}
According to MSDN GetDC() gets the client area dc:
Retrieves a pointer to a common, class, or private device context for the client area depending on the class style specified for the CWnd
So I suppose the bmpWidth and bmpHeight should be the same size as client area rect. But it isn't. It appears to be the size of entire window including toolbar area and menu area. Am I doing something wrong here?
Use GetClientRect to find with and height of client area. This is the area which does not include the titlebar and borders. Instead of calling GetDC(), use the CDC* parameter which is already provided, or else use CClientDC dc(this) which has automatic cleanup. In this case drawing should be something like this:
void CMyView::OnDraw(CDC* dc)
{
CRect rc;
GetClientRect(&rc);
dc->FillSolidRect(rc, RGB(0, 0, 255));
}
Use Window Functions to get information about Windows.
Most Window functions have equivalent in MFC. For example,
In WinApi: GetClientRect(HWND hwnd, LPRECT rc);
In MFC: CWnd::GetClientRect(LPRECT rc);

Screen capture only returns a black image

I'm trying to take a screen capture of the main dialog in my MFC application and save it as an image file. I tried about every example I could find online and always end up with the same result: the image file has the correct dimensions (I tried this with dialogs other than the main one just to be sure), but it is all black. My most recent solution is using the CBitmap class to transfer the main dialog handle to a CImage. Here is my code:
CWnd* mainWindow;
CDC* mainWindowDC;
CBitmap bitmapToSave;
CImage imageToSave;
CRect windowRect;
//Get main window context and create bitmap from it
mainWindow = AfxGetMainWnd();
mainWindowDC = mainWindow->GetWindowDC();
mainWindow->GetWindowRect(&windowRect);
bitmapToSave.CreateCompatibleBitmap(mainWindowDC, windowRect.Width(), windowRect.Height());
imageToSave.Attach(bitmapToSave);
imageToSave.Save("C:\\Capture\\image.bmp", Gdiplus::ImageFormatBMP);
Here is the way to do it:
HRESULT CaptureScreen(const CString& sImageFilePath)
{
CWnd* pMainWnd = AfxGetMainWnd();
CRect rc;
pMainWnd->GetWindowRect(rc);
CImage img;
img.Create(rc.Width(), rc.Height(), 24);
CDC memdc;
CDC* pDC = pMainWnd->GetWindowDC();
memdc.CreateCompatibleDC(pDC);
CBitmap* pOldBitmap = memdc.SelectObject(CBitmap::FromHandle((HBITMAP)img));
memdc.BitBlt(0, 0, rc.Width(), rc.Height(), pDC, 0, 0, SRCCOPY);
memdc.SelectObject(pOldBitmap);
return img.Save(sImageFilePath, Gdiplus::ImageFormatPNG);
}
Please also take a look at this nice implementation: http://www.codeguru.com/cpp/article.php/c18347/C-Programming-Easy-Screen-Capture-Using-MFCATL.htm
You created the bitmap, but you didn't put anything into it. You need to blit from one DC to another to make a copy of what's on the screen.
// ...
CMemDC dcMem;
dcMem.CreateCompatibleDC(&mainWindowDC);
CBitmap * pOld = dcMem.SelectObject(&bitmapToSave);
dcMem.BitBlt(0, 0, windowRect.Width(), windowRect.Height(), &mainWindowDC, windowRect.left, windowRect.top, SRCCOPY);
dcMem.SelectObject(pOld);
// ...

Custom CTreeCtrl - How to modify text / selection color

First of all, i would customize - among other things - color of text and selection color(text background).
For example, text color should be blue; color of text background should be transparent.
So, I have overriden OnPaint() method;
I call SetTextColor() and SetBkColor() functions, but unfortunately I always get invalid colors, or I get an annoying "infinite loop flash" effect.
Here you can see his complete implementation.
void CustomTree::OnPaint()
{
CPaintDC dc(this);
CDC memDC;
memDC.CreateCompatibleDC(&dc);
CRect rcClip, rcClient;
dc.GetClipBox( &rcClip );
GetClientRect(&rcClient);
CBitmap bitmap;
bitmap.CreateCompatibleBitmap( &dc, rcClient.Width(), rcClient.Height() );
memDC.SelectObject( &bitmap );
CRgn rgn;
rgn.CreateRectRgnIndirect( &rcClip );
memDC.SelectClipRgn(&rgn);
rgn.DeleteObject();
/* WHAT IS the correct usage of SetText/Bk Color? */
// ::SetTextColor(memDC, RGB(0, 0, 255));
// ::SetBkColor(memDC, RGB(0, 0, 255));
// COLORREF col = SetTextColor(RGB(0,0,255));
// COLORREF co2 = memDC.SetTextColor(RGB(0,0,255));
// First let the control do its default drawing.
CWnd::DefWindowProc(WM_PAINT, (WPARAM)memDC.m_hDC, 0);
// do some others stuffs...
dc.BitBlt(rcClip.left, rcClip.top, rcClip.Width(), rcClip.Height(), &memDC,
rcClip.left, rcClip.top, SRCCOPY);
memDC.DeleteDC();
}
Where is the error?
Thanks
IT
If you want to change color and font for a treeview, you have to catch and respond to NM_CUSTOMDRAW. Simply setting the properties before calling the default is insufficient.

How to draw text using CDC with transparent background on CBitmap?

I have the following code which sort of works provided you mask out the pink pixels however what I actually want is transparent bits like a PNG file so that I can avoid alpha blending issues and the need mask out a specific color everywhere the bitmap will be used.
CClientDC dc(pWnd);
CDC memDC;
if(!memDC.CreateCompatibleDC(&dc))
return NULL;
CRect bitmapRect;
bitmapRect.SetRectEmpty();
CFont* pOldFont = memDC.SelectObject(pWnd->GetFont());
CSize fontSize = memDC.GetTextExtent(imageText);
bitmapRect.right = fontSize.cx;
bitmapRect.bottom = fontSize.cy;
CBitmap bitmap;
if(!bitmap.CreateCompatibleBitmap(&dc, bitmapRect.Width(), bitmapRect.Height()))
return NULL;
CBitmap* pOldMemDCBitmap = memDC.SelectObject(&bitmap);
memDC.FillSolidRect(&bitmapRect, RGB(255,0,255));
//memDC.SetBkMode(TRANSPARENT); // doesn't work
//memDC.SetBkColor(TRANSPARENT); // doesn't work
memDC.SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
//memDC.DrawText(imageText, bitmapRect, DT_TOP|DT_LEFT|DT_NOCLIP); // same difference
memDC.TextOut(0, 0, imageText);
memDC.SelectObject(pOldMemDCBitmap);
memDC.SelectObject(pOldFont);
memDC.DeleteDC();
CImageList bmImage;
bmImage.Create(bitmapRect.Width(), bitmapRect.Height(), ILC_COLOR32|ILC_MASK, 0, 1);
// this masks out the pink but for some windows blends edges of text causing pink text instead of black!
bmImage.Add(&bitmap, RGB(255,0,255));
Is just the bug filled beast that is MFC misbehaving or am I missing something?
Simple DrawText() with transparent background without MFC:
// in my case a user drawn button:
_windowHandle = CreateWindowEx(...);
SendMessage(_windowHandle, WM_SETFONT, (WPARAM)font, (LPARAM)NULL);
...
// WM_DRAWITEM
SetTextColor(hDC, RGB(216, 27, 27));
SetBkMode(hDC, TRANSPARENT);
RECT rect = { 0, 0, backgroundBitmap.bmWidth, backgroundBitmap.bmHeight };
DrawText(hDC, _text.c_str(), -1, &rect, DT_CENTER | DT_WORDBREAK);
--hfrmobile
About 10 minutes after asking this I read my own comment and realized that "some windows" means it was related to the windows being passed in. Specifically the font being used from said window. Fonts with default properties were exhibiting the strange blending.
At the end of the day I determined I needed to modify the font to turn off the things messing up my drawing code. I eventually narrowed it down to the one culprit causing the problem:
CClientDC dc(pWnd);
CDC memDC;
if(!memDC.CreateCompatibleDC(&dc))
return NULL;
LOGFONT tempFont;
CFont* winFont = pWnd->GetFont();
if (winFont)
winFont->GetLogFont(&tempFont);
else
{
// generate a likely font
SecureZeroMemory(&tempFont, sizeof(LOGFONT));
//TEXTMETRIC txt;
//GetTextMetrics(memDC, &txt);
//tempFont.lfHeight = txt.tmHeight * -1; // 16 is too big looking
tempFont.lfHeight = -12;
tempFont.lfWeight = FW_NORMAL;
tempFont.lfCharSet = DEFAULT_CHARSET;
wcscpy_s(tempFont.lfFaceName, L"Segoe UI"); // Win7 control default
}
tempFont.lfQuality = NONANTIALIASED_QUALITY; // this is the fix!!!
CFont newFont;
newFont.CreateFontIndirect(&tempFont);
CFont* pOldFont = memDC.SelectObject(&newFont);
// ... other stuff same as before ...
So I still FillSolidRect pink then draw my icons, text, whatever I want, etc. Then mask out the pink pixels. With the font quality adjustment it no longer blends pink into the font text and it looks good. The else case above creates a default font to use in case the CWnd* passed in doesn't have a valid one specified.

Lightbox style dialogs in MFC App

Has anyone implemented Lightbox style background dimming on a modal dialog box in a MFC/non .net app.
I think the procedure would have to be something like:
steps:
Get dialog parent HWND or CWnd*
Get the rect of the parent window and draw an overlay with a translucency over that window
allow the dialog to do it's modal draw routine, e.g DoModal()
Are there any existing libraries/frameworks to do this, or what's the best way to drop a translucent overlay in MFC?
edit Here's a mockup of what i'm trying to achieve if you don't know what 'lightbox style' means
Some App:
with a lightbox dialog box
Here's what I did* based on Brian's links
First create a dialog resource with the properties:
border FALSE
3D look FALSE
client edge FALSE
Popup style
static edge FALSE
Transparent TRUE
Title bar FALSE
and you should end up with a dialog window with no frame or anything, just a grey box.
override the Create function to look like this:
BOOL LightBoxDlg::Create(UINT nIDTemplate, CWnd* pParentWnd)
{
if(!CDialog::Create(nIDTemplate, pParentWnd))
return false;
RECT rect;
RECT size;
GetParent()->GetWindowRect(&rect);
size.top = 0;
size.left = 0;
size.right = rect.right - rect.left;
size.bottom = rect.bottom - rect.top;
SetWindowPos(m_pParentWnd,rect.left,rect.top,size.right,size.bottom,NULL);
HWND hWnd=m_hWnd;
SetWindowLong (hWnd , GWL_EXSTYLE ,GetWindowLong (hWnd , GWL_EXSTYLE ) | WS_EX_LAYERED ) ;
typedef DWORD (WINAPI *PSLWA)(HWND, DWORD, BYTE, DWORD);
PSLWA pSetLayeredWindowAttributes;
HMODULE hDLL = LoadLibrary (_T("user32"));
pSetLayeredWindowAttributes =
(PSLWA) GetProcAddress(hDLL,"SetLayeredWindowAttributes");
if (pSetLayeredWindowAttributes != NULL)
{
/*
* Second parameter RGB(255,255,255) sets the colorkey
* to white LWA_COLORKEY flag indicates that color key
* is valid LWA_ALPHA indicates that ALphablend parameter
* is valid - here 100 is used
*/
pSetLayeredWindowAttributes (hWnd,
RGB(255,255,255), 100, LWA_COLORKEY|LWA_ALPHA);
}
return true;
}
then create a small black bitmap in an image editor (say 48x48) and import it as a bitmap resource (in this example IDB_BITMAP1)
override the WM_ERASEBKGND message with:
BOOL LightBoxDlg::OnEraseBkgnd(CDC* pDC)
{
BOOL bRet = CDialog::OnEraseBkgnd(pDC);
RECT rect;
RECT size;
m_pParentWnd->GetWindowRect(&rect);
size.top = 0;
size.left = 0;
size.right = rect.right - rect.left;
size.bottom = rect.bottom - rect.top;
CBitmap cbmp;
cbmp.LoadBitmapW(IDB_BITMAP1);
BITMAP bmp;
cbmp.GetBitmap(&bmp);
CDC memDc;
memDc.CreateCompatibleDC(pDC);
memDc.SelectObject(&cbmp);
pDC->StretchBlt(0,0,size.right,size.bottom,&memDc,0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);
return bRet;
}
Instantiate it in the DoModal of the desired dialog, Create it like a Modal Dialog i.e. on the stack(or heap if desired), call it's Create manually, show it then create your actual modal dialog over the top of it:
INT_PTR CAboutDlg::DoModal()
{
LightBoxDlg Dlg(m_pParentWnd);//make sure to pass in the parent of the new dialog
Dlg.Create(LightBoxDlg::IDD);
Dlg.ShowWindow(SW_SHOW);
BOOL ret = CDialog::DoModal();
Dlg.ShowWindow(SW_HIDE);
return ret;
}
and this results in something exactly like my mock up above
*there are still places for improvment, like doing it without making a dialog box to begin with and some other general tidyups.
I think you just need to create a window and set the transparency. There is an MFC CGlassDialog sample on CodeProject that might help you. There is also an article on how to do this with the Win32 APIs.