I am creating a basic GUI with the Windows API and I have run into an issue. It starts with a main window that opens with a custom background color I set (RGB(230,230,230)). It then displays text in the upper left corner with the static control.
settingstext = CreateWindow("STATIC",
"SETTINGS",
SS_LEFT | WS_CHILD,
12,
20,
100,
20,
hwnd,
NULL,
proginstance,
NULL);
ShowWindow(settingstext, 1);
This works, but when the text is displayed I need a way to change the background of it to match the main window or else it just looks like it doesn't blend in.
My question is, how do I do this? I currently use the method below and it works, but I wanted to know, is there a way to permanently set the background color somehow, right after the CreateWindow function for the static control without changing system colors, and just have it apply to that one control and not anything that sends the WM_CTLCOLORSTATIC message. I have experimented around with using the GetDC function and SetBkColor function outside of the message loop but nothing works.
case WM_CTLCOLORSTATIC:
{
HDC hdcStatic = (HDC) wParam;
SetTextColor(hdcStatic, RGB(0,0,0));
SetBkColor(hdcStatic, RGB(230,230,230));
return (INT_PTR)CreateSolidBrush(RGB(230,230,230));
}
I want to do this because...
I don't want to fill up my message loop with functions that need to be called every time the window repaints.
Have the changes apply to only this static control.
I would be very thankful for any help that could be provided, at least pointing me in the right direction, thanks.
For static text controls there's no permanent way to set the text color or their background. Even if you want to apply the changes to a single static control; you would still have to handle WM_CTLCOLORSTATIC notification message in parent dlgproc just when the control is about to be drawn.
This is due to the DefWindowProc overwriting your changes to the device context each time it handles WM_CTLCOLORSTATIC as stated in the MSDN:
By default, the DefWindowProc function selects the default system colors for the static control.
static HBRUSH hBrush = CreateSolidBrush(RGB(230,230,230));
case WM_CTLCOLORSTATIC:
{
if (settingstext == (HWND)lParam)
//OR if the handle is unavailable to you, get ctrl ID
DWORD CtrlID = GetDlgCtrlID((HWND)lParam); //Window Control ID
if (CtrlID == IDC_STATIC1) //If desired control
{
HDC hdcStatic = (HDC) wParam;
SetTextColor(hdcStatic, RGB(0,0,0));
SetBkColor(hdcStatic, RGB(230,230,230));
return (INT_PTR)hBrush;
}
}
If you're looking to make the control's background transparent over a parent dialog you could use SetBkMode(hdcStatic, TRANSPARENT).
I think there is a permanent way to do it.
Just after you create the label,use GetDC() function to get the Device Context.
Then use:
SetTextColor(hdcStatic, RGB(0,0,0));
SetBkColor(hdcStatic, RGB(230,230,230)); // Code Copied from the above answer by cpx.
And it should do .
Have you considered subclassing the static window and doing owner draw?
Related
I am trying to implement a static control which refreshes(change text) in response to some event, which occurs once every second. Since I didn't want to paint the entire client area every second and so I decided to use a static control, now the problem is the parent window is skinned, meaning it has custom bitmap as its background, and the static control doesn't fit in, so am looking for ways to make the static control's background transparent.
This is what I have now:
hHandle = CreateWindowEx( WS_EX_TRANSPARENT, "STATIC", "", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, 60, 212, 477, 20, hwnd, 0, hInstance, 0) ;
case WM_CTLCOLORSTATIC:
{
HDC hdC = (HDC)wParam;
SetTextColor( hdC, RGB(31,122,179) );
SetBkMode( hdC, TRANSPARENT );
return 0;//(HRESULT)GetStockObject(NULL_BRUSH);
}
break;
I tried returning NULL brush to paint the background, hoping it would make its background transparent but it didn't what more it forced the static control to not repaint properly, what I mean is that the text gets painted on top of the old text so its all messy.
Is subclassing is the only option ?
The only way I've found to do this reliably is to sub-class the static control and paint the background manually.
WS_EX_TRANSPARENT does not make a control truly transparent to underlying pixels (although it may appear like that) and WS_EX_COMPOSITED can not be used for child windows.
Instead, sub-class the static, and catch the WM_ERASEBKGND message. You can then paint the appropriate portion of the underlying bitmap.
The way to get a WC_STATIC control to show only text in the color you choose (over an image or other surface) is the return, as I understand this. This is what worked for me from this link.
case WM_CTLCOLORSTATIC:
SetTextColor((HDC)wParam, RGB(255, 0, 0));
SetBkMode((HDC)wParam, TRANSPARENT);
// the correct return needs HOLLOW_BRUSH
return (LRESULT)GetStockObject(HOLLOW_BRUSH);
break;
You don't mention the window styles on the static control, specifically the extended styles WS_EX_TRANSPARENT seems intended to solve the problem you describe with the repaint. Also as I understand it WS_EX_COMPOSITED might be useful in your context.
You also might consider whether the background window should have WS_CLIPCHILDREN set as that might affect the repaint.
I need my 6 controls (child windows of the main window) to get larger when the main window gets resized by the user (dragging the corners). I thought I could accomplish this by using the MoveWindow function to change the proportions of each child in the main window's WM_SIZE or WM_SIZING function. Doing this made the Debug build go strange (multiple windows, image of the window sticking after exiting, etc). The Release build ran fine but the child windows didn't change when I resized the main window.
I found http://msdn.microsoft.com/en-us/library/ms632598%28v=VS.85%29.aspx#creating_enumerating_etc used a different method of doing this: by enumerating all the child windows, and the enum callback function handling the window resizing through a unique ID assigned to every child. Upon trying this myself, it made no difference to the controls when the main window got resized.
Why isn't this working?
In the main windows switch statement:
case WM_SIZING:
GetClientRect(hwnd, &hwndRect);
EnumChildWindows(hwnd, EnumChildProc, (LPARAM)&hwndRect);
break;
The child enumerator callback function:
BOOL CALLBACK EnumChildProc(HWND hwndChild, LPARAM lParam)
{
LPRECT hwndRect = (LPRECT)lParam;
switch(GetWindowLong(hwndChild, GWL_ID))
{
case ID_CHILD_LLABEL:
MoveWindow(hwndChild, 0, 0, (hwndRect->right - hwndRect->left) - 30, 20, false);
break;
case ID_CHILD_LDIR:
MoveWindow(hwndChild, 12, 20, (hwndRect->right - hwndRect->left) - 40, 20, false);
break;
case ID_CHILD_LLIST:
MoveWindow(hwndChild, 12, 40, (hwndRect->right - hwndRect->left) - 40, (hwndRect->bottom - hwndRect->top) - 238, false);
break;
}
}
From MSDN's article on WM_SIZE: "If the SetScrollPos or MoveWindow function is called for a child window as a result of the WM_SIZE message, the bRedraw or bRepaint parameter should be nonzero to cause the window to be repainted."
I suspect the child controls are moving, they simply aren't being repainted.
It might also be worth verifying that your switch cases are actually being hit.
Edit:
I missed the obvious. You are responding to WM_SIZING, which indicates that the size of the window is about to (but has not yet) changed. WM_SIZE indicates that the size has changed. If you want to use WM_SIZING, you need to use the rect carried in the lParam, not the results of GetClientRect. Unfortunately the WM_SIZING rect is the rect of the window, not the client area, and is in screen coordinates. Unless you really need to display the resized controls while the user is still performing the resize, it would be much easier to just handle the WM_SIZE message.
I am not using a dialog, I'm using my own custom class which I have registered and then used the CreateWindow call to create it, I have preset the background color to red when registering:
WNDCLASSEX wc;
wc.hbrBackground = CreateSolidBrush(RGB(255, 0, 0));
But now I want to change the background color at runtime, by e.g. clicking a button to change it to blue.
I have tried to use SetBkColor() call in the WM_PAINT, and tried returning a brush from the WM_CTLCOLORDLG message, they don't work.
Any help?
From Window Background comes:
...The system paints the background for a
window or gives the window the
opportunity to do so by sending it a
WM_ERASEBKGND message when the
application calls BeginPaint. If an
application does not process the
message but passes it to
DefWindowProc, the system erases the
background by filling it with the
pattern in the background brush
specified by the window's class.....
...... An application can process the
WM_ERASEBKGND message even though a
class background brush is defined.
This is typical in applications that
enable the user to change the window
background color or pattern for a
specified window without affecting
other windows in the class. In such
cases, the application must not pass
the message to DefWindowProc. .....
So, use the WM_ERASEBKGND message's wParam to get the DC and paint the background.
You may try the following:
HBRUSH brush = CreateSolidBrush(RGB(0, 0, 255));
SetClassLongPtr(hwnd, GCLP_HBRBACKGROUND, (LONG_PTR)brush);
Short answer: Handle WM_ERASEBKGND.
Longer answer:
When you register the WNDCLASS, you're providing information about all windows of that class. So if you want to change the color of just one instance of the window, you'll need to handle it yourself.
When it's time to repaint your window, the system will send your wndproc a WM_ERASEBKGND message. If you don't handle it, the DefWindowProc will erase the client area with the color from the window class. But you can handle the message directly, painting whatever color (or background pattern) you like.
I can set the back color when i am registering the class, e.g.:
wincl.hbrBackground = CreateSolidBrush(RGB(202, 238, 255));
RegisterClassEx(&wincl);
But how would i do it to any window i have created with the CreateWindow function?
like a button on my main window, i have visual styles enabled, and i can notice the windows default gray back color behind the button.
Don't tell me i have to SetWindowLong for the window procedure on allllllll my controls and intercept the WM_PAINT :(
All the windows controls send a message to their parent to get the brush to use to fill their background.
Assuming you save a copy of the brush handle somewhere, you can do the following in your WindowProc, or DialogProc, to ensure everything draws with the correct background brush.
case WM_CTLCOLORSTATIC:
case WM_CTLCOLORBTN:
HDC hdc;
HWND hwndCtl;
POINT pt;
hdc = (HDC)wParam;
hwndCtl = (HWND)lParam;
pt.x = 0;
pt.y = 0;
MapWindowPoints(hwndCtl,_hwnd,&pt,1);
x = -pt.x;
y = -pt.y;
SetBrushOrgEx(hdc,x,y,NULL);
return (INT_PTR)_skinBrush;
If you want a customized window you can create your own window class to draw that type of window. Implement a handler for wm_paint and draw whatever you want for the window. There are a lot of tutorials available.
I'd like to be able to do some drawing to the right of the menu bar, in the nonclient area of a window.
Is this possible, using C++ / MFC?
Charlie hit on the answer with WM_NCPAINT. If you're using MFC, the code would look something like this:
// in the message map
ON_WM_NCPAINT()
// ...
void CMainFrame::OnNcPaint()
{
// still want the menu to be drawn, so trigger default handler first
Default();
// get menu bar bounds
MENUBARINFO menuInfo = {sizeof(MENUBARINFO)};
if ( GetMenuBarInfo(OBJID_MENU, 0, &menuInfo) )
{
CRect windowBounds;
GetWindowRect(&windowBounds);
CRect menuBounds(menuInfo.rcBar);
menuBounds.OffsetRect(-windowBounds.TopLeft());
// horrible, horrible icon-drawing code. Don't use this. Seriously.
CWindowDC dc(this);
HICON appIcon = (HICON)::LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
::DrawIconEx(dc, menuBounds.right-18, menuBounds.top+2, appIcon, 0,0, 0, NULL, DI_NORMAL);
::DestroyIcon(appIcon);
}
}
In order to draw in the non-client area, you need to get the "window" DC (rather than "client" DC), and draw in the "window" DC.
You should try handling WM_NCPAINT. This is similar to a normal WM_PAINT message, but deals with the entire window, rather than just the client area. The MSDN documents on WM_NCPAINT provide the following sample code:
case WM_NCPAINT:
{
HDC hdc;
hdc = GetDCEx(hwnd, (HRGN)wParam, DCX_WINDOW|DCX_INTERSECTRGN);
// Paint into this DC
ReleaseDC(hwnd, hdc);
}
This code is intended to be used in the message loop of your applicaton, which is canonically organized using a large 'switch' statement.
As noted in the MFC example from Shog, make sure to call the default version, which in this example would mean a call to DefWindowProc.
If you just want something in the menu bar, maybe it is easier/cleaner to add it as a right-aligned menu item. This way it'll also work with different Windows themes, etc.