I am trying to draw a radiobutton with a transparent background using a subclassed radiobutton & WM_PAINT. I know how to do this using the TransparentBlt function & an off-screen (back)buffer.
My problem is that intially the radiobutton by default draws some text & circle (see image 1). My WM_PAINT message only consists of BeginPaint() and EndPaint(). When i minimize the window and afterwards activate the window again, the default text & circle get replaced by a black square like you would expect (see image 2).
The yellow window is also painted in the same manner using WM_PAINT.
Both procedures return 1 for WM_ERASEBKGND and return 0 for WM_PAINT as required for custom painting & double buffering.
Is this normal behaviour? I found a "fix" by using WS_EX_TRANSPARENT, but I would like to understand first why the radiobutton initially gets painted like that to determine if this is the right fix for me.
Thanks in advance.
Radiobutton creation:
MControlRect rect(0, 0, 100, 20);
unsigned long style = WS_CHILD | BS_AUTORADIOBUTTON;
if (isGroupStarter) {
style += WS_GROUP;
}
::HWND hWnd = _create(pControlParent, WC_BUTTON, style, rect);
::WNDPROC systemProc = (::WNDPROC)GetWindowLong(hWnd, GWL_WNDPROC);
::SetWindowLong(hWnd, GWL_WNDPROC, (long)customRadiobuttonProcedure);
::SetWindowLong(hWnd, GWL_USERDATA, (long)systemProc);
::UpdateWindow(hWnd);
::ShowWindow(hWnd, SW_SHOW);
Radiobutton procedure:
switch (msg) {
case WM_ERASEBKGND:
{
return 1;
break;
}
case WM_NCPAINT:
{
return 0;
break;
}
case WM_PAINT:
{
::PAINTSTRUCT ps;
::HDC hdc = ::BeginPaint(hWnd, &ps);
::EndPaint(hWnd, &ps);
return 0;
break;
}
}
::WNDPROC defaultWindowProc = (::WNDPROC)::GetWindowLong(hWnd, GWL_USERDATA);
return ::CallWindowProc(defaultWindowProc, hWnd, msg, wParam, lParam);
Image 1: initial radiobutton painted
Image 2: radiobutton after minimize > show again
Related
I am pretty new to C++ and the Windows API.
I cannot figure out how to change the background color of a button using WM_CTLCOLORBTN while simultaneously having a bitmap on the button. When I try to do this, the program crashes.
I have created a button like this:
HINSTANCE hInstance = GetModuleHandle(NULL);
HWND hbutton = CreateWindow(L"BUTTON", NULL, WS_VISIBLE | WS_CHILD | BS_BITMAP | BS_PUSHBUTTON, 15, 202.5, 96.5, 72.5, hWnd, (HMENU)301, hInstance, NULL);
In the WndProc function, I have written:
case WM_CTLCOLORBTN:
switch (((LPNMHDR)lParam) -> code)
{
MessageBox(hWnd, L"made it inside of the switch statement", L"Debug", 1);
}
I have also tried:
case WM_CTLCOLORBTN:
LPNMHDR button = (LPNMHDR)lParam;
if(button->idfrom == 301 && button->code == NM_CUSTOMDRAW)
{
}
In addition, I have tried:
case CDDS_PREERASE:
LPNMHDR button = (LPNMHDR)lParam;
if(button->idfrom == 301 && button->code == NM_CUSTOMDRAW)
{
}
Furthermore, I have tried:
case WM_NOTIFY:
MessageBox(hWnd, L"Code in wm_notify got ran", L"Debug", 1);
The code above does not cause a message box to appear, so it is assumed to have never ran.
All of these methods have resulted in the program crashing.
I have seen in some other posts people putting their code inside of WM_NOTIFY, but that results in the code not running at all.
Would anyone happen to know what I am doing wrong?
First, you don't need to specify an HINSTANCE when creating a system-defined window class. So, you can set the hInstance parameter to NULL.
Second, all of the message handlers you have shown are wrong:
The lParam parameter of the WM_CTLCOLORBTN message is an HWND window handle (of the button), not an LPNMHDR struct pointer. NMHDR structs are used only in WM_NOTIFY messages.
CDDS_PREERASE is not a window message. It is the value of a drawing stage used by the NM_CUSTOMDRAW message. CDDS_PREERASE has a numeric value of 3, which is the same value as the WM_MOVE window message, whose lParam contains X/Y coordinates, not an LPNMHDR pointer.
Most button notifications are delivered to a button's parent window using WM_COMMAND messages. There are only 3 button notifications that are delivered to the parent window using WM_NOTIFY messages - BCN_DROPDOWN, BCN_HOTITEMCHANGE, and NM_CUSTOMDRAW. The first 2 don't apply to your button example. The 3rd one applies only if your app has Comctl32.dll v6 enabled, in which case the lParam parameter is an LPNMCUSTOMDRAW struct pointer, not an LPNMHDR pointer.
Now then, the WM_CTLCOLORBTN documentation says:
Parameters
wParam
An HDC that specifies the handle to the display context for the button.
lParam
An HWND that specifies the handle to the button.
Return value
If an application processes this message, it must return a handle to a brush. The system uses the brush to paint the background of the button.
So, try something more like this instead:
HWND hButton = NULL;
HBRUSH hButtonBkg = NULL;
...
case WM_CREATE: {
...
hButton = CreateWindow(L"BUTTON", NULL, WS_VISIBLE | WS_CHILD | BS_BITMAP | BS_PUSHBUTTON, 15, 202.5, 96.5, 72.5, hWnd, (HMENU)301, hInstance, NULL);
hButtonBkg = CreateSolidBrush(RGB(...)); // <-- whatever color you want
...
break;
}
case WM_DESTROY: {
DeleteObject(hButtonBkg);
break;
}
case WM_CTLCOLORBTN: {
if (hButton == (HWND)lParam) {
HDC hdc = (HDC) wParam;
// configure hdc as needed...
SetTextColor(hdc, RGB(...)); // <-- whatever color you want
return (LRESULT) hButtonBkg;
}
break;
}
Alternatively:
case WM_CTLCOLORBTN: {
if (hButton == (HWND)lParam) {
HDC hdc = (HDC) wParam;
// configure hdc as needed...
SetTextColor(hdc, RGB(...)); // <-- whatever color you want
SetDCBrushColor(hdc, RGB(...)); // <-- whatever color you want
return (LRESULT) GetStockObject(DC_BRUSH);
}
break;
}
However, the WM_CTLCOLORBTN documentation also says:
However, only owner-drawn buttons respond to the parent window processing this message.
...
By default, the DefWindowProc function selects the default system colors for the button. Buttons with the BS_PUSHBUTTON, BS_DEFPUSHBUTTON, or BS_PUSHLIKE styles do not use the returned brush. Buttons with these styles are always drawn with the default system colors. Drawing push buttons requires several different brushes - face, highlight, and shadow - but the WM_CTLCOLORBTN message allows only one brush to be returned. To provide a custom appearance for push buttons, use an owner-drawn button. For more information, see Creating Owner-Drawn Controls.
So, you should add the BS_OWNERDRAW style to your button, in which case you will need to handle the WM_DRAWITEM message instead to custom-draw the button how you want.
I am trying to create a borderless window with the standard shadow. I read on the winapi documentation that setting pMarInset with negative values to the DwmExtendFrameIntoClientArea function, creates the "sheet of glass" effect where the client area is rendered without a window border. This gives me the result I wanted, which is a borderless window with the standard shadow. However, when I resize the window, I can see the standard frame behind the solid background color I gave in the WNDCLASS struct. I tried clearing the screen with a solid color but I get some weird results:
This is the window when active:
And when the window is inactive:
When I hover over the app icon in the taskbar, I see the solid color I tried clearing the screen with:
Here is my window procedure:
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(handle, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_HIGHLIGHT+1));
EndPaint(handle, &ps);
return 0;
} break;
case WM_ACTIVATE: {
MARGINS margins = {-1};
DwmExtendFrameIntoClientArea(handle, &margins);
return 0;
} break;
case WM_NCCALCSIZE: {
if(wparam){ return 0; }
return DefWindowProcA(handle, message, wparam, lparam);
} break;
I have a custom subclassed button created in the WM_CREATE message of my WindowProc callback. Here are the creation and subclassing instructions, along with a structure used to control the button state:
static button_state btnstateBtnInstall;
hBtnInstall = CreateWindow(WC_BUTTON, L"Button", WS_CHILD | WS_VISIBLE, (window_width / 2) - (btn_install_width / 2), window_height - (window_height / 6) - (btn_install_height / 2), btn_install_width, btn_install_height, hwnd, (HMENU)HMENU_btn_install, NULL, NULL);
SetWindowSubclass(hBtnInstall, BtnInstallProc, 0, (DWORD_PTR)&btnstateBtnInstall);
The struct is defined as follows:
struct button_state
{
bool pushed;
button_state() { pushed = false; }
};
The subclassed procedure is coded as follows:
LRESULT CALLBACK BtnInstallProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubClass, DWORD_PTR dwRefData)
{
button_state* state = (button_state*)dwRefData;
// Omitted part where I create brushes and font to be used for painting
switch (msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc = ps.rcPaint;
POINT pt;
GetCursorPos(&pt);
ScreenToClient(hwnd, &pt);
BOOL hover = PtInRect(&rc, pt);
if (state->pushed)
{
// Pushed
FillRect(hdc, &rc, hBrPushed);
}
else if (hover)
{
// Mouse over
FillRect(hdc, &rc, hBrHover);
}
else
{
// Normal
FillRect(hdc, &rc, hBrNormal);
}
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, RGB(255, 255, 255));
SelectFont(hdc, SegoeUI);
static LPCWSTR InstallBtnTxt = L"Install";
static int InstallBtnTxtLen = static_cast<int>(wcslen(InstallBtnTxt)); // Should be a safe cast, for small arrays like this one
DrawText(hdc, InstallBtnTxt, InstallBtnTxtLen, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
}
case WM_LBUTTONDOWN:
{
state->pushed = true;
break;
}
case WM_LBUTTONUP:
{
state->pushed = false;
break;
}
// Omitted part where I handle WM_DESTROY to do cleanup
}
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
To compare the behaviour of my button with a standard one, I created another button without subclassing and using only the standard BUTTON class and WS_VISIBLE | WS_CHILD attributes.
As you can see from the attached gif, when I repeatedly click on a standard class button, every click produces the animation while when hitting multiple times the button I created with this code it seems like it isn't catching every click but (as you can see) something like 50% of them.
What am I missing? Why isn't my button as responsive as the one that comes with the standard class?
TL;DR: when clicking slowly on my subclassed button, the painting is correct. As you can see from my attached image, the difference between mine and a standard button is big when repeating clicks on them. How can I make my control be as responsive as the standard one?
When you are clicking fast some WM_LBUTTONDOWN messages are substituted by the WM_LBUTTONDBLCLK (default double click handling). You need to handle it just like you are handling the WM_LBUTTONDOWN message.
case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK: // <-- Added
{
state->pushed = true;
break;
}
I'm trying to make a layered window with c++ win32 but I'm having a problem with the drawing or "collision" of it
For reference the picture that I'm trying to display.
This is the basic creation of the window
//window
DWORD exFlags = 0;
if(m_bTransparent)
exFlags |= WS_EX_LAYERED;
Create(WS_POPUP, exFlags);
std::wstring sPic(L"power-disconnected.png");
m_pAlertPic = m_pPowerMon->GetGPPicMan()->LoadPicture(sPic.c_str());
// make the window layered when using transparency
if(m_bTransparent && m_pAlertPic != nullptr)
{
HDC hdcScreen = GetDC(GetHandle());
HDC hdc = CreateCompatibleDC(hdcScreen);
HBITMAP hbmpold = (HBITMAP)SelectObject(hdc, m_pAlertPic->GetBuffer());
POINT dcOffset = {0, 0};
SIZE size = {ww, wh};
BLENDFUNCTION bf = {AC_SRC_OVER, 0, (int) (2.55 * 100), AC_SRC_ALPHA}; // blend function combines opacity and pixel based transparency
UpdateLayeredWindow(GetHandle(), hdcScreen, NULL, &size, hdc, &dcOffset, RGB(255, 255, 255), &bf, ULW_ALPHA);
SelectObject(hdc, hbmpold);
DeleteDC(hdc);
ReleaseDC(GetHandle(), hdcScreen);
}
and the message loop
int WindowAlert::WndProc(Gnaq::WindowBase* pWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CLOSE:
Hide();
return 1;
case WM_PAINT:
// only draw when the widow is not transparent
// layered window redraw them self
if(!m_bTransparent)
m_pCanvas->Draw(m_pGraphics);
break;
case WM_LBUTTONUP:
pWnd->Hide();
m_bDismised = true;
break;
}
return DefWindowProcW(pWnd->GetHandle(), msg, wParam, lParam);
}
So this is the result I get
As you can see with this method I'm getting white borders where is should actually be fully transparent, but the semi transparent parts do work correctly.
here's what I've tried that gave me a "useful" change.
First I just tried to add the ULW_COLORKEY flag to hide the white color
UpdateLayeredWindow(GetHandle(), hdcScreen, NULL, &size, hdc, &dcOffset, RGB(255, 255, 255), &bf, ULW_ALPHA | ULW_COLORKEY);
And the result.
So this hides the white border but also all the white in the picture.
Next thing I've tried was using SetLayeredWindowAttributes in combination of UpdateLayeredWindow, without the ULW_COLORKEY flag
SetLayeredWindowAttributes(GetHandle(), 0xFFFFFF00, 255, LWA_COLORKEY);
Also in the window proc enable the paint, like this
case WM_PAINT:
m_pCanvas->Draw(m_pGraphics);
break;
This way I'm visually getting what I want like this
But the problem with his approach is that it the complete window is click able while with just using the UpdateLayeredWindow only the parts that should be fully transparent are click able like it should be. I also have the feeling with this last approach that it is more a "hack" than a decent approach.
So i hope that someone can tell me what I'm doing wrong.
The first way was the correct. The fault was in the bitmap, which didn't had premultiplied alpha
Can you change the background of text in area of edit control that would stay static?
In the parent of the edit control, handle the WM_CTLCOLORSTATIC message, the wParam of this message is the HDC that the Edit control is about to draw with,
for most CTLCOLOR messages, if you set text and background colors into this DC, the control will use the colors you set.
You can also return an HBRUSH and the contol will use that for any brush painting that it wil do, but many controls don't use brushes much, so that will have limited effect for some
CTLCOLOR messages. Your best bet here is to return the DC brush, and set the DC Brush color to match the BkColor of the DC.
LRESULT lRet = 0; // return value for our WindowProc.
COLORREF crBk = RGB(255,0,0); // use RED for Background.
...
case WM_CTLCOLORSTATIC:
{
HDC hdc = (HDC)wParam;
HWND hwnd = (HWND)lParam;
// if multiple edits and only one should be colored, use
// the control id to tell them apart.
//
if (GetDlgCtrlId(hwnd) == IDC_EDIT_RECOLOR)
{
SetBkColor(hdc, crBk); // Set to red
SetDCBrushColor(hdc, crBk);
lRet = (LRESULT) GetStockObject(DC_BRUSH); // return a DC brush.
}
else
{
lRet = DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
break;
WM_CTLCOLORSTATIC is for static text control.
To be simple, you can do this in your winproc:
...
case WM_CTLCOLOREDIT:
{
HDC hdc = (HDC)wParam;
SetTextColor(hdc, yourColor); // yourColor is a WORD and it's format is 0x00BBGGRR
return (LRESULT) GetStockObject(DC_BRUSH); // return a DC brush.
}
...
If you have more than 1 edit control, you can use the item id and lParam to check which one need to be change.
WM_CTLCOLOREDIT allows you to set text and background color(+brush), if you want more control than that, you have to subclass and paint yourself
you could do something like this:
CBrush bkBrush;
RECT ctrlRect;
COLORREF crBk = RGB(255,0,0); // Red color
bkBrush.CreateSolidBrush(crBk);
CWnd* pDlg = CWnd::GetDlgItem(IDC_EDIT);
pDlg->GetClientRect(&ctrlRect);
pDlg->GetWindowDC()->FillRect(&ctrlRec, &bkBrush);
pDlg->GetWindowDC()->SetBkColor(crBk);
This should change the background color of the edit control
All you need is to set the required color in control's device context and pass an HBRUSH with same color in WM_CTLCOLOREDIT message. If you want to change both foreground & background colors, use SetTextColor t0 change the text color. But you must pass the background color HBRUSH. But if you want to change the text color only, then you must pass a DC_BRUSH with GetStockObject function.