Related
After asking this question, I changed my code. It works, but when WM_PAINT paints the window and the cursor is moving in it, the painting is not done at the same time everywhere. Here you have a video to see it better. This is my WM_PAINT:
//TV is a struct with the dimensions of the window.
//BitmapBuffer is the bitmap containing the painting.
static int cont;
cont++;
HDC HDc;
PAINTSTRUCT ps;
HDc = BeginPaint(identifier, &ps);
Gdiplus::Graphics lienzo (HDc);
AlphaBlend(HDc, 0, 0, TV.width+4, TV.height+4, buffer, 0, 0, TV.width+4, TV.height+4, CopyInfo);
EndPaint(identifier, &ps);
Since the problem is when moving the mouse, maybe the WM_NCHITTEST message has something to do with it:
case WM_NCHITTEST:
POINTS point1 = MAKEPOINTS(lParam);
POINT point2;
point2.x = point1.x;
point2.y = point1.y;
ScreenToClient(hwnd, &point2);
if (PtInRegion(region, point2.x, point2.y))
{
if (inWindow == false) //inWindow is true when the cursor is in the window
{
inWindow = true;
TrackMouseEvent(&structure);
Repaint(); //this function repaint the buffer and after call invalidrect
}
return HTCLIENT;
}
else
{
if (inWindow == true)
{
inWindow = false;
Repaint();
}
return HTTRANSPARENT;
}
break;
Does anyone have an idea why this happens?
It's hard to see the problem, because your code doesn't define Repaint. However, you should be using InvalidateRect to tell Windows which area is being updated.
THAT SAID...
Windows is hard to program for updating images with out using a double buffering technique. This is when you draw to a memory (bitmap) then bitblt the bitmap to the screen.
You find this answer useful Reduce flicker with GDI+ and C++
Using C++/MFC and GDI (not GDI+), the overall goal is to create an patterned HBRUSH, which will be used in OnCtlColor to outline an edit control in red, with the ability to turn the outline on and off. To do this, you attach a bitmap to an HBRUSH using CreatePatternBrush. Here is the code for doing that, using a stored bitmap resource:
CDialog::OnInitDialog();
BOOL ok = redBoxBitmap.LoadBitmap(MAKEINTRESOURCE(IDB_mespe_EditBox_Red));
ok = redBoxBrush.CreatePatternBrush(&redBoxBitmap);
and in OnCtlColor
HBRUSH CModelEditorSpecies::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr;
int ctrlID=pWnd->GetDlgCtrlID();
if(ctrlID==IDC_MyEditControl)
hbr=(HBRUSH) redBoxBrush;
else
hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
return hbr;
}
The above code all works as desired. However, it depends on the bitmap being sized to the edit control. What I need now is the ability to create the bitmap within the C++ program, sized to the client area of the control, which depends on both the design size of the control (in the dialog editor), and the user's setting of text size in Windows 10 settings.
I cannot find a straightforward way of either constructing a bitmap, or, better, creating an empty one of the proper size (can do), selecting it into a CDC (can do), drawing the red box into it (can do), then extracting the update bitmap from the CDC (how to do?).
Can anyone suggest either how to create the bitmap programmatically, or suggest a better method of getting an edit control boxed in red when the program calls for that?
Added in response to #Constantine Georgiou's answer of 3/9:
New code:
CBitmap redBoxBitmap; // member variables of class CModelEditorSpecies
CBrush redBoxBrush;
BOOL CModelEditorSpecies::OnInitDialog()
{
CDialog::OnInitDialog();
BOOL ok;
CRect r; defaultSpecies1Ctrl.GetClientRect(&r);
xx(r.Width(), r.Height()/*, redBoxBrush*/);
ok = redBoxBrush.CreatePatternBrush(&redBoxBitmap);
//...
}
void CModelEditorSpecies::xx(const int w, const int h)
{
CDC *pDC=GetDC();
redBoxBitmap.CreateCompatibleBitmap(pDC, w, h);
// Create a red pen
CPen redPen;
redPen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
// Draw the bitmap - red pen & default background brush
CBitmap *pOldBitmap=pDC->SelectObject(&redBoxBitmap);
pDC->SelectObject(&redPen);
CBrush editBoxBrush;
editBoxBrush.CreateSysColorBrush(COLOR_WINDOW);
pDC->SelectObject(&editBoxBrush);
pDC->Rectangle(0, 0, w, h);
pDC->SelectObject(pOldBitmap);
// Create the edit-control custom brush
redBoxBrush.CreatePatternBrush(&redBoxBitmap);
return;
}
This code produces an all-black edit control, as if the bitmap being used were monochrome. That would be expected if drawing in the dc does not affect the bitmap, or, if drawing in a dc-compatible bitmap does not use the colors in redPen and editBoxBrush, as suggested by #IInspectable.
Here's how to create the brush - used the Win32 functions instead of their MFC wrappers, but you can figure it out.
BOOL CModelEditorSpecies::OnInitDialog()
{
CDialogEx::OnInitDialog();
// Get edit-control's size
RECT rc;
GetDlgItem(IDC_MyEditControl)->GetClientRect(&rc);
// Create the bitmap and a memory-DC
HDC hDC = ::GetDC(HWND_DESKTOP);
HDC mDC = CreateCompatibleDC(hDC);
HBITMAP hBmp = CreateCompatibleBitmap(hDC, rc.right, rc.bottom);
::ReleaseDC(HWND_DESKTOP, hDC);
// Create a red pen
HPEN hPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
// Draw the bitmap - red pen & default background brush
HBITMAP hOldBmp = (HBITMAP) SelectObject(mDC, hBmp);
HPEN hOldPen = (HPEN) SelectObject(mDC, hPen);
HBRUSH hOldBr = (HBRUSH) SelectObject(mDC, GetSysColorBrush(COLOR_WINDOW));
Rectangle(mDC, rc.left, rc.top, rc.right, rc.bottom);
SelectObject(mDC, hOldBr);
SelectObject(mDC, hOldPen);
SelectObject(mDC, hOldBmp);
// Create the edit-control custom brush
redBoxBrush = CreatePatternBrush(hBmp);
// Clean-up - the SysColorBrush doesn't need to be deleted
DeleteObject(hPen);
DeleteObject(hBmp);
DeleteDC(mDC);
return TRUE;
}
HBRUSH CModelEditorSpecies::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
return (nCtlColor == CTLCOLOR_EDIT && pWnd->GetDlgCtrlID() == IDC_MyEditControl) ?
redBoxBrush : CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
}
Also check the alternatives I suggested in the comments. This one will be drawing into the edit-control's client area.
EDIT:
The code I posted above does work, and after it runs all left is the HBRUSH handle (pattern-brush for your edit box). I can't see why you insist using the MFC wrappers instead. They are "higher-level" thin wrappers, so... "thin" actually, that you still have to perform almost exactly the same operations (create, select into a DC, perfrom some drawing operation, select out of the DC) as the GDI ones. The only operation you don't need to perform is delete the resource (the object's destructor will call DeleteObject() for you).
Anyways, if you prefer MFC over GDI, let's see what's wrong with your code. It has quite a few issues:
First and foremost, you need a memory DC to draw on a bitmap, the window DC you get by calling GetDC() draws on a window's surface.
The DC you get by calling GetDC() must be returned to the system (ReleaseDC()), because pDC is just a pointer, and the compiler won't call the destructor.
The objects you select into a DC must be selected out before it is destroyed, otherwise memory-leaks may be occurring.
So your code should be changed as shown below:
void CModelEditorSpecies::xx()
{
CRect r;
GetDlgItem(IDC_MyEditControl)->GetClientRect(&r);
// Create the bitmap and a memory-DC
CBitmap redBoxBitmap;
CDC mDC, *pDC = GetDC();
redBoxBitmap.CreateCompatibleBitmap(pDC, r.Width(), r.Height());
mDC.CreateCompatibleDC(pDC);
ReleaseDC(pDC);
// Create a red pen and get the default background brush
CPen redPen;
redPen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
CBrush editBoxBrush;
editBoxBrush.CreateSysColorBrush(COLOR_WINDOW);
// Draw the bitmap - red pen & default background brush
CBitmap *pOldBitmap = mDC.SelectObject(&redBoxBitmap);
CPen *pOldPen = mDC.SelectObject(&redPen);
CBrush *pOldBrush =mDC.SelectObject(&editBoxBrush);
mDC.Rectangle(r);
mDC.SelectObject(pOldBrush);
mDC.SelectObject(pOldPen);
mDC.SelectObject(pOldBitmap);
// Create the edit-control custom brush
redBoxBrush.CreatePatternBrush(&redBoxBitmap);
}
It's basically the same as the GDI version.
I am using code from this Project http://www.codeproject.com/Articles/9064/Yet-Another-Transparent-Static-Control in order to draw transparent button images from my subclassed Button control onto my CDialogEx.
This code is meant for legacy 24bpp GDI functions:
BOOL CTransparentStatic2::OnEraseBkgnd(CDC* pDC)
{
if (m_Bmp.GetSafeHandle() == NULL)
{
CRect Rect;
GetWindowRect(&Rect);
CWnd *pParent = GetParent();
ASSERT(pParent);
pParent->ScreenToClient(&Rect); //convert our corrdinates to our parents
//copy what's on the parents at this point
CDC *pDC = pParent->GetDC();
CDC MemDC;
MemDC.CreateCompatibleDC(pDC);
m_Bmp.CreateCompatibleBitmap(pDC,Rect.Width(),Rect.Height());
CBitmap *pOldBmp = MemDC.SelectObject(&m_Bmp);
MemDC.BitBlt(0,0,Rect.Width(),Rect.Height(),pDC,Rect.left,Rect.top,SRCCOPY);
MemDC.SelectObject(pOldBmp);
pParent->ReleaseDC(pDC);
}
else //copy what we copied off the parent the first time back onto the parent
{
CRect Rect;
GetClientRect(Rect);
CDC MemDC;
MemDC.CreateCompatibleDC(pDC);
CBitmap *pOldBmp = MemDC.SelectObject(&m_Bmp);
pDC->BitBlt(0,0,Rect.Width(),Rect.Height(),&MemDC,0,0,SRCCOPY);
MemDC.SelectObject(pOldBmp);
}
return TRUE;
}
However the background of my CDialogEx is being drawn with GDI+ 32bpp rendering like this:
BOOL CParentDialogEx::OnEraseBkgnd(CDC* pDC)
{
// Get GDI+ Graphics for the current Device Context
Graphics gr(*pDC);
// Get the client area
CRect clientRect;
GetClientRect(&clientRect);
// Draw the dialog background
// PLEASE NOTE: m_imgDlgBkgnd is a Gdiplus::Image with PNG format ==> 32bpp Image
gr.DrawImage(m_imgDlgBkgnd, 0, 0, clientRect.Width(), clientRect.Height());
}
This causes the first code snippet to make a backup of a black rectangle instead of the 32bpp drawn content to it. This again causes my button control to always have a black background.
To make my problem clear, please see the pictures below:
Button images are being drawn onto the CDialogEx background (normally):
Button images are being drawn with the first code snippet
As you can see GDI 24bpp cannot see the dialog background. It just assumes plain black background. Only GDI+ could see it. However I was not able to find a way to get a bitmap from a Gdiplus::Graphics object.
How can I get a 32bpp background backup in order to draw my transparent images correctly?
Using no backup images at all causes the alpha blending of GDI+ to blur the background more and more on every draw.
After 3 days of figuring around I found something by testing. This can actually be done way easier and with 32bpp rendering!
// Get the client area on the parent
CRect Rect;
GetWindowRect(&Rect);
CWnd *pParent = GetParent();
ASSERT(pParent);
pParent->ScreenToClient(&Rect);
// Get the Parent's DC
CDC *parentDC = pParent->GetDC();
// GDI Code (only 24bpp support)
//CDC MemDC;
//CBitmap bmp;
//MemDC.CreateCompatibleDC(parentDC);
//bmp.CreateCompatibleBitmap(parentDC,Rect.Width(),Rect.Height());
//CBitmap *pOldBmp = MemDC.SelectObject(&bmp);
//MemDC.BitBlt(0,0,Rect.Width(),Rect.Height(),parentDC,Rect.left,Rect.top,SRCCOPY);
//MemDC.SelectObject(pOldBmp);
// GDI+ Code with 32 bpp support (here comes the important change)
Bitmap* bmp = new Bitmap(Rect.Width(), Rect.Height());
Graphics bmpGraphics(bmp);
HDC hBmpDC = bmpGraphics.GetHDC();
BitBlt(hBmpDC, 0, 0, Rect.Width(), Rect.Height(), parentDC->GetSafeHdc(), Rect.left, Rect.top, SRCCOPY); // BitBlt is actually capable of doing 32bpp blts
// Release DCs
bmpGraphics.ReleaseDC(hBmpDC);
pParent->ReleaseDC(parentDC);
Here you go! 4 lines of code and you get a 32bpp Gdiplus::Bitmap! Which you can draw later on in OnEraseBkgnd:
// Get client Area
CRect rect;
GetClientRect(&rect);
// Draw copied background back onto the parent
Graphics gr(*pDC);
gr.DrawImage(bmp, 0, 0, rect.Width(), rect.Height());
Please note that the Bitmap should be a member variable for performance increase. It also has to be freed on the controls destruction.
Instead of Gdiplus::Image, use Gdiplus::Bitmap which has GetHBITMAP
In this example the background is set to red for png image with transparent background:
Gdiplus::Bitmap gdi_bitmap(L"c:\\test\\filename.png");
HBITMAP hbitmap = NULL;
gdi_bitmap.GetHBITMAP(Gdiplus::Color(128, 0, 0), &hbitmap);
CBitmap bmp;
bmp.Attach(hbitmap);
CDC memdc;
memdc.CreateCompatibleDC(pDC);
memdc.SelectObject(&bmp);
pDC->StretchBlt(0, 0, clientRect.Width(), clientRect.Height(), &memdc, 0, 0,
gdi_bitmap.GetWidth(), gdi_bitmap.GetHeight(), SRCCOPY);
Alternatively, you can use a brush to handle the painting:
CBrush m_Brush;
BOOL CMyDialog::OnInitDialog()
{
CDialogEx::OnInitDialog();
Gdiplus::Bitmap bitmap(L"c:\\test\\filename.png");
HBITMAP hbitmap = NULL;
bitmap.GetHBITMAP(Gdiplus::Color(128, 0, 0), &hbitmap);
CBitmap bmp;
bmp.Attach(hbitmap);
m_Brush.CreatePatternBrush(&bmp);
return 1;
}
HBRUSH CMyDialog::OnCtlColor(CDC* pDC, CWnd* wnd, UINT nCtlColor)
{
//uncomment these two lines if you only want to change dialog background
//if (wnd != this)
// return CDialogEx::OnCtlColor(pDC, wnd, nCtlColor);
if (nCtlColor == CTLCOLOR_STATIC)
{
pDC->SetTextColor(RGB(0, 128, 128));
pDC->SetBkMode(TRANSPARENT);
}
CPoint pt(0, 0);
if (wnd != this)
MapWindowPoints(wnd, &pt, 1);
pDC->SetBrushOrg(pt);
return m_Brush;
}
BOOL CMyDialog::OnEraseBkgnd(CDC* pDC)
{//don't do anything
return CDialogEx::OnEraseBkgnd(pDC);
}
I am trying to make make my ui in win32 everywhere white. The problem is that my tab control's background is not white , so the not the tab itself, but the party next to the tabs is grey.
I am handling WM_CTLCOLORSTATIC for the static controls, but it does not seems to work for the tab control.
case WM_CTLCOLORSTATIC:
{
HDC hEdit = (HDC)w_param;
SetBkMode(hEdit, TRANSPARENT);
SetTextColor(hEdit, RGB(0, 0, 0));
SetBkColor(hEdit, RGB(255, 255, 255));
// Do not return a brush created by CreateSolidBrush(...) because you'll get a memory leak
return (INT_PTR)GetStockObject(WHITE_BRUSH);
}
I hope there is an 'easy' way to make my entire ui white :)
Grz
You can not capture a message to draw on the grey background. The system draws everything in the WM_PRINTCLIENT. There is however a nice hack! The idea is from this post.
I do this (in my WM_PAINT handler):
Create a memory DC to draw into
Send a WM_PRINTCLIENT message to the tab control to get it to draw the tabs into your memory DC
Create a region which mirrors the shape of the tabs
Fill the parts of the memory DC outside this region (RGN_DIFF) with the desired background brush
Blt the result into the DC returned by BeginPaint
Call EndPaint and return, without calling the tab control's own WndProc of course :)
Step 3 is a bit fiddly as you have to know the location and shape of the tabs, but other
than that it's a pretty clean solution (see image below the following sample code). You
could probably use TransparentBlt to replace the system background colour instead.
I am using TransparentBlt in this solution:
create hdcMemTab, step 1
HBITMAP hBitmap, hBitmapOld ; //keep them till the end of the program
HDC hdcMemTab; //keep it till the end of the program
HDC hdc;
Rect rt;
hdc = CreateIC(TEXT("DISPLAY"), NULL, NULL, NULL);
hdcMemTab = CreateCompatibleDC(hdc);
GetWindowRect(hwnd_Tab, &rt);
rt.right = rt.right - rt.left;
rt.bottom = rt.bottom - rt.top;
rt.left = 0;
rt.top = 0;
hBitmap = CreateCompatibleBitmap(hdc, rt.right, rt.bottom);
hBitmapOld = SelectObject(hdcMemTab, hBitmap);
DeleteDC(hdc);
In WM_PAINT of the subclassed tab control:
RECT rt, rtTab;
HDC hdc = BeginPaint(hwnd, &ps);
GetWindowRect(hwnd_Tab, &rt);
rt.right = rt.right - rt.left;
rt.bottom = rt.bottom - rt.top;
rt.left = 0;
rt.top = 0;
//step 2
SendMessage(hwnd_Tab, WM_PRINTCLIENT, (WPARAM)hdcMemTab, PRF_CLIENT);
FillRect(hdc, &rt, gBrushWhite); //gBrushWhite has the desired background color
HRGN hRgn = CreateRectRgn(0, 0, 0, 0);
int n_items = TabCtrl_GetItemCount(hwnd_Tab);
//get tabs region, step 3
for(i = 0; i < n_items; i++){
TabCtrl_GetItemRect(hwnd_Tab, i, &rtTab);
HRGN hTabRgn = CreateRectRgn(rtTab.left, rtTab.top, rtTab.right, rt.bottom);
CombineRgn(hRgn, hRgn, hTabRgn, RGN_OR);
DeleteObject(hTabRgn);
}
GetRgnBox(hRgn, &rtTab);
DeleteObject(hRgn);
//step 5
TransparentBlt(hdc, 0, 0, rt.right, rt.bottom, hdcMemTab, 0, 0, rt.right, rt.bottom, RGB(240, 240, 240)); //(240, 240, 240) is the grey color
BitBlt(hdc, rtTab.left, rtTab.top, rtTab.right - 5, rtTab.bottom, hdcMemTab, rtTab.left, rtTab.top, SRCCOPY);
EndPaint(hwnd, &ps);
//step 6
return 0;
Sorry about my english. What I do is return 0 in WM_PRINTCLIENT message without doing anything, I mean, preventing WM_PRINTCLIENT from calling DefWindowProc. This causes the tabcontrol header to have the same background color as its parent window. The same goes for the TrackBar.
I only have it tested on windows 10, I would like to know if it works for win 7 too.
I'm creating what should be a very simple Win32 C++ app whose sole purpose it to ONLY display a semi-transparent PNG. The window shouldn't have any chrome, and all the opacity should be controlled in the PNG itself.
My problem is that the window doesn't repaint when the content under the window changes, so the transparent areas of the PNG are "stuck" with what was under the window when the application was initially started.
Here's the line where I setup the new window:
hWnd = CreateWindowEx(WS_EX_TOPMOST, szWindowClass, szTitle, WS_POPUP, 0, height/2 - 20, 40, 102, NULL, NULL, hInstance, 0);
For the call to RegisterClassEx, I have this set for the background:
wcex.hbrBackground = (HBRUSH)0;
Here is my handler for WM_PAINT message:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
Gdiplus::Graphics graphics(hdc);
graphics.DrawImage(*m_pBitmap, 0, 0);
EndPaint(hWnd, &ps);
break;
}
One thing to note is that the application is always docked to the left of the screen and doesn't move. But, what's underneath the application may change as the user opens, closes or moves windows under it.
When the application first starts, it looks perfect. The transparent (and simi-transparent) parts of the PNG show through perfectly. BUT, when the background underneath the application changes, the background DOESN'T update, it just stays the same from when the application first started. In fact, WM_PAINT (or WM_ERASEBKGND does not get called when the background changes).
I've been playing with this for quite a while and have gotten close to getting 100% right, but not quite there. For instance, I've tried setting the background to (HBRUSH) NULL_BRUSH and I've tried handling WM_ERASEBKGND.
What can be done to get the window to repaint when the contents under it changes?
I was able to do exactly what I wanted by using the code from Part 1 and Part 2 of this series:
Displaying a Splash Screen with C++
Part 1: Creating a HBITMAP archive
Part 2: Displaying the window archive
Those blog posts are talking about displaying a splash screen in Win32 C++, but it was almost identical to what I needed to do. I believe the part that I was missing was that instead of just painting the PNG to the window using GDI+, I needed to use the UpdateLayeredWindow function with the proper BLENDFUNCTION parameter. I'll paste the SetSplashImage method below, which can be found in Part 2 in the link above:
void SetSplashImage(HWND hwndSplash, HBITMAP hbmpSplash)
{
// get the size of the bitmap
BITMAP bm;
GetObject(hbmpSplash, sizeof(bm), &bm);
SIZE sizeSplash = { bm.bmWidth, bm.bmHeight };
// get the primary monitor's info
POINT ptZero = { 0 };
HMONITOR hmonPrimary = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO monitorinfo = { 0 };
monitorinfo.cbSize = sizeof(monitorinfo);
GetMonitorInfo(hmonPrimary, &monitorinfo);
// center the splash screen in the middle of the primary work area
const RECT & rcWork = monitorinfo.rcWork;
POINT ptOrigin;
ptOrigin.x = 0;
ptOrigin.y = rcWork.top + (rcWork.bottom - rcWork.top - sizeSplash.cy) / 2;
// create a memory DC holding the splash bitmap
HDC hdcScreen = GetDC(NULL);
HDC hdcMem = CreateCompatibleDC(hdcScreen);
HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcMem, hbmpSplash);
// use the source image's alpha channel for blending
BLENDFUNCTION blend = { 0 };
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
// paint the window (in the right location) with the alpha-blended bitmap
UpdateLayeredWindow(hwndSplash, hdcScreen, &ptOrigin, &sizeSplash,
hdcMem, &ptZero, RGB(0, 0, 0), &blend, ULW_ALPHA);
// delete temporary objects
SelectObject(hdcMem, hbmpOld);
DeleteDC(hdcMem);
ReleaseDC(NULL, hdcScreen);
}
Use the SetLayeredWindowAttributesarchive function, this allows you to set a mask color that will become transparent, thus allowing the background to show through.
You will also need to configure your window with the layered flag, e.g.:
SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
After that it's fairly simple:
// Make red pixels transparent:
SetLayeredWindowAttributes(hwnd, RGB(255,0,0), 0, LWA_COLORKEY);
When your PNG contains semi-transparent pixels that you want to blend with the background, this becomes more complicated. You could try looking at the approach in this CodeProject article:
Cool, Semi-transparent and Shaped Dialogs with Standard Controls for Windows 2000 and Above