Preserve painted client area between calls to WM_PAINT in win32 - c++

I am trying to paint(draw) text on the client area on a window in response to some event(not in the WM_PAINT message), so how can I preserve the state of the client area between calls to WM_PAINT? I understand that every time there is a WM_PAINT message(or window refreshes) the window gets redrawn and everything out side WM_PAINT doesn't matter anymore. I think I will be able to communicate better with code, so here is what I have now.
HDC mdc;
int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance,
LPSTR lpszArgument, int nFunsterStil)
{
LoadBitmap(...); // for skinning the app.
stuff..
}
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
BITMAP bm;
HDC dcSkin = CreateCompatibleDC(hdc);
GetObject(hSkinBmp, sizeof(bm), &bm);
SelectObject(dcSkin, hSkinBmp);
BitBlt(dcSkin, 0, 0, wWidth, wHeight, mdc, 0, 0, SRCCOPY);
BitBlt(hdc, 0, 0, wWidth, wHeight, dcSkin, 0, 0, SRCCOPY);
DeleteDC(dcSkin);
EndPaint(hwnd, &ps);
break;
case WM_LBUTTONDOWN;
HDC hdc = GetDC( hwnd );
mdc = CreateCompatibleDC( hdc );
LPRECT rect;
GetClientRect( hwnd, rect);
SelectObject( mdc, CreateCompatibleBitmap( hdc, rect->right, rect->bottom ) );
BitBlt( mdc,0,0,rect->right,rect->bottom,hdc,0,0,SRCCOPY );
HFONT hfont = CreateFont( 0, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
PROOF_QUALITY, DEFAULT_PITCH | FF_SWISS , 0 );
HFONT hOldFont = (HFONT)SelectObject( mdc, hfont );
SetTextColor( mdc, RGB(255,0,0) );
SetBkColor( mdc, RGB(255,255,0) );
TextOut( mdc, 50, 150, logintext.c_str(), strlen( logintext.c_str() ) );
SelectObject( mdc, hOldFont );
ReleaseDC( hwnd, hdc);
InvalidateRect( hwnd, 0, TRUE );
break;
}
As you can see, I have to paint the client area of the window with a bmp, then when mouse button down message comes, I have to output some text on top of the skinned client area.
What I am doing is saving the memory dc created in the WM_LBUTTONDOWN message and trying to paint the window dc first with the dc for skin then the dc for text(memory dc created in WM_LBUTTONDOWN).
The skin bitmap show as it is supposed to, but the Text doesn't.
How do I achieve this ?

If I understand you correctly you want to draw things outside of WM_PAINT and have them persist. Unfortunatly that's not how windows works.
You need to be able to redraw everything in your WM_PAINT handler as the contents of the window can be destroyed at any moment and redrawn, so you have to structure your program to be able to do this. Instead of drawing anything outside of the WM_PAINT hander instead set some flags or other state telling the program what should be on the screen and then invalidate the area of the screen so that a WM_PAINT will be issued to draw that area.
One alternative which might be easier for you is to draw everything to an offscreen bitmap whenever it needs updating, and have the WM_PAINT function just draw that bitmap on the screen so it's always up to date.

Related

C++ winapi Taking Screenshot and Making It Background of Window

