I was told recently that in WM_PAINT: message I should only use BeginPaint() and EndPaint() .
But what when I need multiple windows with bitmaps or drawings in my main window?
How should I manage this in proper way?
So far I was using this routines, that worked for me and was logic for me.
Is it wrong? What is the proper way?
Thank You in advance.
case WM_PAINT:
{
// main window - that contains other sub windows
PAINTSTRUCT ps;
HDC hdc = BeginPaint(_hwnd, &ps);
EndPaint(_hwnd, &ps);
// a window that shows a 256x256 texture:
PAINTSTRUCT ps_texture_256;
HDC hdc_texture_256 = BeginPaint(GetDlgItem(_hwnd, IDS_DC_TEXTURE_256), &ps_texture_256);
HDC hdc_tmp_256 = CreateCompatibleDC(hdc_texture_256);
SelectObject(hdc_tmp_256, convert_hbm_texture_256);
BitBlt(hdc_texture_256, 0, 0, 256, 256, hdc_tmp_256, 0, 0, SRCCOPY);
DeleteObject(convert_hbm_texture_256);
DeleteDC(hdc_tmp_256);
EndPaint(GetDlgItem(_hwnd, IDS_DC_TEXTURE_256), &ps_texture_256);
// a smaller window that shows 128x128 texture:
PAINTSTRUCT ps_texture_128;
HDC hdc_texture_128 = BeginPaint(GetDlgItem(_hwnd, IDS_DC_TEXTURE_128), &ps_texture_128);
HDC hdc_tmp_128 = CreateCompatibleDC(hdc_texture_128);
SelectObject(hdc_tmp_128, convert_hbm_texture_128);
BitBlt(hdc_texture_128, 0, 0, 128, 128, hdc_tmp_128, 0, 0, SRCCOPY);
DeleteObject(convert_hbm_texture_128);
DeleteDC(hdc_tmp_128);
EndPaint(GetDlgItem(_hwnd, IDS_DC_TEXTURE_128), &ps_texture_128);
return (INT_PTR)TRUE;
}
Related
I am studying Double buffering in windows winapi.
When I draw text on HDC direct using DrawText function, it works well like under code.
case WM_PAINT:
hDC = BeginPaint(hwnd,&ps);
DrawText(hDC,"test",4,&rt,DT_CENTER | DT_WORDBREAK);
DeleteDC(hMemDC);
ReleaseDC(hwnd,hDC);
EndPaint(hwnd,&ps);
break;
But, I would like to want to use double buffering, so I make memory dc and bitblt function.
Under code is that,It don't works.I can show white empty screen.
case WM_PAINT:
hDC = BeginPaint(hwnd,&ps);
hMemDC = CreateCompatibleDC(hDC);
//GetClientRect(hwnd, &crt);
//hBitmap = CreateCompatibleBitmap(hDC, crt.right, crt.bottom);
//OldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);
DrawText(hMemDC,"test",4,&rt,DT_CENTER | DT_WORDBREAK);
BitBlt(hDC,0,0,800,800,hMemDC,0,0,SRCCOPY);
DeleteDC(hMemDC);
ReleaseDC(hwnd,hDC);
EndPaint(hwnd,&ps);
break;
Is memory dc different original dc ?
If I use CreateCompatibleBitmap function, it work well.
What is concept am i missing ?
Is there website well-organized website?
A typical non-double-buffered drawing routine looks like this:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rcWnd;
GetClientRect(hWnd, &rcWnd);
{
DrawText(hdc, _T("Hello, world!"), -1,
&rcWnd, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
EndPaint(hWnd, &ps);
}
break;
A typical double-buffered drawing routine looks like this:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rcWnd;
GetClientRect(hWnd, &rcWnd);
{
const int width = rcWnd.right - rcWnd.left
const int height = rcWnd.bottom - rcWnd.top;
// create a new DC based on the target HDC
HDC hDCMem = CreateCompatibleDC(hdc);
// create a bitmap that is compatible with the target DC
HBITMAP hMemBmp = CreateCompatibleBitmap(hdc, width, height);
// select the new bitmap in to the DC, saving the old bitmap
HBITMAP hOldBmp = (HBITMAP)SelectObject(hDCMem, hMemBmp);
// do your drawing
HBRUSH hBr = CreateSolidBrush(RGB(255, 255, 255));
FillRect(hDCMem, &rcWnd, hBr);
DeleteObject(hBr);
DrawText(hDCMem, _T("Hello, world!"), -1,
&rcWnd, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
// copy all the bits from our new DC over to the target DC
BitBlt(hdc, rcWnd.left, rcWnd.top, width, height, hDCMem, 0, 0, SRCCOPY);
// select the original bitmap the DC came with
SelectObject(hDCMem, hOldBmp);
// delete our bitmap
DeleteObject(hMemBmp);
// delete the DC
DeleteDC(hDCMem);
}
EndPaint(hWnd, &ps);
}
break;
case WM_ERASEBKGND:
// since we are drawing the entire area because we are double-buffering, there is
// no need to erase the background. This will speed up your drawing.
return TRUE;
HDC objects are all the same -- there is no special things that happen when you create another one in memory. I mean, they are all in memory, really. You are just creating another canvas to do your drawing on, then copying that canvas over in one shot.
case WM_PAINT:
{
PAINTSTRUCT pt;
HDC hdc;
hdc=BeginPaint(hWnd,&pt);
SetTextColor(hdc,RGB(255,0,0));
SetBkColor(hdc,RGB(0,255,0));
SetBkMode(hdc,TRANSPARENT);
//为什么矩形绘制成功,但是字体没有绘制呢?
TextOut(hdc,0,0,TEXT("WM_PAINT"),strlen("WM_PAINT"));
EndPaint(hWnd,&pt);
}
We use above code to write text in a Window. What should i do if want output "int i" to window, i increace every second? i means ouput 0, 1, 2, 3, 4, 5.... in to window.
Store your i variable somewhere that your WM_PAINT handler can access it. Then, whenever you change the value of i, such as in a timer, then you can call InvalidateRect() to trigger a repaint. Your WM_PAINT handler should draw the current value of i whenever the window needs to be painted.
int i = 0;
...
case WM_TIMER: {
++i;
InvalidateRect(hWnd, NULL, TRUE);
break;
}
case WM_PAINT: {
PAINTSTRUCT pt;
HDC hdc = BeginPaint(hWnd, &pt);
SetTextColor(hdc, RGB(255,0,0));
SetBkColor(hdc, RGB(0,255,0));
SetBkMode(hdc, TRANSPARENT);
TCHAR str[16];
int len = wsprintf(str, TEXT("%d"), i);
TextOut(hdc, 0, 0, str, len);
EndPaint(hWnd, &pt);
break;
}
By default a rich edit control has a "3d" border. I draw a thin border around a rich edit control this way:
if (message == WM_NCPAINT)
{
RECT rc;
HDC hdc;
HPEN pen;
HBRUSH brush;
HGDIOBJ oldP, oldB;
POINT tl, br;
::GetWindowRect(hWnd, &rc);
hdc = ::GetDC(hWnd);
tl.x = rc.left;
tl.y = rc.top;
br.x = rc.right;
br.y = rc.bottom;
::ScreenToClient(hWnd, &tl);
::ScreenToClient(hWnd, &br);
pen = ::CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
brush = (HBRUSH) ::GetStockObject(HOLLOW_BRUSH);
oldP = ::SelectObject(hdc, pen);
oldB = ::SelectObject(hdc, brush);
::Rectangle(hdc, tl.x, tl.y, br.x, br.y);
::SelectObject(hdc, oldP);
::SelectObject(hdc, oldB);
::DeleteObject(pen);
::ReleaseDC(hWnd, hdc);
return 0;
}
The border looks fine but the area under the old border is not redrawn. It looks like I have to redraw to whole content of the rich edit control. After that a text shouldn't be a litte bit cut from the bottom. Here you can see what I mean (the second rich edit control has custom border). How to achieve it ?
GetDC returns DC for client area. In this case you need GetWindowDC for the whole richedit window.
Either way, overriding border color in edit and rich-edit can be difficult, because you also have to handle scrollbar painting in WM_NCPAINT
Assuming you don't need vertical and/or horizontal scrollbar, use GetWindowDC as follows:
LRESULT CALLBACK RichEditProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp,
UINT_PTR, DWORD_PTR)
{
static int border_thickness = 1;
switch(msg)
{
case WM_NCPAINT:
{
HDC hdc = GetWindowDC(hwnd);
RECT rc;
GetClientRect(hwnd, &rc);
rc.right += 2 * border_thickness + 1;
rc.bottom += 2 * border_thickness + 1;
HBRUSH hbrush = (HBRUSH)GetStockObject(NULL_BRUSH);
HPEN hpen = CreatePen(PS_SOLID, 2 * border_thickness, RGB(255, 0, 0));
HBRUSH oldbrush = (HBRUSH)SelectObject(hdc, hbrush);
HPEN oldpen = (HPEN)SelectObject(hdc, hpen);
Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
SelectObject(hdc, oldpen);
SelectObject(hdc, oldbrush);
DeleteObject(hpen);
DeleteObject(hbrush);
ReleaseDC(hwnd, hdc);
return 0;
}
case WM_NCCALCSIZE:
if(lp)
{
NCCALCSIZE_PARAMS* sz = (NCCALCSIZE_PARAMS*)lp;
InflateRect(&sz->rgrc[0], -border_thickness, -border_thickness);
return 0;
}
break;
case WM_NCDESTROY:
RemoveWindowSubclass(hwnd, RichEditProc, 0);
break;
}
return DefSubclassProc(hwnd, msg, wp, lp);
}
By default, WM_NCCALCSIZE is not called in subclass procedure, you have to call SetWindowPos with SWP_FRAMECHANGED
HWND hrichedit = CreateWindowEx(...);
SetWindowSubclass(hrichedit, RichEditProc, 0, 0);
SetWindowPos(hrichedit, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
If you do need horizontal/vertical scrollbar, you might consider using a borderless richedit, then paint around the rich control in parent window's WM_PAINT procedure.
I'm trying to load a bitmap to a window I created. The bitmap should be the background of the window (I want to add labels on it an a progress bar later on).
This is my code:
HINSTANCE hInst;
LRESULT CALLBACK WindProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX WndCls;
static WCHAR szAppName[] = L"BitmapIntro";
MSG Msg;
hInst = hInstance;
WndCls.cbSize = sizeof(WndCls);
WndCls.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
WndCls.lpfnWndProc = WindProcedure;
WndCls.cbClsExtra = 0;
WndCls.cbWndExtra = 0;
WndCls.hInstance = hInst;
WndCls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndCls.hCursor = LoadCursor(NULL, IDC_ARROW);
WndCls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
WndCls.lpszMenuName = NULL;
WndCls.lpszClassName = szAppName;
WndCls.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);
RegisterClassEx(&WndCls);
CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
szAppName,
L"Bitmaps Fundamentals",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
while (GetMessage(&Msg, NULL, 0, 0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return static_cast<int>(Msg.wParam);
}
LRESULT CALLBACK WindProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
HDC hDC, MemDCExercising;
PAINTSTRUCT Ps;
HBITMAP bmpExercising;
switch (Msg)
{
case WM_DESTROY:
PostQuitMessage(WM_QUIT);
break;
case WM_PAINT:
hDC = BeginPaint(hWnd, &Ps);
// Load the bitmap from the resource
bmpExercising = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP3));
// Create a memory device compatible with the above DC variable
MemDCExercising = CreateCompatibleDC(hDC);
// Select the new bitmap
SelectObject(MemDCExercising, bmpExercising);
// Copy the bits from the memory DC into the current dc
BitBlt(hDC, 10, 10, 450, 400, MemDCExercising, 0, 0, SRCCOPY);
// Restore the old bitmap
DeleteDC(MemDCExercising);
DeleteObject(bmpExercising);
EndPaint(hWnd, &Ps);
break;
default:
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return 0;
}
The problem is, the PNG size is small compared to the window, so when the PNG opens, it only occupies the left high corner. How can I make it stretch to my window size or at least draw it over and over until it fills the window?
How can I make it stretch to my window size
Use StretchBlt() instead of BitBlt().
case WM_PAINT:
{
// Get the window dimensions
RECT r;
GetClientRect(hWnd, &r);
// Load the bitmap from the resource
HBITMAP bmpExercising = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP3));
// Get the bitmap dimensions
BITMAP bmp;
GetObject(bmpExercising, sizeof(BITMAP), &bmp);
PAINTSTRUCT Ps;
HDC hDC = BeginPaint(hWnd, &Ps);
// Create a memory device compatible with the above DC variable
HDC MemDCExercising = CreateCompatibleDC(hDC);
// Select the new bitmap
HBITMAP hOldBmp = SelectObject(MemDCExercising, bmpExercising);
// Copy the bits from the memory DC into the current dc
StretchBlt(hDC, 0, 0, r.right - r.left, r.bottom - r.top, MemDCExercising, 0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);
// Restore the old bitmap
SelectObject(MemDCExercising, hOldBmp);
// Destroy the memory device
DeleteDC(MemDCExercising);
// Destroy the bitmap
DeleteObject(bmpExercising);
EndPaint(hWnd, &Ps);
break;
}
or at least draw it over and over until it fills the window?
There are two different ways to handle that.
at startup, load the bitmap and create an HBRUSH around it using CreatePatternBrush(), and then assign that to the WNDCLASS::hbrBackground field when you register your window class. Let the OS draw the window background using the bitmap for you.
HBITMAP bmpExercising = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP3));
WndCls.hbrBackground = CreatePatternBrush(bmpExercising);
if you want to paint the bitmap manually, have your paint handler call BitBlt() in a couple of loops. You know the dimensions of the bitmap (which you can retrieve in code using GetObject() and the BITMAP structure), and you know the dimensions of the window (which you can retrieve in code using GetWindowRect() or GetClientRect()). So simply draw the same bitmap more than one time at different offsets as needed. Start by drawing it once in the top-left corner, then move right bitmap-width pixels and draw it again, repeating until you move past window-width pixels. Then move left back to 0 and move down bitmap-height pixels and repeat the whole width-line again, repeating until you move past window-height pixels.
case WM_PAINT:
{
// Get the window dimensions
RECT r;
GetClientRect(hWnd, &r);
// Load the bitmap from the resource
HBITMAP bmpExercising = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP3));
// Get the bitmap dimensions
BITMAP bmp;
GetObject(bmpExercising, sizeof(BITMAP), &bmp);
PAINTSTRUCT Ps;
HDC hDC = BeginPaint(hWnd, &Ps);
// Create a memory device compatible with the above DC variable
HDC MemDCExercising = CreateCompatibleDC(hDC);
// Select the new bitmap
HBITMAP hOldBmp = SelectObject(MemDCExercising, bmpExercising);
int width = r.right - r.left;
int height = r.bottom - r.top;
// Copy the bits from the memory DC into the current dc
for(int y = 0; y < height; y += bmp.bmHeight)
{
for(int x = 0; x < width; x += bmp.bmWidth)
{
BitBlt(hDC, x, y, bmp.bmWidth, bmp.bmHeight, MemDCExercising, 0, 0, SRCCOPY);
}
}
// Restore the old bitmap
SelectObject(MemDCExercising, hOldBmp);
// Destroy the memory device
DeleteDC(MemDCExercising);
// Destroy the bitmap
DeleteObject(bmpExercising);
EndPaint(hWnd, &Ps);
break;
}
Now, with that said, here are some additional notes:
you should not be loading the bitmap inside of your paint handler. Load it one time before creating the window, and then reuse the same HBITMAP for each paint operation until the window is destroyed, then free the bitmap.
LoadBitmap() is deprecated, you should be using LoadImage() instead, eg:
HBITMAP bmpExercising = (HBITMAP) LoadImage(hInst, MAKEINTRESOURCE(IDB_BITMAP3), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
you said "The bitmap should be the background of the window", so you should be drawing the bitmap in response to the WM_ERASEBKGND message instead of the WM_PAINT message.
case WM_ERASEBKGND:
{
HDC hDC = (HDC) wParam;
// draw the bitmap on hDC as needed...
return 1;
}
I'm trying to create a launcher window with 5 "invisible" buttons that have a smaller partially transparent image centered inside them. The image inside changes depending on whether the user is hovering over the button. In this example the images are resources IDB_Website1 and IDB_Website2.
The images are in .png format. I'm using GDI+ and loading PNG files from resources with http://www.codeproject.com/Articles/3537/Loading-JPG-PNG-resources-using-GDI.
I've used BS_OWNERDRAW to handle the graphics of the button. The problem is that I don't know how to link the "PNG from resources" code to the parent's WM_DRAWITEM. With the current way I've done the code, I lose the transparency of the .png by going through HBITMAP.
Global variable:
HBITMAP globalpic;
Main wndproc:
case WM_CREATE:
{
HWND hwndButton = CreateWindow(TEXT("button"), NULL,
WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
190, 230, 260, 50, // <- the larger rect size
hWnd, (HMENU) HT_BUTTON1, hInst, NULL);
HTButton = (WNDPROC) SetWindowLong(hwndButton, GWL_WNDPROC, (LONG) ButtonProc);
}
case WM_DRAWITEM:
{
DRAWITEMSTRUCT* lpDrawItem = (DRAWITEMSTRUCT*)lParam;
HBITMAP hBmp;
HDC hdc = CreateCompatibleDC(NULL);
if (hovering) // <- mousehover detection, not included
{
gdi.CreateBM(IDB_Website2, _T("PNG"), hInst, hdc, 62, 100, 200, 40);
} else {
gdi.CreateBM(IDB_Website1, _T("PNG"), hInst, hdc, 62, 100, 200, 40);
}
hBmp = globalpic; // get the global var gdi.CreateBM created
DeleteDC(hdc);
BITMAP bm;
GetObject(hBmp, sizeof(bm), &bm);
HDC hMemDC = CreateCompatibleDC(NULL);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hBmp);
StretchBlt (lpDrawItem->hDC,
lpDrawItem->rcItem.left + 30,
lpDrawItem->rcItem.top + 5,
200, <- the width of image
40, <- the height of image
hMemDC,
0, 0,
bm.bmWidth,
bm.bmHeight,
SRCCOPY);
SelectObject(hMemDC, hOldBmp);
DeleteDC(hMemDC);
DeleteObject(hBmp);
break;
}
class GDI's method CreateBM.
Currently, since I don't know of a better way, it sends the HBITMAP to the global variable globalpic. The problem is that by doing this, I lose transparency.
void CreateBM(UINT menuid, LPCTSTR pType, HMODULE hInst, HDC hdc, int x, int y, int w, int h)
{
CGdiPlusBitmapResource m_pBitmap;
m_pBitmap.Load(menuid, pType, hInst);
m_pBitmap.m_pBitmap->GetHBITMAP(RGB(255, 255, 0), &globalpic);
//m_pBitmap.m_pBitmap->GetHBITMAP(GetBkColor(hdc), &globalpic); // <- no-go either
}
Any way I could do this more directly? I feel that going through the HBITMAP route is completely unnecessary but I couldn't for the life of me figure out how I should do it.
Then again, if using the HBITMAP way is fine, any pointers on how I could make the color (I guess (RGB(255, 255, 0) ...?) transparent with the current implementation?
Answering to Hans:
I also have a GDI method like this, but it's unused at the moment because I couldn't get the image removed after drawing it (as is needed when hovering changes the image):
void Create(UINT menuid, LPCTSTR pType, HMODULE hInst, HDC hdc, int x, int y, int w, int h)
{
Graphics grpx(hdc);
ht_img = new CGdiPlusBitmapResource();
ht_img -> Load(menuid, pType, hInst);
grpx.DrawImage(*ht_img, x, y, w, h);
delete ht_img;
}