Related
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'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.
I am trying to load some bitmaps when starting up the application. I am trying to load them in WM_CREATE but only the last bitmap i load will stay loaded.
I can load the pictures in WM_PAINT but i have been told it's better to load them in WM_CREATE.
//before switch statement
static HBITMAP bitmap1, bitmap2;
case WM_CREATE: {
HINSTANCE hInstance = GetModuleHandle(NULL);
bitmap1 = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_BITMAP1),
IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
bitmap2 = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_BITMAP2),
IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
if (!bitmap1 || !bitmap2) MessageBox(NULL, _T("Error while loading images"), _T("Error!"), MB_ICONEXCLAMATION | MB_OK);
case WM_PAINT:{
//Draw bitmap...
}
I expected that both bitmaps got loaded as they do when loaded inside WM_PAINT
EDIT:
The problem weren't were i thought. The problem is it draws behind the filled area i had made. I still don't know how to fix.
//Draw bitmap function
bool DrawBitmap(HBITMAP hBitmap, int posX, int posY, int sizeX, int sizeY)
{
BITMAP bmp;
HWND hWnd = FindWindow(windowClassName, NULL);
if (!hWnd) return false;
HDC hdc = GetDC(hWnd);
if (!hdc) return false;
HDC hBitmapDC = CreateCompatibleDC(hdc);
if (!hBitmapDC) return false;
GetObject(hBitmap, sizeof(bmp), &bmp);
SelectObject(hBitmapDC, hBitmap);
BitBlt(hdc, posX, posY, sizeX, sizeY, hBitmapDC, 0, 0, SRCCOPY);
DeleteObject(hBitmap);
ReleaseDC(hWnd, hBitmapDC);
ReleaseDC(hWnd, hdc);
return true;
}
//Before switch
RECT recRect;
STATIC HBITMAP bitmap1;
//case WM_CREATE:
case WM_CREATE: {
HINSTANCE hInstance = GetModuleHandle(NULL);
bitmap1 = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_BITMAP1),
IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
if (!bitmap) MessageBox(NULL, _T("Error while loading images"), _T("Error!"), MB_ICONEXCLAMATION | MB_OK);
return 0;
break;
//case WM_PAINT
case WM_PAINT:
DefWindowProc(hwnd, msg, wParam, lParam);
hdc = GetWindowDC(hwnd);
recRect = { -1, -1, 50 + 1, 30 + 1 };
FillRect(hdc, &recRect, (HBRUSH)CreateSolidBrush(RGB(30, 30, 30)));
if (DrawBitmap(bitmap1, 5, 5, 10, 10) == false) MessageBox(NULL, _T("Error while drawing images"), _T("Error!"), MB_ICONEXCLAMATION | MB_OK);
return DefWindowProc(hwnd, msg, wParam, lParam);
return 0;
break;
The problem does not occur when i declare the bitmap in WM_PAINT
GetWindowDC returns the dc for the whole window, including the non-client area. You usually need GetDC instead.
Always use BeginPaint/EndPaint when responding to WM_PAINT and return 0.
CreateSolidBrush is a GDI resource leak. After creating brush or other GDI objects, you have to destroy those objects, otherwise you have resource leak, the program will crash after 10,000 GDI leaks.
In your draw function you have DeleteObject(hBitmap) This will destroy the bitmap immediately. But you probably want to keep the bitmap handle and destroy it only after the program is finished.
Suggestion:
bool DrawBitmap(HDC hdc, HBITMAP hBitmap, int posX, int posY, int sizeX, int sizeY)
{
HDC memdc = CreateCompatibleDC(hdc);
HGDIOBJ oldbmp = SelectObject(memdc, hBitmap);
BitBlt(hdc, posX, posY, sizeX, sizeY, memdc, 0, 0, SRCCOPY);
SelectObject(memdc, oldbmp);
DeleteDC(memdc);
return true;
}
...
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HBRUSH hbrush = CreateSolidBrush(RGB(255, 0, 0));
FillRect(hdc, &ps.rcPaint, hbrush);
DeleteObject(hbrush);
BITMAP bm1;
GetObject(bitmap1, sizeof(bm1), &bm1);
DrawBitmap(hdc, bitmap1, 0, 0, bm1.bmWidth, bm1.bmHeight);
int x = bm1.bmWidth;
int y = bm1.bmHeight;
BITMAP bm2;
GetObject(bitmap2, sizeof(bm2), &bm2);
DrawBitmap(hdc, bitmap2, x, y, bm2.bmWidth, bm2.bmHeight);
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
DeleteObject(bitmap1);
DeleteObject(bitmap2);
PostQuitMessage(0);
return 0;
I specify a font and its size for my listview items using win32 api and it works properly in Windows Xp. I install it in Windows 7 and see size of fonts are too small and hard to read although i specified 17 for its size.
I increased default font sizes in Windows 7 but still the fonts in my program are too small.
This is the code that i specify font for the listview items inside Window procedure :
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT pDIS=(LPDRAWITEMSTRUCT)lParam;
HDC hDC=pDIS -> hDC;
RECT rc = pDIS -> rcItem;
HBRUSH bg = (HBRUSH) (::GetStockObject(DC_BRUSH));
HPEN pn=(HPEN)(::GetStockObject(NULL_PEN));
::SelectObject( hDC , bg );
::SelectObject( hDC , pn );
::SetTextColor( hDC , RGB(0,0,0));
HFONT hF;
hF=CreateFont(17, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Tahoma");
HFONT hOldFont = (HFONT) SelectObject(hDC, hF);
if( (pDIS->itemID % 2) != 0 )
::SetDCBrushColor(hDC, RGB(255,255,255));
else{
::SetDCBrushColor(hDC, RGB(223, 241, 255));
}
::Rectangle( hDC , rc.left , rc.top , rc.right , rc.bottom );
char buffer[1000] = {0};
ListView_GetItemText(pDIS -> hwndItem, pDIS -> itemID, 0, (LPWSTR)buffer, 1000);
::DrawText(hDC, (LPWSTR)buffer, -1, &rc, DT_SINGLELINE | DT_VCENTER);
SelectObject(hDC, hOldFont);
DeleteObject(hF);
}
break;
How can i make Windows display my desired font size and not that small font?
Thanks!
Use SystemParametersInfo to find the default font as follows:
NONCLIENTMETRICS metrics;
metrics.cbSize = sizeof(NONCLIENTMETRICS);
::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS),
&metrics, 0);
hfont = CreateFontIndirect(&metrics.lfMessageFont);
Use .lfMessageFont for ListView and other child controls.
This will retrieve the correct font name and font size.
This font size is already adjusted for DPI settings of system and application.
You can create this font once during windows creation. Then assign it as the main font for the window and listview control. This will update the header control for the listview.
HFONT hfont;
...
case WM_CREATE:
{
if (!hfont)
{
NONCLIENTMETRICS metrics;
metrics.cbSize = sizeof(NONCLIENTMETRICS);
::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS),
&metrics, 0);
hfont = CreateFontIndirect(&metrics.lfMessageFont);
}
hListView = CreateWindow(WC_LISTVIEW ...);
SendMessage(hWnd, WM_SETFONT, (WPARAM)hfont, MAKELPARAM(TRUE, 0));
SendMessage(hListView, WM_SETFONT, (WPARAM)hfont, MAKELPARAM(TRUE, 0));
...
break;
}
Also, do not use (LPWSTR) cast to hide compiler warnings and errors. Use casting only when you are sure it's applicable. In this case, char and wchar_t are very different storage types, casting may in some special cases, but it's far from reliable.
char buffer[1000] creates a buffer of size 1000 bytes. But you are making a call to ListView_GetItemText to read 1000 Unicode characters, which in this case is 2000 bytes and results in buffer overrun. You can change as follows:
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
HDC hDC = pDIS->hDC;
RECT rc = pDIS->rcItem;
COLORREF textcolor = RGB(0, 0, 0);
COLORREF bkcolor = RGB(255, 255, 255);
if((pDIS->itemID % 2) == 0)
{
bkcolor = RGB(223, 241, 255);
}
if(pDIS->itemState & ODS_SELECTED)
{
textcolor = RGB(255, 255, 255);
bkcolor = RGB(0, 0, 255);
}
SetDCBrushColor(hDC, bkcolor);
SelectObject(hDC, GetStockObject(DC_BRUSH));
SelectObject(hDC, GetStockObject(NULL_PEN));
SetTextColor(hDC, textcolor);
Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
auto oldfont = SelectObject(hDC, hfont);
wchar_t buffer[1000] = { 0 };
ListView_GetItemText(pDIS->hwndItem, pDIS->itemID, 0, buffer, 1000);
DrawText(hDC, buffer, -1, &rc, DT_SINGLELINE | DT_VCENTER);
SelectObject(hDC, oldfont);
return TRUE;
}
*hfont is not destroyed in above window procedure. It should be cleaned up elsewhere.
How can I stop displaying the bitmap in my Win32 Project. I have a method that I'm calling in WM_PAINT called LoadBitmap. It's code looks like this:
bool LoadBitmap(LPTSTR szfilename, HDC winhdc, int x, int y) {
HBITMAP bitmap;
bitmap = (HBITMAP)LoadImage(NULL, szfilename, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (bitmap == NULL) {
::MessageBox(NULL, _T("Bitmap failed"), NULL, MB_OK);
return false;
}
HDC hdc;
hdc = ::CreateCompatibleDC(winhdc);
if (hdc == NULL)
{
::MessageBox(NULL, _T("HDC FAILED"), NULL, MB_OK);
return false;
}
BITMAP qbitmap;
int ireturn = GetObject(reinterpret_cast<HGDIOBJ>(bitmap), sizeof(BITMAP), reinterpret_cast<LPVOID>(&qbitmap));
if (!ireturn) {
::MessageBox(NULL, _T("RETURN FAILED"), NULL, MB_OK);
return false;
}
HBITMAP holdbitmap = (HBITMAP)::SelectObject(hdc, bitmap);
if (holdbitmap == NULL) {
::MessageBox(NULL, _T("HOLD FAILED"), NULL, MB_OK);
return false;
}
BOOL qRetBlit = ::BitBlt(winhdc, x, y, qbitmap.bmWidth, qbitmap.bmHeight, hdc, 0, 0, SRCCOPY);
if (!qRetBlit)
{
::MessageBox(NULL, _T("BLIT FAILED"), NULL, MB_OK);
return false;
}
::SelectObject(hdc, holdbitmap);
::DeleteDC(hdc);
::DeleteObject(bitmap);
return true;
}
NOTE x and y change continuously.
When x and y change however, the previous instance of them stays behind.
How can I stop displaying a bitmap once it has been painted in a new position?
If there is background brush then the old bitmap is erased with each paint call. If there is no background then erase it manually for example with FillRect Here is example with double buffering, assumes there is no background brush.
case WM_PAINT:
{
...
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc = ps.rcPaint;
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP membitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
HGDIOBJ oldbitmap = SelectObject(memdc, membitmap);
//double-buffer ready, do custom paintings here:
FillRect(memdc, &rc, GetSysColorBrush(COLOR_3DFACE));
LoadBitmap(filename, memdc, x, y);
//BitBlt to hdc
BitBlt(hdc, 0, 0, rc.right, rc.bottom, memdc, 0, 0, SRCCOPY);
//cleanup:
SelectObject(hdc, oldbitmap);
DeleteObject(membitmap);
DeleteDC(memdc);
EndPaint(hwnd, &ps);
return 0;
}
Edit *************
It will be faster to load the bitmap file only once. For each paint request, we need only to draw the bitmap, instead of LoadImage every time + draw.
You can declare HBITMAP handles as static values in window's procedure. Set it up once in WM_CREATE, and clean it up in WM_NCDESTROY. Now we can use them anywhere in side window procedure:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
static HBITMAP hbitmap_background = NULL;
static HBITMAP hbitmap_sprite = NULL;
switch (msg)
{
case WM_CREATE:
{
hbitmap_background = (HBITMAP)LoadImage(NULL,
L"background.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (!hbitmap_background)
OutputDebugStringW(L"!hbitmap_background\n");
hbitmap_sprite = (HBITMAP)LoadImage(NULL,
L"sprite.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (!hbitmap_sprite)
OutputDebugStringW(L"!hbitmap_sprite\n");
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc = ps.rcPaint;
//setup double-buffering:
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP membitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
HGDIOBJ oldbitmap = SelectObject(memdc, membitmap);
//double-buffer ready, do custom paintings here:
FillRect(memdc, &rc, GetSysColorBrush(COLOR_3DFACE));
DrawBitmap(hbitmap_background, memdc, 0, 0);
DrawBitmap(hbitmap_sprite, memdc, 0, 0);
//BitBlt to hdc
BitBlt(hdc, 0, 0, rc.right, rc.bottom, memdc, 0, 0, SRCCOPY);
//cleanup:
SelectObject(hdc, oldbitmap);
DeleteObject(membitmap);
DeleteDC(memdc);
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_NCDESTROY:
{
//cleapup bitmap handles
if (hbitmap_background)
DeleteObject(hbitmap_background);
if (hbitmap_sprite)
DeleteObject(hbitmap_sprite);
}
}
return DefWindowProc(hwnd, msg, wp, lp);
}
We can change DrawBitmap so it only needs HBITMAP handle
void DrawBitmap(HBITMAP hbitmap, HDC hdc, int x, int y)
{
if (!hbitmap)
{
OutputDebugStringW(L"error\n");
return;
}
BITMAP bm;
GetObject(hbitmap, sizeof(BITMAP), &bm);
HDC memdc = CreateCompatibleDC(hdc);
HGDIOBJ oldbitmap = SelectObject(memdc, hbitmap);
BitBlt(hdc, x, y, bm.bmWidth, bm.bmHeight, memdc, 0, 0, SRCCOPY);
SelectObject(memdc, oldbitmap);
DeleteDC(memdc);
}
Note, in this example I assume background.bmp is very large, and sprite.bmp is very small. If it is painted the other way around then background will hide the sprite.