I'm trying to adapt a screensaver written in C++ with WinAPIs to work for multiple monitors. I found this article that suggests to rewrite this basic WM_PAINT handler:
case WM_PAINT:
{
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint(hWnd, &ps );
DoDrawing(hdc, ps.rcPaint);
EndPaint(hWnd, &ps);
}
break;
void DoDrawing(HDC hDC, RECT rcDraw)
{
//Do actual drawing in 'hDC'
}
Into something like this to incorporate drawing for multiple screens:
case WM_PAINT:
{
PAINTSTRUCT ps = {0};
HDC hdcE = BeginPaint(hWnd, &ps );
EnumDisplayMonitors(hdcE,NULL, MyPaintEnumProc, 0);
EndPaint(hWnd, &ps);
}
break;
BOOL CALLBACK MyPaintEnumProc(
HMONITOR hMonitor, // handle to display monitor
HDC hdc1, // handle to monitor DC
LPRECT lprcMonitor, // monitor intersection rectangle
LPARAM data // data
)
{
RECT rc = *lprcMonitor;
// you have the rect which has coordinates of the monitor
DoDrawing(hdc1, rc);
// Draw here now
return 1;
}
But the question I have is what about special optimization/clipping that BeginPaint() sets up in the DC after processing WM_PAINT message? With this approach it will be lost. Any idea how to preserve it across the EnumDisplayMonitors() call?
The answer is actually described in the MSDN docs for EnumDisplayMonitors. When you pass an HDC parameter to EnumDisplayMonitors, then the DC it passes to your callback function is a subset of the DC that you originally passed in with these changes:
The clip has been further reduced to only cover the intersection of the original clip with the monitor's clip rectangle.
The color format of the DC is for the specific monitor rather than for the "primary" monitor of the window.
Note that in modern Windows (at least since Win8), you will never really see different color formats in practice for Window DCs since GDI always runs with 32-bit color.
Related
I am trying to draw an image using GDI+. When I do it inside WM_PAINT it works:
case WM_PAINT: {
hdc = BeginPaint(hWnd, &ps);
Gdiplus::Graphics graphics(hdc);
Gdiplus::Image gdiImage(L"unt.png");
graphics.DrawImage(&gdiImage, 40, 40);
EndPaint(hWnd, &ps);
break;
}
But when I do it on a button click or inside WM_CREATE it doesn't draw the image:
HDC hdc2 = GetDC(hWnd);
Gdiplus::Graphics graphics(hdc2);
Gdiplus::Image gdiImage(L"unt.png");
graphics.DrawImage(&gdiImage, 40, 40);
Even if I use BeginPaint() and EndPaint() it still fails. So, is there any way to draw the image outside of WM_PAINT?
In Win32, almost all drawing must happen in WM_PAINT handler.
Probably you don't see any drawing because as soon you finish to handle the button click, you receive a WM_PAINT.
If you draw outside WM_PAINT your drawing have short life because of invalidation of windows and then WM_PAINT message.
So the correct way to draw in Win32 is the WM_PAINT handler.
I edited the answer after a comment of author.
Suppose you need to change the image after a mouse click. You can do:
case WM_PAINT: {
hdc = BeginPaint(hWnd, &ps);
Gdiplus::Graphics graphics(hdc);
Gdiplus::Image gdiImage(clicked ? L"image_A.png" : L"image_B.png");
graphics.DrawImage(&gdiImage, 40, 40);
EndPaint(hWnd, &ps);
break;
}
case WM_LBUTTONDOWN: {
clicked = true;
InvalidateRect(hwnd, NULL, true);
break;
}
The InvalidateRect function is the answer. With that function you tell to Windows to redraw the window. This is the link to man page:InvalidateRect
If you need to update an image based on a button click (or any other Windows Event handler for that matter), you have to invalidate the area that the image occupies on the screen—usually through InvalidateRect()—and then have your WM_PAINT handler draw the image.
It seems the flickering is generated by the CombineRgn function, but I really have no idea why this happens, since i've never used regions that much I'm possibly missing some knowledge on the matter.
Some events in the program triggers the addition of little rectangles to the main region, here's the code that handles that:
HRGN ActualRegion = CreateRectRgn(0, 0, 0, 0);
GetWindowRgn(hwnd, ActualRegion);
HRGN AddedRect = CreateRectRgn(//long code that creates a rectangle)
CombineRgn(ActualRegion, ActualRegion, AddedRect, RGN_OR);
SetWindowRgn(hwnd, ActualRegion, FALSE);
InvalidateRect(hwnd, NULL, FALSE);
White Flickering appears only after the invalidation if new regions where combined to the main one.
Here's how I'm implementing double buffering in WM_PAINT:
PLEASE NOTE that on creation i'm enabling the DWM blur behind function with an invalid region (different from the Main one) which means that everything painted with BLACK_BRUSH will result in a 100% "invisible" portion of the program
RECT r; GetClientRect(hwnd, &r);
PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps);
HDC MemDc = CreateCompatibleDC(hdc);
HBITMAP hBmp = CreateCompatibleBitmap(hdc, r.right, r.bottom);
HBITMAP hOld = (HBITMAP)SelectObject(MemDc, hBmp);
//Making sure this dc is filled with "invisible" pixels to display
SelectObject(MemDc, GetStockObject(BLACK_BRUSH));
Rectangle(MemDc, //arbitrary values that matches the entire screen);
BitBlt(hdc, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), MemDc, 0, 0, SRCCOPY);
//clean-up
SelectObject(MemDc, hOld);
DeleteObject(hBmp);
DeleteDC(MemDc);
EndPaint(hwnd, &ps);
WM_ERASEBKGND obviously returns TRUE without further handling, the WNDCLASSEX instance of the window has a default BLACK_BRUSH as the hbrBackground field.
I also tried to intercept and return TRUE from WM_NCPAINT message.
I'm doing everything necessary to avoid intermediate drawcalls, everything handled inside the WM_PAINT uses a backbuffer, also i'd like to mention i'm not working with images/bitmaps. Everything is drawn with gdi/gdi+, and in no place i'm actually issuing a "white" redraw that may possibly cause said flicker. I'm a bit lost here
Is it something that i'm possibly missing ? I can't really understand what may be causing white flickering in this scenario
The problem is not the CombineRgn function but the SetWindowRgn function which you call before the system draws the window for the first time. If you call SetWindowRgn after the first draw, no flicker. Unfortunatelly I don't know why. So, a way to counter that is to set the window region after the first draw (take the code that sets window region from WM_CREATE and leave only the DwmEnableBlurBehindWindow):
static int stc = 0;
//in WM_PAINT after the EndPaint(hwnd, &ps); add
HRESULT lr = DefWindowProc(hwnd, message, wParam, lParam);
if( stc == 0 ){
OnlyOnce();
stc++;
}
return lr;
and the OnlyOnce:
void OnlyOnce(void){
int X_Screen = GetSystemMetrics(SM_CXSCREEN);
int Y_Screen = GetSystemMetrics(SM_CYSCREEN);
HRGN ActualRegion = CreateRectRgn(X_Screen - 100, Y_Screen - 100, X_Screen - 100 + 40, Y_Screen - 100 + 40);
SetWindowRgn(hWnd, ActualRegion, true);
return;
}
i am trying to get used to WinApi and decided to make a GUI for a sudoku-generator i programmed.
It should adjust dynamicly to the windowsize the user chooses.
So far everything works as inteded, but if the WM_PAINT-msg is sent too often in a short window of time (eg changing the size of the window) the program crashes.
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
stringstream ss; //not used
RECT rect;
int w;
int h;
HBRUSH coluns=CreateSolidBrush(RGB(50,120,180));
HBRUSH colsel=CreateSolidBrush(RGB(80,150,220));
HBRUSH colmso=CreateSolidBrush(RGB(50,70,190));
switch (message)
{
case WM_SIZE: //
{
GetWindowRect(hwnd,&rect);
menu.wndw=rect.right-rect.left; //menu is a class to store important information
menu.wndh=rect.bottom-rect.top;
h=menu.wndh;
w=menu.wndw;
for(int i=1;i<10;i++)
{
for(int j=1;j<10;j++)
{
menu.feld[i][j].SetSpace((w/4)+((i-1)*(w/20))+i+(2*((i-1)/3)),(h/4)+((j-1)*(h/20))+j+(2*((j-1)/3)),(w/4)+((i)*(w/20))+i+(2*((i-1)/3)),(h/4)+((j)*(h/20))+j+(2*((j-1)/3)));
}
} //feld is a class wich exists in a 10x10 array with the 0s not being used
InvalidateRect(hwnd,NULL, TRUE);
}
break;
case WM_PAINT:
{
RECT re;
w=menu.wndw;
h=menu.wndh;
hdc = BeginPaint(hwnd,&ps);
re.left=(w/4)-4;
re.top=(h/4)-4;
re.right=(w/4)+9*(w/20)+18;
re.bottom=(h/4)+9*(h/20)+18;
FillRect(hdc,&re,CreateSolidBrush(RGB(0,0,0)));
for(int i=1;i<10;i++)
{
for(int j=1;j<10;j++)
{
re=menu.feld[i][j].GetSpace();
if(menu.feld[i][j].GetSelect()==uns)
if(FillRect(hdc,&re,coluns)==0)
MessageBox(hwnd, "fail","fail",0);
if(menu.feld[i][j].GetSelect()==mso)
if(FillRect(hdc,&re,colmso)==0)
MessageBox(hwnd, "fail","fail",0);
if(menu.feld[i][j].GetSelect()==sel)
if(FillRect(hdc,&re,colsel)==0)
MessageBox(hwnd, "fail","fail",0);
}
}
EndPaint(hwnd, &ps);
}
break;
http://www.pic-upload.de/view-22113118/Unbenannt.png.html
here is a picture of what the executed program looks like.
Now as described earlier the program will crash if u change the windowsize in a lot of small steps. After calling the MW_PAINT msg for ~10 times the window will just freeze with 1 of the rects being white instead of the desired color (random one, different every time).
my assumption is that i need to release some kind of resources because mby a stack will overflow or smth, but i have really no idea where i could have a leak in my program.
i would be very grateful if anyone could help me.
You create three brush handles every single time your window procedure executes. These handles are never tidied up. And then inside the WM_PAINT handler, you create a brush which you pass to FillRect and so can never destroy it.
So you leak three handles every time the window procedure executes (which happens a lot), and one more every time it handles WM_PAINT. Simply put, your program leaks like a sieve!
You should consider creating these brushes when the window is created, and destroying them when the window is destroyed. Or perhaps creating them inside the WM_PAINT handler, and destroying them as soon as you have finished using them. But since they have constant colors it is probably best to create 4 brushes up front, once and for all.
You are leaking GDI resources as member David Heffernan said.
Here is the example of how to properly use brushes in your application-pay attention to WM_COMMAND handler in that example.
If you do not use stock GDI objects you must delete them after you are done working with them.
Here is the simple example that fills window with red brush in WM_PAINT handler:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPain( hdc, &ps );
HBRUSH hbrRedBrush = CreateSolidBrush( RGB( 255, 0, 0 ) );
RECT r;
GetClientRect( hWnd, &r );
FillRect( hdc, &r, hbrRedBrush );
DeleteObject( hbrRedBrush ); //you must delete GDI object!
EndPaint( hWnd, &ps );
}
return 0L;
In your case, I would make 4 static brushes and rework my code a little, adding the proper cleanup in WM_CLOSE handler. Below are the suggested changes:
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
// add static before HBRUSH
static HBRUSH coluns=CreateSolidBrush(RGB(50,120,180));
static HBRUSH colsel=CreateSolidBrush(RGB(80,150,220));
static HBRUSH colmso=CreateSolidBrush(RGB(50,70,190));
static HBRUSH BlackBrush = CreateSolidBrush(RGB(0,0,0));
switch (message)
{
// this is the problematic handler
case WM_PAINT:
{
//the changed part
FillRect( hdc, &re, BlackBrush );
}
break;
case WM_CLOSE:
{
DeleteObject( BlackBrush );
DeleteObject( coluns );
DeleteObject( colsel );
DeleteObject( colmso );
// other clean up code
}
break;
IMPORTANT NOTE:
This time you used FillRect API, but next time you might load bitmaps and other stuff that require from you to restore HDC into original state after you are done with drawing.
You do that like this:
HBITMAP bmpOld = (HBITMAP)SelectObject( hdc, myBitmap );
// bmpOld stores the original state of the device context
// you do something with myBitmap
// then you return device context into original state
// by selecting the original value, bmpOld, back into device context
SelectObject( hdc, oldBmp );
DeleteObject( myBitmap );
Again, pay attention to WM_COMMAND handler in the above MSDN example to see how they did it.
Here is the link to a great Win32 API tutorial for beginners-give it a go.
In the end I recommend you this tool for detecting GDI leaks.
If you have further questions leave a comment and I will reply as soon as possible.
Best regards and good luck!
INTRODUCTION:
I have decided to make a test project in MS Visual Studio 2008, in C++ to test a small program in pure WIN32, regarding painting a bitmap as window's background.
PROBLEM:
Window should have gray brush and a bitmap picture stretched over its client area.
In my code for WM_PAINT, if I try to paint gray brush for window, without bitmap, everything seems to work well.
Same goes if I just try to put a bitmap as a background.
Yet, when I combine these two, so I can get a bitmap picture stretched, and gray background behind bitmap, this is what happens:
Bitmap picture appears to "stands still", but gray brush appears over entire window for a second, then disappears entirely, so only stretched bitmap is seen, and then appears again, and so on.
It seems as if it is drawn from top going to the bottom, and it seems as if application is doing it all over again.
This is how I see it when I start my program.
RELEVANT INFORMATION:
The program was made by choosing option File->New, and then choosing Win32 project from the options.
Window class was set automatically, and the following members were set like this:
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
I have added static global variable to store bitmap handle:
static HBITMAP bmp;
In the window procedure, made by the wizard, I have initialized it with following code:
case WM_CREATE:
bmp = LoadBitmap( hInst, MAKEINTRESOURCE(IDB_BITMAP1) );
return 0;
break;
In the window procedure, made by the wizard, I have added WM_PAINT handler, with following code:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
RECT r;
GetClientRect( hWnd, &r );
// TODO: Add any drawing code here...
// fill client area with gray brush, just to test
FillRect( hdc, &r, (HBRUSH)GetStockObject( GRAY_BRUSH ) );
// memory DC for double buffering
HDC MemDC = CreateCompatibleDC( hdc );
// select our bitmap into memory DC
HBITMAP old = (HBITMAP)SelectObject( MemDC, bmp );
// get bitmap's width and height so we can stretch it
BITMAP b;
GetObject( bmp, sizeof(BITMAP), &b );
// stretch our bitmap
StretchBlt( hdc, 0, 0, r.right - r.left, r.bottom - r.top,
MemDC, 0, 0, b.bmWidth, b.bmHeight, SRCCOPY );
// perform proper cleanup
SelectObject( MemDC, old );
DeleteDC(MemDC);
EndPaint(hWnd, &ps);
}
return 0L;
break;
I have also invalidated client area when window is resized, or erasing of background happens, like this:
case WM_ERASEBKGND:
InvalidateRect( hWnd, NULL, TRUE );
return 1L;
break;
case WM_SIZE:
InvalidateRect( hWnd, NULL, TRUE );
return 0L;
Bitmap is destroyed like this:
case WM_DESTROY:
DeleteObject( bmp );
PostQuitMessage(0);
break;
IMPORTANT NOTE:
Even if I comment out handlers for WM_SIZE and WM_ERASEBKGND, the effect still occurs.
I do not have much experience with double buffering, but this is simple thing to do.
I just fail to see the mistake, so I ask more experienced and skillfull colleagues to help.
If additional source code is required, ask for it and I will post it, but until then I will omit it to keep the question brief.
You should not call InvalidateRect in WM_ERASEBKGND. That's just going to force an endless series of paint cycles.
The job of WM_ERASEBKGND is to paint the background, and that's all you should ever do. If your WM_PAINT is going to paint the entire window, then there's no need to paint any background. In which case, and I think this is your scenario, you should do nothing in WM_ERASEBKGND.
I am trying to implement double buffering but it doesn't seem to work i.e. the graphic still flickers.
The WM_PAINT gets called everytime when the mouse moves. (WM_MOUSEMOVE)
Pasted WM_PAINT below:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
RECT rect;
GetClientRect(hWnd, &rect);
int width=rect.right;
int height=rect.bottom;
HDC backbuffDC = CreateCompatibleDC(hdc);
HBITMAP backbuffer = CreateCompatibleBitmap( hdc, width, height);
int savedDC = SaveDC(backbuffDC);
SelectObject( backbuffDC, backbuffer );
HBRUSH hBrush = CreateSolidBrush(RGB(255,255,255));
FillRect(backbuffDC,&rect,hBrush);
DeleteObject(hBrush);
if(fileImport)
{
importFile(backbuffDC);
}
if(renderWiredCube)
{
wireframeCube(backbuffDC);
}
if(renderColoredCube)
{
renderColorCube(backbuffDC);
}
BitBlt(hdc,0,0,width,height,backbuffDC,0,0,SRCCOPY);
RestoreDC(backbuffDC,savedDC);
DeleteObject(backbuffer);
DeleteDC(backbuffDC);
EndPaint(hWnd, &ps);
}
Add the following handler:
case WM_ERASEBKGND:
return 1;
The reason it works is because this message is sent before painting to ensure that painting is done on the window class's background. The flashing is going back and forth between the background and what's painted over it. Once the background has stopped being painted, it stops conflicting with what is painted over it, which includes filling the window with a solid colour, so there will still be a background anyway.