I'm trying to make the snippingtool in c++. I managed to create a borderless, fullscreen window via this code;
WindProc:
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
switch (message)
{
case WM_CHAR: //this is just for a program exit besides window's borders/taskbar
if (wparam==VK_ESCAPE)
{
DestroyWindow(hwnd);
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wparam, lparam);
}
}
Creating the window;
WNDCLASS windowClass={0};
windowClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
windowClass.hCursor=LoadCursor(NULL, IDC_ARROW);
windowClass.hInstance=NULL;
windowClass.lpfnWndProc=WndProc;
windowClass.lpszClassName=TEXT("Window in Console"); //needs to be the same name
//when creating the window as well
windowClass.style=CS_HREDRAW | CS_VREDRAW;
//also register the class
if (!RegisterClass(&windowClass))
MessageBoxA(NULL, "Could not register class", "Error", MB_OK);
HWND windowHandle=CreateWindowA("Window in Console",
NULL,
WS_POPUP, //borderless
0, //x coordinate of window start point
0, //y start point
GetSystemMetrics(SM_CXSCREEN), //width of window
GetSystemMetrics(SM_CYSCREEN), //height of the window
NULL, //handles and such, not needed
NULL,
NULL,
NULL);
ShowWindow(windowHandle, SW_RESTORE);
Whats left to do is now taking the screenshot of the screen and drawing it on form. Which i fail at this part.
When i googled, i first saw SetPixel function but to draw the form it took like half a minute. it was veerry slow. Then people said use Device Context (its the forms' drawing data in the memory as i understood) and draw on that, it will be much quicker then just update the window. And here's what i did;
int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
HDC hdc = GetDC(windowHandle);
BitBlt(hdc, 0, 0, nScreenWidth, nScreenHeight, GetDC(NULL), 0, 0, SRCCOPY | CAPTUREBLT);
UpdateWindow(windowHandle);
ShowWindow(windowHandle, SW_RESTORE);
UpdateWindow(windowHandle);
As you can guess, it didn't work. My form is blank white. I don't understand if i should write this on WM_PAINT message on WindProc or not. I tried many variations to this and actually one point it worked i guess but then stopped working when i changed something and i couldn't get it to work again...
thank you.
Thanks for the comments, i did some more research on WM_PAINT message. And i found this golden document:
http://www.winprog.org/tutorial/bitmaps.html
My code in the original post stays the same, i only added 2 things;
1-Taking a screenshot of the screen and saving it;
(got it from here:
How can I take a screenshot in a windows application?)
// get the device context of the screen
HDC hScreenDC = GetDC(NULL);
// and a device context to put it in
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
int width = GetSystemMetrics(SM_CXSCREEN);
int height = GetSystemMetrics(SM_CYSCREEN);
// hBitmap is a HBITMAP that i declared globally to use within WM_PAINT
// maybe worth checking these are positive values
hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
// get a new bitmap
HBITMAP hOldBitmap = (HBITMAP) SelectObject(hMemoryDC, hBitmap);
BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);
hBitmap = (HBITMAP) SelectObject(hMemoryDC, hOldBitmap);
// clean up
DeleteDC(hMemoryDC);
ReleaseDC(NULL,hScreenDC);
// now your image is held in hBitmap. You can save it or do whatever with it
2- Painting via WM_PAINT:
switch (message)
{
case WM_PAINT:{
int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
BITMAP bm;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmOld = (HBITMAP) SelectObject(hdcMem, hBitmap);
GetObject(hBitmap, sizeof(bm), &bm);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
}
return 0;
Note that i'm totally new to GDI, window stuff. I just kinda mashed pieces together i found here and there, but it works xd
thanks all for the help.
EDIT: also, just a quick info. If your display settings have some kinda scaling, screenshots are also scaled. What this means is if you have per se 125% scaling, then the screenshot will not be the actual fullscreen. To prevent this you need to have a manifest file.
https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests
the setting we are looking for is DPI awareness. here's my manifest file;
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" >
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
my .rc file:
#include "winuser.h"
1 RT_MANIFEST scanner.exe.manifest

CreateFont, DeleteObject and GDI handle/memory leak

I am looking at this sample https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createfonta of CreateFont API
It clearly says that after the font been created with CreateFont it should be destroyed by DeleteObject call. DeleteObject(hFont); is only called once. CreateFont is called 3 times. Is this a bug in the MS docs? Shouldn't the old font be stored by retrieving it with SelectObject and set back after new fonts been used?
Yes, two of the created font objects are leaked.
Note that MS sample code is generally poor about error handling and object cleanup (they generally focus on demonstrating whatever is central to the sample - here the CreateFont call - while ignoring or minimizing those issues).
The example in the documentation did cause the leak of the font object
I built a sample as follows:
#include <Windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR szCmdLine, _In_ int iCmdShow)
{
static TCHAR szAppName[] = TEXT("hello windows");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
}
hwnd = CreateWindow(szAppName,
TEXT("the hello program"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessageW(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_PAINT:
{
RECT rect;
HBRUSH hBrush;
HFONT hFont;
hdc = BeginPaint(hWnd, &ps);
//Logical units are device dependent pixels, so this will create a handle to a logical font that is 48 pixels in height.
//The width, when set to 0, will cause the font mapper to choose the closest matching value.
//The font face name will be Impact.
hFont = CreateFont(48, 0, 0, 0, FW_DONTCARE, FALSE, TRUE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH, TEXT("Impact"));
SelectObject(hdc, hFont);
//Sets the coordinates for the rectangle in which the text is to be formatted.
SetRect(&rect, 100, 100, 700, 200);
SetTextColor(hdc, RGB(255, 0, 0));
DrawText(hdc, TEXT("Drawing Text with Impact"), -1, &rect, DT_NOCLIP);
//DeleteObject(hFont);
//Logical units are device dependent pixels, so this will create a handle to a logical font that is 36 pixels in height.
//The width, when set to 20, will cause the font mapper to choose a font which, in this case, is stretched.
//The font face name will be Times New Roman. This time nEscapement is at -300 tenths of a degree (-30 degrees)
hFont = CreateFont(36, 20, -300, 0, FW_DONTCARE, FALSE, TRUE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH, TEXT("Times New Roman"));
SelectObject(hdc, hFont);
//Sets the coordinates for the rectangle in which the text is to be formatted.
SetRect(&rect, 100, 200, 900, 800);
SetTextColor(hdc, RGB(0, 128, 0));
DrawText(hdc, TEXT("Drawing Text with Times New Roman"), -1, &rect, DT_NOCLIP);
//DeleteObject(hFont);
//Logical units are device dependent pixels, so this will create a handle to a logical font that is 36 pixels in height.
//The width, when set to 10, will cause the font mapper to choose a font which, in this case, is compressed.
//The font face name will be Arial. This time nEscapement is at 250 tenths of a degree (25 degrees)
hFont = CreateFont(36, 10, 250, 0, FW_DONTCARE, FALSE, TRUE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, VARIABLE_PITCH, TEXT("Arial"));
SelectObject(hdc, hFont);
//Sets the coordinates for the rectangle in which the text is to be formatted.
SetRect(&rect, 500, 200, 1400, 600);
SetTextColor(hdc, RGB(0, 0, 255));
DrawText(hdc, TEXT("Drawing Text with Arial"), -1, &rect, DT_NOCLIP);
DeleteObject(hFont);
EndPaint(hWnd, &ps);
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Start the task manager after running the sample, you can see it in details:
Then trigger a WM_PAINT message:
We can find that its GDI Objects has increased by 2, and it will increase every time it is triggered, so this example will cause object leakage.
When we call DeleteObject(hFont); after each use(In line 75 and line 88 of my sample), and repeat the above steps, we will find that GDI Objects will not increase, thus solving the problem of object leakage.

Resizing the window results in bitmaps being overwritten

I've been messing around with bitmaps and rendering in Visual C++ and hit a weird problem.
I'm trying to get the drawing area to resize when the user resizes the window. Right now, not only does enlarging the window not enlarge the visible drawing area (white borders appear outside of where the original window bounds were), but changing the window size seems to be messing with my bitmaps.
Here's what the game looks like on start-up:
After changing the width or height of the window, the purple character has now been replace by the orange one.
And finally, all three become blue.
If I keep going, all the characters will be overwritten by the green grass background bitmap.
It seems to happen in the reverse order that the bitmaps are rendered in. Adding a second row of characters confirmed it's a not a problem in the rendering code - the actual bitmaps in memory are getting changed somehow. Removing the resize code causes the bitmap error to stop repro-ing.
It's almost certainly a mistake in my resize code, when I try to reinitialize the device contexts, but I'm not sure what it is.
Here's the relevant set-up code:
HBITMAP player_blue = (HBITMAP) LoadImage (NULL, L"Images/test_player_blue.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
HBITMAP player_orange = (HBITMAP) LoadImage (NULL, L"Images/test_player_orange.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
HBITMAP player_purple = (HBITMAP) LoadImage (NULL, L"Images/test_player_purple.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
HBITMAP background_bitmap = (HBITMAP) LoadImage (NULL, L"Images/test_background.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
HDC hdc = GetDC (Window);
HDC hdc_temp = CreateCompatibleDC (hdc);
HBITMAP bitmap_buffer = CreateCompatibleBitmap (hdc, resize.screen_width, resize.screen_height);
SelectObject (hdc, bitmap_buffer);
The main loop from wWinMain():
while (!Exit)
{
while (PeekMessage (&msg, nullptr, 0, 0, PM_REMOVE))
{
DispatchMessage(&msg);
}
Render ();
}
The callback function where the resizing is done:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_SIZE:
RECT window_rect;
if (GetWindowRect (Window, &window_rect))
{
hdc = GetDC (Window);
hdc_temp = CreateCompatibleDC (hdc);
bitmap_buffer = CreateCompatibleBitmap (hdc, resize.screen_width, resize.screen_height);
SelectObject (hdc, bitmap_buffer);
screen_width = window_rect.right - window_rect.left;
screen_height = window_rect.bottom - window_rect.top;
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
The actual rendering via BitBlt():
void Render ()
{
SelectObject (hdc_temp, background_bitmap);
BitBlt (hdc, 0, 0, 800, 600, hdc_temp, 0, 0, SRCCOPY);
SelectObject (hdc_temp, player_blue);
BitBlt (hdc, 0, 0, 126, 126, hdc_temp, 0, 0, SRCCOPY);
SelectObject (hdc_temp, player_orange);
BitBlt (hdc, 126, 0, 126, 126, hdc_temp, 0, 0, SRCCOPY);
SelectObject (hdc_temp, player_purple);
BitBlt (hdc, 252, 0, 126, 126, hdc_temp, 0, 0, SRCCOPY);
}
As Ken commented, you are leaking precious GDI resources. A few more resizes and your window might go all black :)
Please see https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createcompatibledc :
When you no longer need the memory DC, call the DeleteDC function. You probably don't need to create that DC on every resize.
Same goes for CreateCompatibleBitmap.

Winapi edit controls disappear when moving window outside of the screen

I'm creating several EDIT elements with a for loop. When I move the window outside the screen and then back inside, the EDIT controls disappear, along with a line in the WM_PAINT event.
The "create" event:
case WM_CREATE:
for( int i =0; i < 4; i++ ){
text = CreateWindow("EDIT","text",WS_VISIBLE | WS_CHILD , 0,i*40+100,100,10, hwnd , NULL, NULL, NULL);
}
// Other elements
break;
The "paint" event:
case WM_PAINT:
{
hdc = GetDC(hwnd);
SetRect(&secondbackground, 0, 70, 800, 500);
FillRect(hdc, &secondbackground, CreateSolidBrush(RGB(0,200,200)) );
ReleaseDC(hwnd, hdc);
hdc = GetDC(hwnd);
SetRect(&secondbackground, 0, 0, 800, 20);
FillRect(hdc, &secondbackground, CreateSolidBrush(RGB(0,100,200)) );
ReleaseDC(hwnd, hdc);
hdc = BeginPaint( hwnd, &ps );
MoveToEx(hdc,1,52,NULL);
LineTo(hdc,100,200);
EndPaint( hwnd, &ps );
hdc = GetDC(hwnd);
SetPixel(hdc,300,300,RGB(255,255,255));
EndPaint( hwnd, &ps );
}
break;
Use BeginPaint/EndPaint when handling WM_PAINT, that will force background drawing which GetDC does not.

BitBlt not updating WM_PAINT in Windows C++ API

I have the following function to draw a loaded bitmap to a window.
void OnPaint(HWND hwnd) {
PAINTSTRUCT ps;
HDC hdc;
BITMAP bitmap;
HDC hdcMem;
HGDIOBJ oldBitmap;
hdc = BeginPaint(hwnd, &ps);
hdcMem = CreateCompatibleDC(hdc);
HBITMAP bmp = mainBitmap;
oldBitmap = SelectObject(hdcMem, mainBitmap);
GetObject(bmp, sizeof(bitmap), &bitmap);
x += 64;
RECT rect;
rect.left = x;
rect.top = 0;
rect.right = x+64;
rect.bottom = 64;
HBITMAP hBmp = CreateCompatibleBitmap(
hdc, // Handle to a device context
rect.right - rect.left, // Bitmap width
rect.bottom - rect.top // Bitmap height
);
BitBlt(
hdc, // Destination rectangle context handle
0, // Destination rectangle x-coordinate
0, // Destination rectangle y-coordinate
rect.right - rect.left, // Destination rectangle width
rect.bottom - rect.top, // Destination rectangle height
hdcMem, // A handle to the source device context
rect.left, // Source rectangle x-coordinate
rect.top, // Source rectangle y-coordinate
SRCCOPY // Raster-operation code
);
SelectObject(hdcMem, oldBitmap);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
}
And I have the following image loaded into HBITMAP mainBitmap:
The image is drawn when the window opens successfully, and I see the first icon in the sprite bitmap (yellow grapple hook), but my issue is, when I press 'C' to re-paint the window, the image does not change to the next icon in the sprite image.
Things I Know
On initialization, x = 64;
Every time I press 'C', paint is called. (Confirmed in Visual
Studio Debugger)
x is incremented by 64 each time OnPaint is called.
Why is the graphic not changing?
Here is my WindowsProc function to handle the WM_PAINT message:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
HANDLE_MSG(hwnd, WM_PAINT, OnPaint);
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Try to call function InvalidateRect() to update region.