So i'm painting a bitmap, heres my code:
hdcMem = CreateCompatibleDC(hdc);
SelectObject(hdcMem, g_hBitmap);
GetObject(g_hBitmap, sizeof(bm), &bm);
BitBlt(hdc, 196 - (bm.bmWidth/2), 90, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
DeleteDC(hdcMem);
Sometimes, when I paint it with this code, the bitmap is not displayed. Although if i minimize/unminimize the window, the bitmap is displayed. I'm pretty sure there's no problems with my code so is there something else I should be doing?
EDIT:
Turns out it's not just bitmaps, if I draw text with TextOut sometimes it's not displayed until its minimized/unminimized. I don't think minimizing/unminimizing sends another WM_PAINT message, so I don't think that when i do that it's causing it to be repainted correctly.
Oh and the rest of the controls get painted normally, just the stuff inside WM_PAINT isn't painted.
UPDATEHere's the code thats causing the problems, it works 98% of the time too.
// This is a global variable
bool GlobalVar = false;
// This is a different thread started with _beginthread
void ThreadExample()
{
GlobalVar = true;
InvalidateRect(hMainWnd, NULL, TRUE);
_endthread();
}
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
if (GlobalVar == true)
{
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, 0x0000ff);
OrigFont = SelectObject(hdc, g_hLargeFont);
GetTextExtentPoint32(hdc, ErrorMsg, lstrlen(ErrorMsg), &sz);
TextOut(hdc, 196 - (sz.cx/2), 100, ErrorMsg, lstrlen(ErrorMsg));
SelectObject(hdc, OrigFont);
}
EndPaint(hWnd, &ps);
break;
EDIT2:Another important detail could be, in my actual application, this code is inside a if statement that checks a global variable, and paints if its true. And this variable is set from a different thread, and after the variable is set I call InvalidateRect(hMainWnd, NULL, TRUE);
Updated my example code to represent this.
What is immediately not good with this code snippet (you actually should rather have posted more details) is that you delete temporary DC with your global bitmap handle still selected into it. You need to do SelectObject once again to unselect your bitmap.
You normally do it like this:
HGDIOBJ hPreviousBitmap = SelectObject(hdcMem, g_hBitmap);
// ...
SelectObject(hdcMem, hPreviousBitmap);
Also, error checking never hurts. Possibly one of the API calls fail and it's important which one exactly as it sheds more light on the issue.
Related
Here the Painting the Window mentioned that:
After you finish painting the client area, you clear the update region, which tells the operating system that it does not need to send another WM_PAINT message until something changes.
One may write these code:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rect;
GetClientRect(hWnd, &rect);
HBRUSH brush = CreateSolidBrush(RGB(127, 127, 127));
FillRect(hdc, &rect, brush);
const wchar_t * lstr = L"here is information";
TextOut(hdc,
5, 5,
lstr, _tcslen(lstr));
DrawText(hdc, TEXT("Singleline in center~"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
MoveToEx(hdc, 50, 100, NULL);
LineTo(hdc, 44, 10);
LineTo(hdc, 78, 40);
Rectangle(hdc, 16, 36, 72, 70);
Rectangle(hdc, 34, 50, 54, 70);
DeleteObject(brush);
EndPaint(hWnd, &ps);
}
What means by clear here? We do not want what we have drawn to be cleared.
WM_PAINT messages are generated on demand (see Paint messages will come in as fast as you let them). For each window, the system maintains an update region. Clients can mark parts or all of a window's client area as "invalid", calling InvalidateRect or InvalidateRgn. Either one adds to the update area, but doesn't immediately trigger a WM_PAINT message.
When the system determines that it's time to send a WM_PAINT message, it is the client's responsibility to empty the update region when it's done painting so that no additional WM_PAINT messages are generated until the update region is non-empty again.
The call to BeginPaint does that for you, so you don't have to worry about this so long as you use standard WM_PAINT handling. If you do have more specific requirements (e.g. when using a Direct2D render target) you would have to manually clear the update region with a call to ValidateRect or ValidateRgn.
So I am experimenting with Win32 API and trying to plot a line with colour gradient (by creating a custom Pen). The code is working fine and I am getting my desired result but when I start resizing my window, CreatePen() function starts giving NULL value, and thus Windows start using the default black pen.
This is my WndProc code:
(https://pastebin.com/xiyzX5fu for full code)
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxClient, cyClient;
HDC hdc;
HPEN hPen;
PAINTSTRUCT ps;
switch (message)
{
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
case WM_PAINT:
printf("%d\t%d\n", cxClient, cyClient); //Printing the resolution
hdc = BeginPaint(hwnd, &ps);
MoveToEx(hdc, 0, cyClient / 2, NULL);
int count = 0;
//Plotting 256 lines each of length (cxClient/256) with different color
for (int i = 0; i < cxClient; i += cxClient / 256)
{
if ((hPen = CreatePen(PS_SOLID, 10, RGB(count, 255, 255))) == NULL)
{
DWORD lasterror;
lasterror = GetLastError();
printf("We got a NULL Value and last error code was %lu\n", lasterror);
break;
}
else
SelectObject(hdc, hPen);
count += 1; // For uniform color gradient across width
LineTo(hdc, i, cyClient / 2);
}
DeleteObject(hPen);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
I am trying to debug it for the last few hours and here are some of my observations:
My calculation for RGB are correct. In fact, even a default pen
CreatePen(PS_SOLID, 1, 0x00000000) leads to same error.
The error is more profound if the increment value is smaller, i.e.
for i += cxClient/5 (plots only 5 lines) CreatePen would return
NULL values much less often while resizing as compared to i += cxClient/256 (plots 256 lines)
(Line 59)
Interesting One: I print the size of my Client Area using printf (Line 53). The moment CreatePen() returns a NULL value,
the console will stop printing the statement containing the Client
Area and will only print the NULL error statement (Line 65) again and again. It will
print both only when I add a break (Line 66) after printing the
NULL error statement.
I think I am not able to capture the error of CreatePen() using
GetlastError() (Line 64) correctly because it always prints 0
(ERROR_SUCCESS) on the console.
Based on these observations I think the problem doesn't lie in CreatePen() but in my way of calculating the length of each line.
You are creating the pen, selecting it into the dc, and then deleting it while it is still selected in the dc. This will case DeleteObject to fail. You need to remember the old pen that was selected, and then select that back into the dc before deleting your pen:
HPEN oldPen;
...
oldPen = SelectObject(hdc, hPen);
...
SelectObject(hdc, oldPen);
DeleteObject(hPen);
Yes, you are leaking GDI object. When the number of GDI objects in your process reaches 10,000, the system will no longer allow the creation of new GDI objects. This is why hPen will eventually return NULL.
Furthermore, as we saw last time, the maximum number of window manager
objects that can be created is around 32,700. Giving a program 10,000
is already a third of the total amount available. That’s already
pretty darned generous, if you ask me. Preventing a program from
running away and consuming all of the window manager objects is an
attempt to contain the damage caused by a runaway program. Even if a
program goes haywire, there’s still around 20,000 objects available
for the other programs to use.
Refer: Why is the limit of window handles per process 10,000?
I don't get how I am supposed to handle brushes for coloring static text background.
At first everything looks nice as it is supposed to be:
However, after the statics have been redrawn for several times, they change to this:
I also noticed this depends on whether I'm straight returning the same brush in every case (for debugging) or using the actual code with different cases(grey boxes after first redrawing).
My WM_CTLCOLORSTATIC message handling looks like this:
case WM_CTLCOLORSTATIC:
{
HDC hdcStatic = (HDC) wParam;
SetTextColor(hdcStatic, RGB(0,0,0));
HBRUSH hbrDefault = CreateSolidBrush(RGB(255,255,255));
return (INT_PTR)hbrDefault;
(Simplified for debugging)
I guess this has something to do with freeing the brushes after using with DeleteObject(), but how could I do this when I need to return the brushes, but I want to delete them before leaving the function?
MSDN resources didn't help: WM_CTLCOLORSTATIC
EDIT : I found my mistake.
I declared my brushes as global variables like this:
HBRUSH hbrBkFoodCat[FOODCAT_LENGTH];
HBRUSH hbrDefault;
But then I initialised them on startup like this:
for(int i=0;i<FOODCAT_LENGTH;i++) {
hbrBkFoodCat[i] = CreateSolidBrush(foodCatClr[i]);
}
HBRUSH hbrDefault = CreateSolidBrush(RGB(255,255,255));
As you can see, I accidentally declared hbrDefault again but this time as a local variable, so at message handling I got that grey boxes (NULL brush).
What I tried out (stupid idea I know), was to initialize them at message handling. Since I just copy-pasted that initialization right into the handling, it became a local variable again, but this time it was 'in range' for the return. This lead me to the assumption something was wrong with freeing the brushes, because of having to redraw it numerous times before getting that grey background (still don't get this though).
Thank you all for your help anyway!
The MSDN documentation for that message says that you MUST free the brush. But you don't have to create/free it everytime. Just create it once and reuse. Free it when you don't need it anymore, but not in the message handler.
Do not create a new brush every time you process a WM_CTLCOLORSTATIC message. That is a resource leak. Create the brush one time, either when you first create the static text control, or when it sends you WM_CTLCOLORSTATIC for the first time. Keep returning that same brush for every WM_CTLCOLORSTATIC message:
HBRUSH hbrStaticBkg = NULL;
...
case WM_CTLCOLORSTATIC:
{
HDC hdc = (HDC) wParam;
SetTextColor(hdc, RGB(...));
if (!hbrStaticBkg) hbrStaticBkg = CreateSolidBrush(RGB(...));
return (LRESULT) hbrStaticBkg;
}
Destroy the brush only after you have destroyed the static text control.
DestroyWindow(hwndStatic);
if (hbrStaticBkg) {
DeleteObject(hbrStaticBkg);
hbrStaticBkg = NULL;
}
If you want to change the background color during the lifetime of the static text control, destroy the brush and invalidate the control to trigger a repaint, then create the new brush when requested.
COLOREREF clrStaticText = RGB(0,0,0);
COLORREF clrStaticBkg = RGB(255,255,255);
HBRUSH hbrStaticBkg = NULL;
...
case WM_CTLCOLORSTATIC:
{
HDC hdc = (HDC) wParam;
SetTextColor(hdc, clrStaticText);
if (!hbrStaticBkg) hbrStaticBkg = CreateSolidBrush(clrStaticBkg);
return (LRESULT) hbrStaticBkg;
}
...
clrStaticText = RGB(...);
clrStaticBkg = RGB(...);
if (hbrStaticBkg) {
DeleteObject(hbrStaticBkg);
hbrStaticBkg = NULL;
}
InvalidateRect(hwndStatic, NULL, TRUE);
I am using plain winapi c to create a GUI, I am new to this language and am struggling with something many might think is basic. Could someone please explain to me how I change the background colour for static text because currently is transparent. The code I am using for the text is:
hwndStatic = CreateWindow(TEXT("static"), TEXT(""),
WS_CHILD | WS_VISIBLE,
10, 70, 90, 25, hwnd, NULL, g_hinst, NULL);
In general, you change the drawing of static text controls by handling WM_GETCTLCOLORSTATIC.
In that handler, you can change things about the DC, like the text color, background mode, background color, even the font that's selected.
You can also return a handle to a GDI brush (using a cast to get it by the type system). The control will erase itself first with the brush and then draw the text.
The callback will happen for all static controls that are children of the current window, so you first test to see if it's the child you care about.
For example:
case WM_CTLCOLORSTATIC:
HWND hwnd = (HWND) lParam;
if (hwnd == hwndStatic) {
HDC hdc = (HDC) wParam;
::SetTextColor(hdc, RGB(0xFF, 0, 0)); // set the text to red
::SetBkMode(hdc, OPAQUE);
::SetBkColor(hdc, RGB(0x00, 0xFF, 0x00)); // set background to green
HBRUSH hbrBackground = ::GetSysColorBrush(COLOR_WINDOW);
return (INT_PTR) hbrBackground;
}
return 0;
This shows several things you can do. You probably don't want to do all of them, but it can be educational to see them all in action.
Note that if you create a brush to return, you have to keep track of it and delete it later. I've avoided this issue by relying on GetSysColorBrush. The system owns those, so you shouldn't delete them. You can also use GetStockObject for system GDI objects that you don't have to manage. But if you need a custom color, you'll have to use CreateSolidBrush and then clean it up.
Respond to the WM_CTLCOLORSTATIC message in your program and have it return a brush object of the proper color.
I've slightly modified the example from the link:
case WM_CTLCOLORSTATIC:
{
HWND hWnd = (HWND) lParam;
if (hWnd == hMyStatic)
{
HBRUSH hbrBkgnd = CreateSolidBrush(RGB(0,0,0));
return (INT_PTR)hbrBkgnd;
}
return 0;
}
When I use SetBkMode(hdc, TRANSPARENT); in the code below, I got the following effect when I resize the main window (and hence when the child receives the WM_PAINT message):
The problem is : When I resize the main window, The old area of "Find:" shoule be erased, I guess. But it just remains there.
If I don't use SetBkMode(hdc, TRANSPARENT);, I don't have this problem. It looks like:
, i.e it has white background. Furthermore, if I use SetBkMode(hdc, TRANSPARENT);, it looks like the same as above, before I resize the main window. So I don't think SetBkMode(hdc, TRANSPARENT); works here.
the hwnd is a static child with style SS_BITMAP.
Do you know why this issue occurs?
switch (message) {
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
SelectObject(hdc, gDefaultGuiFont);
SetBkMode(hdc, TRANSPARENT);
RECT rc;
GetClientRect(hwnd, &rc);
DrawText(hdc, _TR("Find:"), -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
.............
}
Try to use "fixed" rectangles. For example
RECT rc;
GetClientRect(hwnd, &rc);
rc.left += ...; rc.top += ...; // shift up-left point
DrawText(hdc, _TR("Find:"), -1, &rc, DT_SINGLELINE | DT_LEFT | DT_TOP);
The idea is you draw text in wrong position (once) and in right position (twice) while backgound updated only once. Can't say more on part of code.
The problem is that windows is not updating the control (in time) that's behind your static control, you are now responsible for it's contents. So you want to use the background provided by the parent. Well just ask the parent to draw it for you in the child window:
RECT rc;
GetClientRectRelative(m_hWnd, GetParent(m_hWnd), &rc);
SetWindowOrgEx(m_mdc, rc.left, rc.top, NULL);
SendMessage(GetParent(m_hWnd), WM_PAINT, (WPARAM)(HDC)m_mdc);
SetWindowOrgEx(m_mdc, 0, 0, NULL);
In which
bool GetClientRectRelative(HWND hWnd, HWND hWndRelativeTo, RECT *pRect)
{
RECT rcWnd, rcRelativeTo;
if (!GetClientRect(hWnd, &rcWnd) ||
!ClientToScreen(hWnd, (POINT*)&rcWnd) ||
!ClientToScreen(hWnd, (POINT*)&rcWnd + 1) ||
!GetClientRect(hWndRelativeTo, &rcRelativeTo) ||
!ClientToScreen(hWndRelativeTo, (POINT*)&rcRelativeTo) ||
!ClientToScreen(hWndRelativeTo, (POINT*)&rcRelativeTo + 1))
return false;
pRect->top = rcWnd.top - rcRelativeTo.top;
pRect->left = rcWnd.left - rcRelativeTo.left;
pRect->right = rcWnd.right - rcRelativeTo.left;
pRect->bottom = rcWnd.bottom - rcRelativeTo.top;
return true;
}
Now draw anything you like, I suggest you'd use the TRANSPARENT background mode.
Please create all your child windows with the styles WS_CLIPCHILDREN and WS_CLIPSIBLINGS, then these problems will become apparent immediately and you avoid flicker.