I am trying to understand better the quirks of Visual C++. As such, I have come to the stage where I make a program that starts out as an empty window, but as you click around, a check board of red and blue squares emerges (it's not good on the eyes, but it works). The squares also alternate between the two colours if you click them again. Each square is 100x100 pixels, and I have in my project folder the image files for them (I know I could use the last two integer agruments of BitBlt to use a single image that is half-blue, half-red, and therefore not need toPaint, but that's not the issue here)
This is what my painting routine looks like right now (and it works fine, whatever's not declared here is a global variable):
case WM_PAINT:
{
// 'HDC hdc' declared before the switch statement
// 'PAINTSTRUCT ps' declared before the switch statement
// 'hWnd' is the first argument to WndProc()
hdc = BeginPaint(hWnd, &ps);
HBITMAP toPaint = NULL;
BITMAP bm;
// 'xcor' and 'ycor' are the coordinates of the last left-click
// 'int counters[][]' keeps track of the number of left-clicks in each square
// 'blue' and 'red' are HBITMAPs initialized in case WM_CREATE
if (counters[xcor / 100][ycor / 100] % 2 == (xcor / 100 + ycor / 100) % 2)
toPaint = blue;
else
toPaint = red;
HDC hdcMem = CreateCompatibleDC(hdc);
HGDIOBJ hbmOld = SelectObject(hdcMem, toPaint);
GetObject(toPaint, sizeof(bm), &bm);
BitBlt(hdc, xcor - xcor % 100, ycor - ycor % 100, 100, 100, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld);
EndPaint(hWnd, &ps);
break;
}
Now, whenever I would resize the window, either maximizing, or just dragging the edges, everything would be repainted, and since there is only one left-click stored, it would only draw one square, and the rest would go back to default gray. I therefore decided to catch the WM_SIZE case, and redraw all squares that had been drawn up to that point:
case WM_SIZE:
hdc = BeginPaint(hWnd, &ps);
for (int i = 0; i < 20; i++)
{
for (int j = 0; j < 20; j++)
{
// checks whether that square has been drawn before
if (counters[i][j] == 0)
continue;
HBITMAP toPaint = NULL;
BITMAP bm;
if (counters[i][j] % 2 == (i + j) % 2)
toPaint = blue;
else
toPaint = red;
HDC hdcMem = CreateCompatibleDC(hdc);
HGDIOBJ hbmOld = SelectObject(hdcMem, toPaint);
GetObject(toPaint, sizeof(bm), &bm);
BitBlt(hdc, i*100, j*100, 100, 100, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld);
}
}
EndPaint(hWnd, &ps);
break;
As you can see, everything inside the innermost for-loop, after the first if-test, is more or less an exact copy of what I had in my WM_PAINT case, which I would think is a good sign that those lines should be put into its own function call, something like DrawSquare(HWND hWnd, int i, int j, HDC handle, PAINTSTRUCT ps). However, I can't figure out how to navigate all the pointers, references and copies. I can get something that compiles, but then it won't draw anything.
How would I write such a DrawSquare()?
Well, that was easier than I anticipated. Using
void DrawSquare(HWND hWnd, HDC hdc, int x, int y)
{
HBITMAP toPaint = NULL;
BITMAP bm;
if (counters[x][y] % 2 == (x + y) % 2)
toPaint = blue;
else
toPaint = red;
HDC hdcMem = CreateCompatibleDC(hdc);
HGDIOBJ hbmOld = SelectObject(hdcMem, toPaint);
GetObject(toPaint, sizeof(bm), &bm);
BitBlt(hdc, x * 100, y * 100, 100, 100, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld);
// Thanks to the comment below, to avoid memory leak
DeleteDC(hdcMem);
}
without the PAINTSTRUCT worked just fine (since I'm not explicitly using it). Now my cases look like this:
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
DrawSquare(hWnd, hdc, xcor / 100, ycor / 100);
EndPaint(hWnd, &ps);
break;
case WM_SIZE:
hdc = BeginPaint(hWnd, &ps);
for (int i = 0; i < 20; i++)
{
for (int j = 0; j < 20; j++)
{
if (counters[i][j] == 0)
continue;
DrawSquare(hWnd, hdc, i, j);
}
}
EndPaint(hWnd, &ps);
break;
which is much better than what I had.
Related
Can somebody explain how to draw text on a bitmap in memory?
I have the following code but i can't figure out how to do it.
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HDC buffDC = CreateCompatibleDC(hdc);
SelectObject(buffDC, hFnt);
SetTextColor(buffDC, RGB(1, 1, 1));
SetBkColor(buffDC, RGB(0, 255, 0));
RECT rc;
GetClientRect(hWnd, &rc);
HBITMAP buffBitmap = CreateCompatibleBitmap(buffDC, rc.right, rc.bottom);
int savedDC = SaveDC(buffDC);
HBRUSH hBrush = CreateSolidBrush(RGB(0, 255, 0));
FillRect(buffDC, &rc, hBrush);
DeleteObject(hBrush);
//This is the part where i would like to draw to the bitmap
TextOutA(buffDC, 0, 0, "Hello", 6);
SelectObject(buffDC, buffBitmap);
BitBlt(hdc, 0, 0, rc.right, rc.bottom, buffDC, 0, 0, SRCCOPY);
RestoreDC(buffDC, savedDC);
DeleteObject(buffBitmap);
DeleteDC(buffDC);
EndPaint(hWnd, &ps);
break;
}
I have seen a lot of different solutions mostly using MFC, however I would like to avoid that approach if possible.
EDIT: I have checked the other already asked questions but I couldn't find one wich would cover this without MFC.
My original problem was that I'm using a timer to call RedrawWindow and update the position of the text and make a sort of scrolling text which moves from right to left.
When I was in the testing process I have noticed that on some machines the app runs with up to 25% CPU usage and on some others it uses < 1%. I have tested the app one two machines with exactly the same specs running Windows 7 and the app ran on one with ~10% and the other with 0%.
(By the way my timer is getting called every 33ms and RedrawWindow uses RDW_UPDATENOW and I didn't handle the WM_ERASEBKGND message either :P
Since WM_TIMER (as far as I know) is a low priority message I was not concerned about the timer causeing the issue with the CPU usage.)
I satrted to think that maybe I should be using a bitmap and BitBlt it to the screen rather than just simply drawing to the dc and updating the x coordinate every time I repaint the screen.
Thanks
Before you can draw onto a bitmap, you have to select it into a memory device context.
Move SelectObject(buffDC, buffBitmap); before the first call to a drawing function, but usually as soon as possible after you created the bitmap.
In your sample code it appears suitable to insert it after the SaveDC() call so the original bitmap will be restored later when you call RestoreDC():
int savedDC = SaveDC(buffDC);
SelectObject(buffDC, buffBitmap);
As commenters noted, CreateCompatibleBitmap(buffDC, rc.right, rc.bottom) should be changed to CreateCompatibleBitmap(hdc, rc.right, rc.bottom).
From the reference of CreateCompatibleBitmap():
When a memory device context is created, it initially has a 1-by-1
monochrome bitmap selected into it. If this memory device context is
used in CreateCompatibleBitmap, the bitmap that is created is a
monochrome bitmap. To create a color bitmap, use the HDC that was used
to create the memory device context
Finally a suggestion: If you just need a temporary bitmap (as in your sample code), there is a more efficient API available since Windows Vista. It is called the buffered paint API. MSDN does not appear to provide a good overview, here is a tutorial and the reference (all functions that have "BufferedPaint" in their name).
Here is the Window Procedure which worked for me and is based on the answer from zett42.
This piece of code is just for testing purposses as I cannot post the original source code of the application I'm working on due to work.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int xPos;
const bool bIsBufferedPaint = true;
switch (message)
{
case WM_CREATE:
{
BufferedPaintInit();
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
if(bIsBufferedPaint)
{
HDC newDC;
RECT rc;
RECT dstrc;
GetClientRect(hWnd, &rc);
dstrc = rc;
dstrc.left = rc.right + xPos;
HPAINTBUFFER hBufferedPaint = BeginBufferedPaint(hdc, &rc, BPBF_COMPATIBLEBITMAP, NULL, &newDC);
if(hBufferedPaint)
{
BufferedPaintClear(hBufferedPaint, NULL);
SetTextColor(newDC, RGB(0, 0, 0));
DrawText(newDC, L"Using buffered paint", -1, &dstrc, DT_SINGLELINE | DT_VCENTER | DT_LEFT);
Sleep(2);
EndBufferedPaint(hBufferedPaint, TRUE);
}
else
{
// buffer paint did not work.
}
}
else
{
HDC buffDC = CreateCompatibleDC(hdc);
SetTextColor(buffDC, RGB(0, 0, 0));
SetBkColor(buffDC, RGB(255, 255, 255));
RECT rc;
GetClientRect(hWnd, &rc);
HBITMAP buffBitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
int savedDC = SaveDC(buffDC);
SelectObject(buffDC, buffBitmap);
HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
FillRect(buffDC, &rc, hBrush);
DeleteObject(hBrush);
std::string testText = "Not using the buffered paint API";
TextOutA(buffDC, xPos, 0, testText.c_str(), testText.size());
BitBlt(hdc, 0, 0, rc.right, rc.bottom, buffDC, 0, 0, SRCCOPY);
RestoreDC(buffDC, savedDC);
DeleteObject(buffBitmap);
DeleteDC(buffDC);
}
EndPaint(hWnd, &ps);
}
break;
case WM_ERASEBKGND:
return 1;
case WM_TIMER:
{
switch(wParam)
{
case TIMER1:
{
xPos--;
RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ERASE);
if(bIsBufferedPaint)
{
if(xPos <= -500)
xPos = 0;
}
else
{
if(xPos <= -50)
xPos = 1000;
}
}
break;
}
}
break;
case WM_NCDESTROY:
{
BufferedPaintUnInit();
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
I am trying to teach myself C++ and have ran into a snag. This is my first attempt at making a simple connect to database Win32 program.
What I want to do:
I want all my strings defined in lang.h (this would allow for easier translating).
I then want to use DrawText() in the WM_PAINT case statement to draw the text in certain positions on the screen.
I have gotten the text to draw on the screen with this:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
SelectObject(ps.hdc, GetStockObject(GRAY_BRUSH));
Rectangle(ps.hdc, 100, 100, 500, 300);
RECT rect;
GetClientRect(hWnd, &rect);
SetTextColor(ps.hdc, RGB(0, 0, 0));
SetBkMode(ps.hdc, TRANSPARENT);
rect.left = 110;
rect.top = 110;
LPCWSTR message = L"Connect to Database: ";
DrawText(ps.hdc, message, -1, &rect, DT_SINGLELINE | DT_NOCLIP);
EndPaint(hWnd, &ps);
}
break;
But, what I really want to do is use the strings that I defined in lang.h
Example: (this does not work, just one of the many things I tried)
//lang.h
#define MSG_DBPATH = L"Path to Database:";
//SimpleDBConnect.cpp
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
SelectObject(ps.hdc, GetStockObject(GRAY_BRUSH));
Rectangle(ps.hdc, 100, 100, 500, 300);
RECT rect;
GetClientRect(hWnd, &rect);
SetTextColor(ps.hdc, RGB(0, 0, 0));
SetBkMode(ps.hdc, TRANSPARENT);
rect.left = 110;
rect.top = 110;
DrawText(ps.hdc, MSG_DBPATH, -1, &rect, DT_SINGLELINE | DT_NOCLIP);
EndPaint(hWnd, &ps);
}
break;
Is it possible to draw text on the screen within the WM_PAINT case statement using defined text in an external header file?
#define MSG_DBPATH L"Path to Database:"
Note no equal sign and no semicolon.
i want to take a image of window's content and show it as smaller bitmap in that window... i followed this article: http://msdn.microsoft.com/en-us/library/dd183402(v=vs.85).aspx and when i want take screenshot of whole desktop - it works fine... problem is when i try to get bitmap only of window's content. Any ideas what am I doing wrong ?
Here's my code:
HDC hDC;
HDC hDCMemDC = NULL;
HBITMAP hbmWindow = NULL;
BITMAP bmpWindow;
hDC = GetDC(hWnd);
hDCMemDC = CreateCompatibleDC(hDC);
RECT clientRect;
GetClientRect(hWnd, &clientRect);
hbmWindow = CreateCompatibleBitmap(hDC, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
SelectObject(hDCMemDC, hbmWindow);
BitBlt(hDCMemDC, 0, 0, 100, 100, hDC, 0, 0, SRCCOPY);
Thanks
void DrawSelf(HDC Context, RECT Area, RECT NewArea)
{
uint32_t W = Area.right - Area.left;
uint32_t H = Area.bottom - Area.top;
uint32_t NW = NewArea.right - NewArea.left;
uint32_t NH = NewArea.bottom - NewArea.top;
StretchBlt(Context, NewArea.left, NewArea.top, NW, NH, Context, Area.left, Area.top, W, H, SRCCOPY);
}
Then you can do:
RECT Area;
RECT Area2;
HDC DC = GetDC(hwnd); //Gets the client area only.. Use GetWindowDC for the whole window including the title-bar.
GetClientRect(hwnd, &Area); //client area only.
GetClientRect(hwnd, &Area2);
//Smaller area in which to draw.
Area2.left += 5;
Area2.right -= 5;
Area2.top += 5;
Area2.bottom -= 5;
DrawSelf(DC, Area, Area2);
ReleaseDC(hwnd, dc);
Use GetWindowDC instead of GetDC to get the entire window area.
This code is attempting to capture the image painted on the window in a box of up to 100x100 around the cursor. BitBlt is not returning 0 in either location here, and I'm pretty sure the issue is with the first function call of BitBlt, where I am trying to copy the image from the background of the window into meta, which is an HDC declared globally. In addition to just trying to create the HDC entirely in memory, I tried to create and load a bitmap of white space and capture the new image into the handle associated with it, but all that did was act like an eraser and draw a white box around the cursor as it moved. Relevant code is below, mouseRect and clientRect are global variables relating to the box around the cursor and the client rectangle, respectively. Any help is appreciated, thanks!
case WM_CREATE:
hInstance = ((LPCREATESTRUCT) lParam)->hInstance;
GetClientRect(hWnd, &clientRect);
hdc = GetDC(hWnd);
meta = CreateCompatibleDC(hdc);
return 0;
case WM_MOUSEMOVE:
x = LOWORD(lParam);
y = HIWORD(lParam);
hdc = GetDC(hWnd);
BitBlt(hdc, mouseRect.left, mouseRect.top, mouseRect.right-mouseRect.left, mouseRect.bottom-mouseRect.top, meta, 0, 0, SRCCOPY);
ReleaseDC(hWnd, meta);
meta = CreateCompatibleDC(hdc);
if(y<50)
mouseRect.top = 0;
else
mouseRect.top = y-50;
if(x<50)
mouseRect.left = 0;
else
mouseRect.left = x-50;
if(clientRect.right-x<50)
mouseRect.right = clientRect.right;
else
mouseRect.right = x+50;
if(clientRect.bottom-y<50)
mouseRect.bottom = clientRect.bottom;
else
mouseRect.bottom = y+50;
BitBlt(meta, 0, 0, mouseRect.right-mouseRect.left, mouseRect.bottom-mouseRect.top, hdc, mouseRect.left, mouseRect.top, SRCCOPY);
ReleaseDC(hWnd, hdc);
return 0;
Fixed the code, here's the corrected WM_MOUSEMOVE code
case WM_MOUSEMOVE:
x = LOWORD(lParam);
y = HIWORD(lParam);
hdc = GetDC(hWnd);
BitBlt(hdc, mouseRect.left, mouseRect.top, mouseRect.right-mouseRect.left, mouseRect.bottom-mouseRect.top, hdcMemDC, 0, 0, SRCCOPY);
ReleaseDC(hWnd, hdcMemDC);
if(y<50)
mouseRect.top = 0;
else
mouseRect.top = y-50;
if(x<50)
mouseRect.left = 0;
else
mouseRect.left = x-50;
if(clientRect.right-x<50)
mouseRect.right = clientRect.right;
else
mouseRect.right = x+50;
if(clientRect.bottom-y<50)
mouseRect.bottom = clientRect.bottom;
else
mouseRect.bottom = y+50;
hdcMemDC = CreateCompatibleDC(hdc);
hbmScreen = CreateCompatibleBitmap(hdc, mouseRect.right-mouseRect.left, mouseRect.bottom-mouseRect.top);
SelectObject(hdcMemDC,hbmScreen);
if(!BitBlt(hdcMemDC, 0, 0, mouseRect.right-mouseRect.left, mouseRect.bottom-mouseRect.top, hdc, mouseRect.left, mouseRect.top, SRCCOPY))
{
MessageBox(hWnd, "BitBlt has failed", "Failed", MB_OK);
}
ReleaseDC(hWnd, hdc);
return 0;
I'm using the DrawText function in a Win32 program to display "Local" at the top center of the screen and "Server" in the center. When I run the program it displays "Local" but not "Server". Here is the code in my message loop:
case WM_PAINT:
{
RECT localLabel;
localLabel.left = 0;
localLabel.top = 0;
localLabel.right = 270;
localLabel.bottom = 20;
PAINTSTRUCT localPs;
HDC localHandle = BeginPaint(hwnd, &localPs);
DrawText(localHandle, "Local", -1, &localLabel, DT_CENTER);
EndPaint(hwnd, &localPs);
PAINTSTRUCT serverPs;
RECT serverLabel;
serverLabel.left = 0;
serverLabel.top = 100;
serverLabel.right = 270;
serverLabel.bottom = 20;
HDC serverHandle = BeginPaint(hwnd, &serverPs);
DrawText(serverHandle, "Server", -1, &serverLabel, DT_CENTER);
EndPaint(hwnd, &serverPs);
}
break;
I tried using the same PAINTSTRUCT but that didn't help. I tried using the same HDC but that didn't help either. How can I display both on the screen?
Thanks.
Your second rectangle is not valid (bottom should be 120 instead of 20 because it's the actual bottom coordinate, not the height). Also, you have to render both strings before calling EndPaint():
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT localLabel;
localLabel.left = 0;
localLabel.top = 0;
localLabel.right = 270;
localLabel.bottom = 20;
DrawText(hdc, "Local", -1, &localLabel, DT_CENTER);
RECT serverLabel;
serverLabel.left = 0;
serverLabel.top = 100;
serverLabel.right = 270;
serverLabel.bottom = 120;
DrawText(hdc, "Server", -1, &serverLabel, DT_CENTER);
EndPaint(hwnd, &ps);
Finally, as an aside, you probably don't want to leave all that code in one of your window procedure's case statements. Consider moving it into its own function to improve readability (and maintainability).
First of all, your bottom coordinate is over your top one, is that intentional?
Then, you should call BeginPaint/EndPaint just once for each WM_PAINT you receive. It typically goes like this:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC localHandle = BeginPaint(hwnd, &ps);
// do *all* the drawing
EndPaint(hwnd, &ps);
}
break;
"bottom" is exactly that, the bottom of the rect. You're using it as if it was the height.
serverLabel.bottom = serverLabel.top + 20;
Seems to me that serverLabel.bottom = 20; should be serverLabel.bottom = 120;