WinAPI managing brushes - c

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);

Related

Win32 API child window flicker

I'm new to the Win32 API and can't understand why all of my child windows are flickering so much whenever I input something into them. Does anyone know why it is flickering? Below is what I coded; it doesn't seem to work as intended. Even though I'm sure I handled all of my handles correctly and used the pointers properly throughout the code.
#include <windows.h>
#include <string.h>
#include <stdlib.h>
#define CONVERT_TAX 1
LRESULT CALLBACK WindowProcedure(HWND,UINT,WPARAM,LPARAM);
void AddMenus(HWND hWnd);
void AddControls(HWND hWnd);
int TaxConversion(int TaxRate);
HWND hOut;
HWND hTax;
int main(HINSTANCE hInst, HINSTANCE vPrevInst,LPSTR args, int ncmdshow){
WNDCLASSW wc ={};
wc.hIcon=LoadIcon(NULL, IDI_APPLICATION); //The icon handle for the window class. LoadIcon(NULL, IDI_APPLICATION) loads the default application icon.
wc.cbWndExtra=0; //The number of extra bytes to allocate for each individual window. Do not confuse this with cbClsExtra, which is common to all instances. This is often 0.
wc.cbClsExtra=0; //The number of extra bytes to allocate for the window class. For most situations, this member is 0.
wc.style=0; //
wc.hbrBackground=(HBRUSH)COLOR_WINDOW; //A handle to the background brush. GetStockObject (WHITE_BRUSH) gives a handle to a white brush. The return value must be cast because GetStockObject returns a generic object.
wc.hCursor=LoadCursor(NULL,IDC_ARROW); //The cursor handle for the window class. LoadCursor(NULL, IDC_ARROW) loads the default cursor.
wc.hInstance=hInst; //The instance handle. Just assign the hInst argument in WinMain to this field.
wc.lpszClassName=L"myWindowClass"; //The class name that identifies this window class structure. In this example, the CLSNAME global variable stores the window class name.
wc.lpfnWndProc=WindowProcedure; //Stores the address of the window procedure, a function that handles events for all windows that are instances of this window class.
if(!RegisterClassW(&wc))
return -1;
CreateWindowW(L"myWindowClass",L"My Window",WS_OVERLAPPEDWINDOW|WS_VISIBLE,500,500,500,500,NULL,NULL,NULL,NULL);
MSG msg = {0};
while(GetMessage(&msg,NULL,0,0)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProcedure(HWND hWnd,UINT msg,WPARAM wp,LPARAM lp){ //Procedures for Events
switch(msg){
case WM_COMMAND:
switch(wp){
case CONVERT_TAX:
char Tax[100];
GetWindowText(hTax,Tax,100);
SetWindowText(hOut,Tax);
break;
}
case WM_CREATE:
AddControls(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProcW(hWnd,msg,wp,lp);
}
return 0;
}
void AddControls(HWND hWnd){
CreateWindowW(L"Static",L"Enter Tax Rate",WS_CHILD|WS_VISIBLE,1,1,90,90,hWnd,0,0,0);
hTax=CreateWindowW(L"Edit",L"",WS_CHILD|WS_BORDER|WS_VISIBLE,1,92,90,90,hWnd,0,0,0);
CreateWindowW(L"Button",L"Find Amount",WS_CHILD|WS_BORDER|WS_VISIBLE,1,183,90,90,hWnd,(HMENU)CONVERT_TAX,0,0);
hOut=CreateWindowW(L"Edit",L"",WS_CHILD|WS_BORDER|WS_VISIBLE,1,267,90,90,hWnd,0,0,0);
}
I see two mistakes in your code:
when setting wc.hbrBackground, you must add +1 to the color value, per the documentation:
https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassw
hbrBackground
Type: HBRUSH
A handle to the class background brush. This member can be a handle to the physical brush to be used for painting the background, or it can be a color value. A color value must be one of the following standard system colors (the value 1 must be added to the chosen color)...
wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1); // <-- add +1
your WM_COMMAND handler is missing a break after the switch block, so every command message received will fall through into your WM_CREATE handler, thus creating more and more child controls.
case WM_COMMAND:
switch(wp){
case CONVERT_TAX:
char Tax[100];
GetWindowText(hTax,Tax,100);
SetWindowText(hOut,Tax);
break;
}
break; // <-- add this

Win32 GDI color palette transparency bug

This question could be considered more of a bug report on an unsightly and time-wasting issue I've recently encountered while using Win32/GDI:
That is, loading a bitmap image into static control (a bitmap static control, not icon). I'll demonstrate with the following code (this follows the creation of the main window):
HBITMAP hbmpLogo;
/* Load the logo bitmap graphic, compiled into the executable file by a resource compiler */
hbmpLogo = (HBITMAP)LoadImage(
wc.hInstance, /* <-- derived from GetModuleHandle(NULL) */
MAKEINTRESOURCE(ID_LOGO), /* <-- ID_LOGO defined in a header */
IMAGE_BITMAP,
0, 0,
LR_CREATEDIBSECTION | LR_LOADTRANSPARENT);
/* We have a fully functioning handle to a bitmap at this line */
if (!hbmpLogo)
{
/* Thus this statement is never reached */
abort();
}
We then create the control, which is a child of the main window:
/* Add static control */
m_hWndLogo = CreateWindowExW(
0, /* Extended styles, not used */
L"STATIC", /* Class name, we want a STATIC control */
(LPWSTR)NULL, /* Would be window text, but we would instead pass an integer identifier
* here, formatted (as a string) in the form "#100" (let 100 = ID_LOGO) */
SS_BITMAP | WS_CHILD | WS_VISIBLE, /* Styles specified. SS = Static Style. We select
* bitmap, rather than other static control styles. */
32, /* X */
32, /* Y */
640, /* Width. */
400, /* Height. */
hMainParentWindow,
(HMENU)ID_LOGO, /* hMenu parameter, repurposed in this case as an identifier for the
* control, hence the obfuscatory use of the cast. */
wc.hInstance, /* Program instance handle appears here again ( GetModuleHandle(NULL) )*/
NULL);
if (!m_hWndLogo)
{
abort(); /* Also never called */
}
/* We then arm the static control with the bitmap by the, once more quite obfuscatory, use of
* a 'SendMessage'-esque interface function: */
SendDlgItemMessageW(
hMainParentWindow, /* Window containing the control */
ID_LOGO, /* The identifier of the control, passed in via the HMENU parameter
* of CreateWindow(...). */
STM_SETIMAGE, /* The action we want to effect, which is, arming the control with the
* bitmap we've loaded. */
(WPARAM)IMAGE_BITMAP, /* Specifying a bitmap, as opposed to an icon or cursor. */
(LPARAM)hbmpLogo); /* Passing in the bitmap handle. */
/* At this line, our static control is sufficiently initialised. */
What is not impressive about this segment of code is the mandated use of LoadImage(...) to load the graphic from the program resources, where it is otherwise seemingly impossible to specify that our image will require transparency. Both flags LR_CREATEDIBSECTION and LR_LOADTRANSPARENT are required to effect this (once again, very ugly and not very explicit behavioural requirements. Why isn't LR_LOADTRANSPARENT good on its own?).
I will elaborate now that the bitmap has been tried at different bit-depths, each less than 16 bits per pixel (id est, using colour palettes), which incurs distractingly unaesthetical disuniformity between them. [Edit: See further discoveries in my answer]
What exactly do I mean by this?
A bitmap loaded at 8 bits per pixel, thus having a 256-length colour palette, renders with the first colour of the bitmap deleted (that is, set to the window class background brush colour); in effect, the bitmap is now 'transparent' in the appropriate areas. This behaviour is expected.
I then recompile the executable, now loading a similar bitmap but at (a reduced) 4 bits per pixel, thus having a 16-length colour palette. All is good and well, except I discover that the transparent region of the bitmap is painted with the WRONG background colour, one that does not match the window background colour. My wonderful bitmap has an unsightly grey rectangle around it, revealing its bounds.
What should the window background colour be? All documentation leads back, very explicitly, to this (HBRUSH)NULL-inclusive eyesore:
WNDCLASSEX wc = {}; /* Zero initialise */
/* initialise various members of wc
* ...
* ... */
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); /* Here is the eyesore. */
Where a certain colour preset must be incremented, then cast to a HBRUSH typename, to specify the desired background colour. 'Window colour' is an obvious choice, and a fragment of code very frequently recurring and reproducible.
You may note that when this is not done, the Window instead assumes the colour of its preceding number code, which on my system happens to be the 'Scroll' colour. Indeed, and alas, if I happen to forget the notorious and glorious +1 appended to the COLOR_WINDOW HBRUSH, my window will become the unintended colour of a scroll bar.
And it seems this mistake has propagated within Microsofts own library. Evidence? That a 4-bpp bitmap, when loaded, will also erase the bitmap transparent areas to the wrong background color, where a 8-bpp bitmap does not.
TL;DR
It seems the programmers at Microsoft themselves do not fully understand their own Win32/GDI interface jargon, especially regarding the peculiar design choice behind adding 1 to the Window Class WNDCLASS[EX] hbrBackground member (supposedly to support (HBRUSH)NULL).
This is unless, of course, anyone can spot a mistake on my part?
Shall I submit a bug report?
Many thanks.
As though to patch over a hole in a parachute, there is a solution that produces consistency, implemented in the window callback procedure:
LRESULT CALLBACK WndProc(HWND hWnd, UINT uiMsg, WPARAM wp, LPARAM lp)
{
/* ... */
switch (uiMsg)
{
/* This message is sent to us as a 'request' for the background colour
* of the static control. */
case WM_CTLCOLORSTATIC:
/* WPARAM will contain the handle of the DC */
/* LPARAM will contain the handle of the control */
if (lp == (LPARAM)g_hLogo)
{
SetBkMode((HDC)wp, TRANSPARENT);
return (LRESULT)GetSysColorBrush(COLOR_WINDOW); /* Here's the magic */
}
break;
}
return DefWindowProc(hWnd, uiMsg, wp, lp);
}
It turns out the problem was not reproducible when other transparent bitmaps of varying sizes (not only bit depths) were loaded.
This is horrible. I am not sure why this happens. Insights?
EDIT: All classes have been removed to produce a neat 'minimal reproducible example'.

Load an image only once (SDL2)

I want to simulate a tv screen in my game. A 'no signal' image is displayed all along. It will be replaced by a scene of a man shooting another one, that's all. so I wrote this, which load my image every time :
void Display_nosignal(SDL_Texture* Scene, SDL_Renderer* Rendu)
{
SDL_Rect dest = {416, 0, 416, 416};
SDL_Rect dest2 = {SCENE};
SDL_SetRenderTarget(Rendu, Scene);
SDL_Rect src = {(1200-500)/2,0,500,545};
/*Scene = IMG_LoadTexture(Rendu, "mages/No_Signal.jpg");
if (Scene==NULL){printf("Erreur no signal : %s\n", SDL_GetError());}*/
if (Scene == NULL)
{
Scene = IMG_LoadTexture(Rendu, "mages/No_Signal.jpg");
if (Scene == NULL){printf("Erreur no signal : %s\n", SDL_GetError());}
}
SDL_RenderCopy(Rendu, Scene, NULL, &src);
SDL_SetRenderTarget(Rendu, NULL);
//SDL_DestroyTexture(Scene);
//500, 545
}
and it causes memory leaks. I've tried to destroy the texture in the loop etc., but nothing changes. so, can you advice me some ways to load the image at the very beginning , keep it , and display it only when needed.
I agree with the commenters about dedicated texture loader being a correct solution, but if you only want this behavior for one particular texture it may be an overkill. In that case you can write a separate function which loads this particular texture and make sure it is only called once.
Alternatively, you can use static variables. If a variable declared in a function is marked as static it will retain its value across calls to that function. You can find a simple example here (it's a tutorial-grade source but it shows basic usage) or here (SO source).
By modifying your code ever so slightly you should be able to make sure that the texture is loaded only once. By marking a pointer to it as static you ensure that its value (so address of the loaded texture) is not lost bewteen calls to the function. Afterwards the pointer will live in memory until the program terminates. Thanks to this, we do not have to free the texture's memory (unless you explicitly want to free it at some point, but then the texture manager is probably a better idea). A memory leak will not occur, since we are never going to lose the reference to the texture.
void Display_nosignal(SDL_Texture* Scene, SDL_Renderer* Rendu)
{
static SDL_Texture* Scene_cache = NULL; // Introduce a variable which remembers the texture across function calls
SDL_Rect dest = {416, 0, 416, 416};
SDL_Rect dest2 = {SCENE};
SDL_SetRenderTarget(Rendu, Scene);
SDL_Rect src = {(1200-500)/2,0,500,545};
if (Scene_cache == NULL) // First time we call the function, Scene_cache will be NULL, but in next calls it will point to a loaded texture
{
Scene_cache = IMG_LoadTexture(Rendu, "mages/No_Signal.jpg"); // If Scene_cache is NULL we load the texture
if (Scene_cache == NULL){printf("Erreur no signal : %s\n", SDL_GetError());}
}
Scene = Scene_cache; // Set Scene to point to the loaded texture
SDL_RenderCopy(Rendu, Scene, NULL, &src);
SDL_SetRenderTarget(Rendu, NULL);
}
If you care about performance and memory usage etc you should read about consequences of static variables, for instance their impact on cache and how they work internally. This might be considered a "dirty hack" but it might be just enough for a small project that does not need bigger solutions.

plain winapi c GUI changing static text background

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;
}

WM_PAINT doesn't show up after painting

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.

Resources