Related
I encounter a problem with GDI objects increasing. How can I solve this problem?
The DeleteObject() function doesn`t help.
// Other Events
GetClientRect(hAnimationStars, &Dimensions);
AnimationStarsDC = BeginPaint(hAnimationStars, &ps);
MemoryDC = CreateCompatibleDC(AnimationStarsDC);
HBITMAP Bitmap = CreateCompatibleBitmap(AnimationStarsDC, Dimensions.right, Dimensions.bottom);
SelectObject(MemoryDC, Bitmap);
SetBkMode(MemoryDC, TRANSPARENT);
FillRect(MemoryDC, &Dimensions, CreateSolidBrush(BackgroundColor));
// Draw Operations
BitBlt(AnimationStarsDC, 0, 0, Dimensions.right, Dimensions.bottom, MemoryDC, 0, 0, SRCCOPY);
while (!DeleteDC(MemoryDC));
while (!DeleteObject(Bitmap));
EndPaint(hAnimationStars, &ps);
// Other Events
You need to restore any object you replace with SelectObject() before destroying the HDC. You also need to destroy the HBRUSH you are creating.
GetClientRect(hAnimationStars, &Dimensions);
HDC AnimationStarsDC = BeginPaint(hAnimationStars, &ps);
HDC MemoryDC = CreateCompatibleDC(AnimationStarsDC);
HBITMAP Bitmap = CreateCompatibleBitmap(AnimationStarsDC, Dimensions.right, Dimensions.bottom);
HBITMAP oldBmp = (HBITMAP) SelectObject(MemoryDC, Bitmap); // <-- REMEMBER THE OLD BITMAP!
SetBkMode(MemoryDC, TRANSPARENT);
HBRUSH Brush = CreateSolidBrush(BackgroundColor); // <-- REMEMBER THE BRUSH YOU CREATE!
FillRect(MemoryDC, &Dimensions, Brush);
DeleteObject(CreateSolidBrush); // <-- DESTROY THE BRUSH!
// Draw Operations
BitBlt(AnimationStarsDC, 0, 0, Dimensions.right, Dimensions.bottom, MemoryDC, 0, 0, SRCCOPY);
SelectObject(MemoryDC, oldBmp); // <-- RESTORE THE OLD BITMAP!
DeleteObject(Bitmap);
DeleteDC(MemoryDC);
EndPaint(hAnimationStars, &ps);
I'm trying to paint an Icon onto my window with the win32 api. Here is where I load the image.
case WM_CREATE: {
HANDLE image = (HICON)LoadImage(NULL, TEXT("Button.ico"), IMAGE_ICON, 16, 16, LR_LOADFROMFILE | LR_LOADTRANSPARENT);
break;
}
Here is where I try to paint the icon onto the screen.
case WM_NCPAINT: {
PAINTSTRUCT ps;
BITMAP bm;
HDC hdc = BeginPaint(ParentHwnd, &ps);
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmOld = SelectObject(hdcMem, image);
GetObject(image, sizeof(bm), &bm);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);
EndPaint(ParentHwnd, &ps);
break;
}
For some reason, I get this error on SelectObject();
E0144 a value of type "HGDIOBJ" cannot be used to initialize an entity of type "HBITMAP"
I'm using Visual Studio community 2019. I've looked all over the place for an answer. Thank you in advance for your efforts to help.
One, you are not supposed to use BeginPaint() in a WM_NCPAINT handler, only in a WM_PAINT handler. Per the WM_NCPAINT documentation, use GetDCEx() instead.
Two, you likely have STRICT Type Checking turned on (which is a good thing), that is why you are getting the compiler error. Under STRICT, an HGDIOBJ (aka void*) cannot be assigned to an HBITMAP (aka struct HBITMAP__*), so you would need to explicitly type-cast the return value of SelectObject(). However, you are loading an HICON, which you can't select as-is into an HDC, so you will have to either:
load a BMP file instead of an ICO file.
convert the HICON data to an actual HBITMAP.
use DrawIcon() or DrawIconEx().
Three, in your WM_CREATE handler, your image variable is local to that message handler, so whatever image you are accessing in the WM_NCPAINT handler is not the same variable.
Try this instead:
HBITMAP image;
...
case WM_CREATE: {
HICON icon = (HICON) LoadImage(NULL, TEXT("Button.ico"), IMAGE_ICON, 16, 16, LR_LOADFROMFILE | LR_LOADTRANSPARENT);
// convert icon to image as needed...
DestroyIcon(icon);
break;
}
case WM_NCPAINT: {
BITMAP bm;
HDC hdc = GetDCEx(hwnd, (HRGN)wParam, DCX_WINDOW | DCX_INTERSECTRGN);
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmOld = (HBITMAP) SelectObject(hdcMem, image);
GetObject(image, sizeof(bm), &bm);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);
ReleaseDC(hwnd, hdc);
break;
}
Or this:
HICON image;
...
case WM_CREATE: {
image = (HICON) LoadImage(NULL, TEXT("Button.ico"), IMAGE_ICON, 16, 16, LR_LOADFROMFILE | LR_LOADTRANSPARENT);
break;
}
case WM_DESTROY: {
DestroyIcon(image);
break;
}
case WM_NCPAINT: {
HDC hdc = GetDCEx(hwnd, (HRGN)wParam, DCX_WINDOW|DCX_INTERSECTRGN);
DrawIcon(hdc, 0, 0, image);
ReleaseDC(hwnd, hdc);
break;
}
I have a problem - I need to draw two png files, one on the other. When I do it usual way, there is a "blinking" effect (first image overdraws the second one for small time period). I use GDI+ library and my WM_PAINT handling looks like this:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint( hwnd, & ps );
displayImage(firstImage, hwnd);
displayImage(secondImage, hwnd);
EndPaint( hwnd, & ps );
break;
}
displayImage function:
void displayImage(HBITMAP mBmp, HWND mHwnd)
{
RECT myRect;
BITMAP bm;
HDC screenDC, memDC;
HBITMAP oldBmp;
BLENDFUNCTION bf;
GetObject(mBmp, sizeof(bm), &bm);
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0xff;
bf.AlphaFormat = AC_SRC_ALPHA;
screenDC = GetDC(mHwnd);
GetClientRect(mHwnd, &myRect);
if (mBmp == NULL)
FillRect(screenDC, &myRect, WHITE_BRUSH);
else
{
memDC = CreateCompatibleDC(screenDC);
oldBmp = (HBITMAP)SelectObject(memDC, mBmp);
AlphaBlend (screenDC, 0, 0, myRect.right,myRect.bottom, memDC, 0, 0, bm.bmWidth,bm.bmHeight, bf);
SelectObject(memDC, oldBmp);
DeleteDC(memDC);
ReleaseDC(mHwnd, screenDC);
}
}
Loading files to variables:
HBITMAP mLoadImg(WCHAR *szFilename)
{
HBITMAP result=NULL;
Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap(szFilename,false);
bitmap->GetHBITMAP(NULL, &result);
delete bitmap;
return result;
}
firstImage = mLoadImg(L"data\\img\\screen.png");
secondImage = mLoadImg(L"data\\img\\screen2.png");
I've heard that I should do a off-screen drawing. How should that look like?
You don't need all of that. You can use GDI+ directly:
static Gdiplus::Image *firstImage;
static Gdiplus::Image *secondImage;
case WM_CREATE: // or WM_INITDIALOG if it's dialog
{
firstImage = new Gdiplus::Image(L"data\\img\\screen.png");
secondImage = new Gdiplus::Image(L"data\\img\\screen2.png");
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
Gdiplus::Graphics gr(hdc);
gr.DrawImage(firstImage, 0, 0);
gr.DrawImage(secondImage, 0, 0);//<== this will draw transparently
EndPaint(hwnd, &ps);
return 0;
}
However, this code is still drawing 2 images back to back with possible flicker (like your original code). Use double-buffering in WM_PAINT so that only one BltBlt is done. Simply change to:
if (msg == WM_PAINT)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc;
GetClientRect(hwnd, &rc);
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
HGDIOBJ oldbmp = SelectObject(memdc, hbitmap);
FillRect(memdc, &rc, WHITE_BRUSH);
Gdiplus::Graphics gr(memdc);
gr.DrawImage(firstImage, 0, 0);
gr.DrawImage(secondImage, 0, 0);
BitBlt(hdc, 0, 0, rc.right, rc.bottom, memdc, 0, 0, SRCCOPY);
SelectObject(memdc, oldbmp);
DeleteObject(hbitmap);
DeleteDC(memdc);
EndPaint(hwnd, &ps);
return 0;
}
As for the original code:
void displayImage(HBITMAP mBmp, HWND mHwnd)
{
HDC hdc = GetDC(mHwnd);
...
}
You should change the function declaration to void displayImage(HBITMAP mBmp, HWND mHwnd, HDC hdc) then you can pass the hdc directly from WM_PAINT
First, change displayImage to take the HDC and RECT from the caller instead of the HWND.
void displayImage(HBITMAP mBmp, HDC hdc, const RECT &myRect)
{
if (mBmp == NULL)
FillRect(screenDC, &myRect, WHITE_BRUSH);
else
{
BITMAP bm;
GetObject(mBmp, sizeof(bm), &bm);
HDC memDC = CreateCompatibleDC(screenDC);
HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, mBmp);
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0xff;
bf.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(hdc, 0, 0, myRect.right, myRect.bottom, memDC, 0, 0, bm.bmWidth, bm.bmHeight, bf);
SelectObject(memDC, oldBmp);
DeleteDC(memDC);
}
}
Then, in the caller create a compatible DC and bitmap. These are your off-screen space for doing the compositing. Make the calls to displayImage with this new DC. This will compose the PNGs offscreen. Finally, blit the composed result to the actual window DC in one go.
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT myRect;
GetClientRect(hwnd, &myRect);
// Create an off-screen DC for composing the images.
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmpMem = CreateCompatibleBitmap(hdc, myRect.right, myRect.bottom);
HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcMem, hbmpMem);
// Compose the images to the offscreen bitmap.
displayImage(firstImage, hdcMem, myRect);
displayImage(secondImage, hdcMem, myRect);
// Blit the resulting composition to the window DC.
BitBlt(hdc, 0, 0, myRect.right, myRect.bottom,
hdcMem, 0, 0, SRCCOPY);
// Clean up the offscreen stuff.
SelectObject(hdcMem, hbmpOld);
DeleteObject(hbmpMem);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
break;
}
Finally, if you're still seeing a flash of the background color, see Pavan Chandaka's answer.
Handle "WM_ERASEBKGND" message by your self.
Actually before loading the second image two things happen.
WM_ERASEBKGND is triggered first to fill the image area with, whatever current windows background color is.
WM_PAINT to render action.
Documentation says to avoid blink/Flickr, provide a default handler for "WM_ERASEBKGND".
Below is the link, go to "A Control That Doesn't Flicker". You have an example too.
https://msdn.microsoft.com/en-us/library/ms969905.aspx
I've looked at multiple responses to similar questions from both this site and others, and while I feel like I've gotten closer, I just can't quite get it right. Still, this is probably a super-noobish question.
So I used to only call the WndProc case "WM_Paint" (via InvalidateRect) one every few minutes, so I didn't really notice the leak. Now I've added something that calls it about 5 times a second. In that second my memory usage jumps about 3800k. Yeah, that got noticed...
Here's the code:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
// Other cases omitted since we skip them due to "case WM_Paint:".
case WM_PAINT:
wndProc_Paint(hwnd);
break;
// Other cases omitted since we skip them due to "break;".
}
return 0;
}
void wndProc_Paint(HWND hwnd)
{
g_hbmBoard = ConvertIplImageToHBITMAP(targetBoardImg); //OpenCV command
BITMAP bm;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps); // <- Breakpoint here while monitoring mem usage shows this is what is adding ~772k per call which never gets released.
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmBoard);
GetObject(g_hbmBoard, sizeof(bm), &bm);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmGreenLight);
GetObject(g_hbmGreenLight, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 59, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmWorkingLight);
GetObject(g_hbmWorkingLight, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 94, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmWorkingIndicator);
GetObject(g_hbmWorkingIndicator, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 129, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
//DeleteObject(hbmOld);
SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);
//SelectObject(hdc, hbmOld);
//ReleaseDC(hwnd, hdc);
//DeleteDC(hdc);
//DeleteObject(hdc);
EndPaint(hwnd, &ps);
}
With the exception of the g_hbmWorkingIndicator segment and the commented out parts, this is what my WM_Paint looked like before it got called 5times/sec (even before I put it into it's own function - which was a separate issue).
The line "HDC hdc = BeginPaint(hwnd, &ps);" is where the memory gets added, and it never gets released. This happens every time we go through the function. So now for the stuff I tried in fixing the issue.
Reading up on similar issues, I assumed that I needed to Release or Delete the DC of hdc. I wasn't (still aren't - yes I've read the MSDN page) sure how SelectObject works with these, so I've tried it with and without ReleaseDC & DeleteDC (&both) for both hdc and hdcMem. I also tried DeleteObject for hdc & hdcMem. None of those had any affect.
DeleteObject(hbmOld) had an effect. It solved the issue. Except it's the wrong solution, because although I don't have the memory climbing out of control, I do have some of the visuals getting replaced with the wrong visuals. g_hbmGreenLight usually gets g_hbmBoard's graphic while g_hbmWorkingLight turns green (g_hbmGreenLight's graphic). Moving "DeleteObject(hbmOld);" to after EndPaint(~) or trying to use a "SelectObject" just changes which objects get replaced by which wrong graphic - also; returns the memory leak.
EDIT: For completeness's sake, I have included the code for ConvertIplImageToHBITMAP(IplImage* image) here. It is entirely possible that this is the culprit.
HBITMAP ConvertIplImageToHBITMAP(IplImage* pImage)
{
IplImage* image = (IplImage*)pImage;
bool imgConverted = false;
if(pImage->nChannels != 3)
{
IplImage* imageCh3 = cvCreateImage(cvGetSize(pImage), 8, 3);
if(pImage->nChannels==1){cvCvtColor(pImage, imageCh3, CV_GRAY2RGB);}
image = imageCh3;
imgConverted = true;
}
int bpp = image->nChannels * 8;
assert(image->width >= 0 && image->height >= 0 && (bpp == 8 || bpp == 24 || bpp == 32));
CvMat dst;
void* dst_ptr = 0;
HBITMAP hbmp = NULL;
unsigned char buffer[sizeof(BITMAPINFO) + 255*sizeof(RGBQUAD)];
BITMAPINFO* bmi = (BITMAPINFO*)buffer;
BITMAPINFOHEADER* bmih = &(bmi->bmiHeader);
ZeroMemory(bmih, sizeof(BITMAPINFOHEADER));
bmih->biSize = sizeof(BITMAPINFOHEADER);
bmih->biWidth = image->width;
bmih->biHeight = image->origin ? abs(image->height) : -abs(image->height);
bmih->biPlanes = 1;
bmih->biBitCount = bpp;
bmih->biCompression = BI_RGB;
if (bpp == 8)
{
RGBQUAD* palette = bmi->bmiColors;
int i;
for (i = 0; i < 256; i++)
{
palette[i].rgbRed = palette[i].rgbGreen = palette[i].rgbBlue = (BYTE)i;
palette[i].rgbReserved = 0;
}
}
hbmp = CreateDIBSection(NULL, bmi, DIB_RGB_COLORS, &dst_ptr, 0, 0);
cvInitMatHeader(&dst, image->height, image->width, CV_8UC3, dst_ptr, (image->width * image->nChannels + 3) & -4);
cvConvertImage(image, &dst, image->origin ? CV_CVTIMG_FLIP : 0);
if(imgConverted)
{cvReleaseImage(&image);}
return hbmp;
}
So, all that said: Help me StackExchange, you're my only hope! ;_;
You are losing the original HBITMAP that is returned by SelectObject(hdcMem, g_hbmBoard), so you are not restoring it correctly before calling DeleteDC(), and thus it gets leaked. You are overwriting the hbmOld variable every time you call SelectObject(), but you are not restoring the current hbmOld value back into dcMem before overwriting hbmOld again. When using SelectObject(), you MUST restore the original object when you are done making changes to the HDC.
Try this instead:
void wndProc_Paint(HWND hwnd)
{
g_hbmBoard = ConvertIplImageToHBITMAP(targetBoardImg); //OpenCV command
BITMAP bm;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmOld = (HBITMAP) SelectObject(hdcMem, g_hbmBoard); // save the original HBITMAP
GetObject(g_hbmBoard, sizeof(bm), &bm);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, g_hbmGreenLight); // returns g_hbmBoard, no need to save it to hbmpOld
GetObject(g_hbmGreenLight, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 59, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, g_hbmWorkingLight); // returns g_hbmGreenLight, no need to save it to hbmpOld
GetObject(g_hbmWorkingLight, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 94, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, g_hbmWorkingIndicator); // returns g_hbmWorkingLight, no need to save it to hbmpOld
GetObject(g_hbmWorkingIndicator, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 129, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld); // restore the original HBITMAP
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
// who owns g_hbmBoard? ConvertIplImageToHBITMAP() creates
// a new HBITMAP, so you need to free it with DeleteObject()
// before calling ConvertIplImageToHBITMAP() again. It would
// be better to create g_hbmBoard one time outside of WM_PAINT,
// recreate g_hbmBoard only when the source image actually changes,
// and then re-use g_hbmBoard as-is inside of WM_PAINT.
// ConvertIplImageToHBITMAP() really does not belong in WM_PAINT...
//
//DeleteObject(g_hbmBoard);
}
Alternatively, use SaveDC()/RestoreDC() instead:
void wndProc_Paint(HWND hwnd)
{
g_hbmBoard = ConvertIplImageToHBITMAP(targetBoardImg); //OpenCV command
BITMAP bm;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HDC hdcMem = CreateCompatibleDC(hdc);
int iOldState = SaveDC(hdcMem); // save everything the HDC currently has selected
SelectObject(hdcMem, g_hbmBoard);
GetObject(g_hbmBoard, sizeof(bm), &bm);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, g_hbmGreenLight);
GetObject(g_hbmGreenLight, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 59, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, g_hbmWorkingLight);
GetObject(g_hbmWorkingLight, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 94, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, g_hbmWorkingIndicator);
GetObject(g_hbmWorkingIndicator, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 129, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
RestoreDC(hdcMem, iOldState); // restore everything the HDC originally had selected
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
// who owns g_hbmBoard? ConvertIplImageToHBITMAP() creates
// a new HBITMAP, so you need to free it with DeleteObject()
// before calling ConvertIplImageToHBITMAP() again. It would
// be better to create g_hbmBoard one time outside of WM_PAINT,
// recreate g_hbmBoard only when the source image actually changes,
// and then re-use g_hbmBoard as-is inside of WM_PAINT...
// ConvertIplImageToHBITMAP() really does not belong in WM_PAINT...
//
//DeleteObject(g_hbmBoard);
}
I'm trying to set pixel by mouse click, but nothing happens when I click. Here is part of my code.
First, I control window size changing in WM_SIZE.
Than, at first time when I want to set pixel by mouse I get window's width and height, then copy window's content to memory HDC and HBITMAP (in Store Window) (HBITMAP size equal to (width,height)). In fact, I copy to memory only clear window.
And than in any case I set pixel to memory DC. In next WM_PAINT message handling I'm drawing memory DC to screen.
.....
case WM_SIZE:
{
CheckWidthHeight();
break;
}
case WM_MBUTTONDOWN:
{
if (firstTimeDraw)
{
CheckWidthHeight();
StoreWindow();
firstTimeDraw = false;
}
SetPixel(memoryDC, LOWORD(lParam), HIWORD(lParam), RGB(0,0,0));
break;
}
case WM_PAINT:
{
RestoreWindow();
break;
}
.....
where my functions and variables is:
HDC memoryDC;
HBITMAP memoryBitmap;
int width = 0, height = 0;
bool firstTimeDraw = true;
void CheckWidthHeight()
{
RECT clientRect;
GetClientRect(hwnd, &clientRect);
width = clientRect.right - clientRect.left;
height = clientRect.bottom - clientRect.top;
}
//Copy real window content to memory window
void StoreWindow()
{
HDC hDC = GetDC(hwnd);
memoryDC = CreateCompatibleDC(hDC);
memoryBitmap = CreateCompatibleBitmap(hDC, width, height);
SelectObject(memoryDC, memoryBitmap);
BitBlt(memoryDC, 0, 0, width, height, hDC, 0, 0, SRCCOPY);
ReleaseDC(hwnd, hDC);
}
//Copy memory windows content to real window at the screen
void RestoreWindow()
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint(hwnd, &ps);
memoryDC = CreateCompatibleDC(hDC);
SelectObject(memoryDC, memoryBitmap);
BitBlt(hDC, 0, 0, width, height, memoryDC, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
}
What I'm doing wrong?
UPD:
A shot in the dark: You're handling the middle button click. Are you by any chance clicking on the left or right mouse buttons? :)
Ok. Now I use WM_LBUTTONUP or WM_LBUTTONDOWN. Nothing happens again.
UPD2:
When you change the memory DC, you'll also want to invalidate the part of the window that is affected so that Windows will generate a WM_PAINT message for it. InvalidateRect would be a good place to start.
I placed this code
RECT rect;
GetClientRect(hwnd, &rect);
InvalidateRect(hwnd, &rect, true);
before EndPaint. Nothing. Than I move it after EndPaint. Nothing.
In the WM_PAINT handler, you need to use a DC provided by BeginPaint and call EndPaint when you're done with it.
I do it in RestoreWindow().
I don't know yet what's the problem...
UPD3:
InvalidateRect() needs to happen in the WM_?BUTTONDOWN handler after the SetPixel (not in RestoreWindow())- it's what tells windows that you want to get a WM_PAINT in the first place.
Ok. I've done it before you wrote this message. Still don't work.
UPD4:
Thank you a lot, Remy! Thank you to all the rest. Now all right!!
Two things.
When you change the memory DC, you'll also want to invalidate the part of the window that is affected so that Windows will generate a WM_PAINT message for it. InvalidateRect would be a good place to start.
In the WM_PAINT handler, you need to use a DC provided by BeginPaint and call EndPaint when you're done with it.
When you call RestoreWindow() to draw the bitmap onscreen, you are wiping out your memoryDC variable that you used to draw the pixels with. The bitmap is still selected into the original HDC that you have now lost, and a bitmap cannot be selected into multiple HDCs at the same time (the MSDN documentation for SelectObject() says as much). So you are not actually drawing the bitmap onscreen at all.
There is no need to call CreateCompatibleDC() or SelectObject() inside of RestoreWindow() because you already have the bitmap and memory HDC set up inside of StoreWindow(), so they use them as-is instead.
Try this:
HDC memoryDC = NULL;
HBITMAP memoryBitmap = NULL;
int width = 0, height = 0;
void CheckWidthHeight()
{
RECT clientRect;
GetClientRect(hwnd, &clientRect);
width = clientRect.right - clientRect.left;
height = clientRect.bottom - clientRect.top;
}
void StoreWindow()
{
HDC hDC = GetDC(hwnd);
memoryDC = CreateCompatibleDC(hDC);
memoryBitmap = CreateCompatibleBitmap(hDC, width, height);
SelectObject(memoryDC, memoryBitmap);
BitBlt(memoryDC, 0, 0, width, height, hDC, 0, 0, SRCCOPY);
ReleaseDC(hwnd, hDC);
}
void RestoreWindow()
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint(hwnd, &ps);
if (memoryDC)
BitBlt(hDC, 0, 0, width, height, memoryDC, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
}
...
case WM_SIZE:
{
CheckWidthHeight();
break;
}
case WM_LBUTTONDOWN:
{
if (!memoryDC)
StoreWindow();
if (memoryDC)
{
SetPixel(memoryDC, LOWORD(lParam), HIWORD(lParam), RGB(0,0,0));
RECT rect;
rect.left = LOWORD(lParam);
rect.top = HIWORD(lParam);
rect.right = rect.left + 1;
rect.bottom = rect.top + 1;
InvalidateRect(hwnd, &rect, TRUE);
}
break;
}
case WM_PAINT:
{
RestoreWindow();
break;
}
...