I am trying to make a radio button control with a transparent background using only Win32 when themes are enabled. The reason for doing this is to allow a radio button to be placed over an image and have the image show (rather than the grey default control background).
What happens out of the box is that the control will have the grey default control background and the standard method of changing this by handling either WM_CTLCOLORSTATIC or WM_CTLCOLORBTN as shown below does not work:
case WM_CTLCOLORSTATIC:
hdcStatic = (HDC)wParam;
SetTextColor(hdcStatic, RGB(0,0,0));
SetBkMode(hdcStatic,TRANSPARENT);
return (LRESULT)GetStockObject(NULL_BRUSH);
break;
My research so far indicates that Owner Draw is the only way to achieve this. I've managed to get most of the way with an Owner Draw radio button - with the code below I have a radio button and a transparent background (the background is set in WM_CTLCOLORBTN). However, the edges of the radio check are cut off using this method - I can get them back by uncommenting the call to the function DrawThemeParentBackgroundEx but this breaks the transparency.
void DrawRadioControl(HWND hwnd, HTHEME hTheme, HDC dc, bool checked, RECT rcItem)
{
if (hTheme)
{
static const int cb_size = 13;
RECT bgRect, textRect;
HFONT font = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0);
WCHAR *text = L"Experiment";
DWORD state = ((checked) ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL) | ((bMouseOverButton) ? RBS_HOT : 0);
GetClientRect(hwnd, &bgRect);
GetThemeBackgroundContentRect(hTheme, dc, BP_RADIOBUTTON, state, &bgRect, &textRect);
DWORD dtFlags = DT_VCENTER | DT_SINGLELINE;
if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */
bgRect.top = bgRect.top + (textRect.bottom - textRect.top - cb_size) / 2;
/* adjust for the check/radio marker */
bgRect.bottom = bgRect.top + cb_size;
bgRect.right = bgRect.left + cb_size;
textRect.left = bgRect.right + 6;
//Uncommenting this line will fix the button corners but breaks transparency
//DrawThemeParentBackgroundEx(hwnd, dc, DTPB_USECTLCOLORSTATIC, NULL);
DrawThemeBackground(hTheme, dc, BP_RADIOBUTTON, state, &bgRect, NULL);
if (text)
{
DrawThemeText(hTheme, dc, BP_RADIOBUTTON, state, text, lstrlenW(text), dtFlags, 0, &textRect);
}
}
else
{
// Code for rendering the radio when themes are not present
}
}
The method above is called from WM_DRAWITEM as shown below:
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
hTheme = OpenThemeData(hDlg, L"BUTTON");
HDC dc = pDIS->hDC;
wchar_t sCaption[100];
GetWindowText(GetDlgItem(hDlg, pDIS->CtlID), sCaption, 100);
std::wstring staticText(sCaption);
DrawRadioControl(pDIS->hwndItem, hTheme, dc, radio_group.IsButtonChecked(pDIS->CtlID), pDIS->rcItem, staticText);
SetBkMode(dc, TRANSPARENT);
SetTextColor(hdcStatic, RGB(0,0,0));
return TRUE;
}
So my question is two parts I suppose:
Have I missed some other way to achieve my desired result?
Is it possible to fix the clipped button corners issue with my code and still have a transparent background
After looking at this on and off for nearly three months I've finally found a solution that I'm pleased with. What I eventually found was that the radio button edges were for some reason not being drawn by the routine within WM_DRAWITEM but that if I invalidated the radio button control's parent in a rectangle around the control, they appeared.
Since I could not find a single good example of this I'm providing the full code (in my own solution I have encapsulated my owner drawn controls into their own class, so you will need to provide some details such as whether the button is checked or not)
This is the creation of the radiobutton (adding it to the parent window) also setting GWL_UserData and subclassing the radiobutton:
HWND hWndControl = CreateWindow( _T("BUTTON"), caption, WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
xPos, yPos, width, height, parentHwnd, (HMENU) id, NULL, NULL);
// Using SetWindowLong and GWL_USERDATA I pass in the this reference, allowing my
// window proc toknow about the control state such as if it is selected
SetWindowLong( hWndControl, GWL_USERDATA, (LONG)this);
// And subclass the control - the WndProc is shown later
SetWindowSubclass(hWndControl, OwnerDrawControl::WndProc, 0, 0);
Since it is owner draw we need to handle the WM_DRAWITEM message in the parent window proc.
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
hTheme = OpenThemeData(hDlg, L"BUTTON");
HDC dc = pDIS->hDC;
wchar_t sCaption[100];
GetWindowText(GetDlgItem(hDlg, pDIS->CtlID), sCaption, 100);
std::wstring staticText(sCaption);
// Controller here passes to a class that holds a map of all controls
// which then passes on to the correct instance of my owner draw class
// which has the drawing code I show below
controller->DrawControl(pDIS->hwndItem, hTheme, dc, pDIS->rcItem,
staticText, pDIS->CtlID, pDIS->itemState, pDIS->itemAction);
SetBkMode(dc, TRANSPARENT);
SetTextColor(hdcStatic, RGB(0,0,0));
CloseThemeData(hTheme);
return TRUE;
}
Here is the DrawControl method - it has access to class level variables to allow state to be managed since with owner draw this is not handled automatically.
void OwnerDrawControl::DrawControl(HWND hwnd, HTHEME hTheme, HDC dc, bool checked, RECT rcItem, std::wstring caption, int ctrlId, UINT item_state, UINT item_action)
{
// Check if we need to draw themed data
if (hTheme)
{
HWND parent = GetParent(hwnd);
static const int cb_size = 13;
RECT bgRect, textRect;
HFONT font = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0);
DWORD state;
// This method handles both radio buttons and checkboxes - the enums here
// are part of my own code, not Windows enums.
// We also have hot tracking - this is shown in the window subclass later
if (Type() == RADIO_BUTTON)
state = ((checked) ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL) | ((is_hot_) ? RBS_HOT : 0);
else if (Type() == CHECK_BOX)
state = ((checked) ? CBS_CHECKEDNORMAL : CBS_UNCHECKEDNORMAL) | ((is_hot_) ? RBS_HOT : 0);
GetClientRect(hwnd, &bgRect);
// the theme type is either BP_RADIOBUTTON or BP_CHECKBOX where these are Windows enums
DWORD theme_type = ThemeType();
GetThemeBackgroundContentRect(hTheme, dc, theme_type, state, &bgRect, &textRect);
DWORD dtFlags = DT_VCENTER | DT_SINGLELINE;
if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */
bgRect.top = bgRect.top + (textRect.bottom - textRect.top - cb_size) / 2;
/* adjust for the check/radio marker */
// The +3 and +6 are a slight fudge to allow the focus rectangle to show correctly
bgRect.bottom = bgRect.top + cb_size;
bgRect.left += 3;
bgRect.right = bgRect.left + cb_size;
textRect.left = bgRect.right + 6;
DrawThemeBackground(hTheme, dc, theme_type, state, &bgRect, NULL);
DrawThemeText(hTheme, dc, theme_type, state, caption.c_str(), lstrlenW(caption.c_str()), dtFlags, 0, &textRect);
// Draw Focus Rectangle - I still don't really like this, it draw on the parent
// mainly to work around the way DrawFocus toggles the focus rect on and off.
// That coupled with some of my other drawing meant this was the only way I found
// to get a reliable focus effect.
BOOL bODAEntire = (item_action & ODA_DRAWENTIRE);
BOOL bIsFocused = (item_state & ODS_FOCUS);
BOOL bDrawFocusRect = !(item_state & ODS_NOFOCUSRECT);
if (bIsFocused && bDrawFocusRect)
{
if ((!bODAEntire))
{
HDC pdc = GetDC(parent);
RECT prc = GetMappedRectanglePos(hwnd, parent);
DrawFocus(pdc, prc);
}
}
}
// This handles drawing when we don't have themes
else
{
TEXTMETRIC tm;
GetTextMetrics(dc, &tm);
RECT rect = { rcItem.left ,
rcItem.top ,
rcItem.left + tm.tmHeight - 1,
rcItem.top + tm.tmHeight - 1};
DWORD state = ((checked) ? DFCS_CHECKED : 0 );
if (Type() == RADIO_BUTTON)
DrawFrameControl(dc, &rect, DFC_BUTTON, DFCS_BUTTONRADIO | state);
else if (Type() == CHECK_BOX)
DrawFrameControl(dc, &rect, DFC_BUTTON, DFCS_BUTTONCHECK | state);
RECT textRect = rcItem;
textRect.left = rcItem.left + 19;
SetTextColor(dc, ::GetSysColor(COLOR_BTNTEXT));
SetBkColor(dc, ::GetSysColor(COLOR_BTNFACE));
DrawText(dc, caption.c_str(), -1, &textRect, DT_WORDBREAK | DT_TOP);
}
}
Next is the window proc that is used to subclass the radio button control - this
is called with all windows messages and handles several before then passing unhandled
ones on to the default proc.
LRESULT OwnerDrawControl::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
// Get the button parent window
HWND parent = GetParent(hWnd);
// The page controller and the OwnerDrawControl hold some information we need to draw
// correctly, such as if the control is already set hot.
st_mini::IPageController * controller = GetWinLong<st_mini::IPageController *> (parent);
// Get the control
OwnerDrawControl *ctrl = (OwnerDrawControl*)GetWindowLong(hWnd, GWL_USERDATA);
switch (uMsg)
{
case WM_LBUTTONDOWN:
if (controller)
{
int ctrlId = GetDlgCtrlID(hWnd);
// OnCommand is where the logic for things like selecting a radiobutton
// and deselecting the rest of the group lives.
// We also call our Invalidate method there, which redraws the radio when
// it is selected. The Invalidate method will be shown last.
controller->OnCommand(parent, ctrlId, 0);
return (0);
}
break;
case WM_LBUTTONDBLCLK:
// We just treat doubleclicks as clicks
PostMessage(hWnd, WM_LBUTTONDOWN, wParam, lParam);
break;
case WM_MOUSEMOVE:
{
if (controller)
{
// This is our hot tracking allowing us to paint the control
// correctly when the mouse is over it - it sets flags that get
// used by the above DrawControl method
if(!ctrl->IsHot())
{
ctrl->SetHot(true);
// We invalidate to repaint
ctrl->InvalidateControl();
// Track the mouse event - without this the mouse leave message is not sent
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = hWnd;
TrackMouseEvent(&tme);
}
}
return (0);
}
break;
case WM_MOUSELEAVE:
{
if (controller)
{
// Turn off the hot display on the radio
if(ctrl->IsHot())
{
ctrl->SetHot(false);
ctrl->InvalidateControl();
}
}
return (0);
}
case WM_SETFOCUS:
{
ctrl->InvalidateControl();
}
case WM_KILLFOCUS:
{
RECT rcItem;
GetClientRect(hWnd, &rcItem);
HDC dc = GetDC(parent);
RECT prc = GetMappedRectanglePos(hWnd, parent);
DrawFocus(dc, prc);
return (0);
}
case WM_ERASEBKGND:
return 1;
}
// Any messages we don't process must be passed onto the original window function
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
Finally the last little piece of the puzzle is that you need to invalidate the control (redraw it) at the right times. I eventually found that invalidating the parent allowed the drawing to work 100% correctly. This was causing flicker until I realised that I could get away by only invalidating a rectangle as big as the radio check, rather than as big as the whole control including text as I had been.
void InvalidateControl()
{
// GetMappedRectanglePos is my own helper that uses MapWindowPoints
// to take a child control and map it to its parent
RECT rc = GetMappedRectanglePos(ctrl_, parent_);
// This was my first go, that caused flicker
// InvalidateRect(parent_, &rc_, FALSE);
// Now I invalidate a smaller rectangle
rc.right = rc.left + 13;
InvalidateRect(parent_, &rc, FALSE);
}
A lot of code and effort for something that should be simple - drawing a themed radio button over a background image. Hopefully the answer will save someone else some pain!
* One big caveat with this is it only works 100% correctly for owner controls that are over a background (such as a fill rectangle or an image). That is ok though, since it is only needed when drawing the radio control over a background.
I've done this some time ago as well. I remember the key was to just create the (radio) buttons as usual. The parent must be the dialog or window, not a tab control. You could do it differently but I created a memory dc (m_mdc) for the dialog and painted the background on that. Then add the OnCtlColorStatic and OnCtlColorBtn for your dialog:
virtual HBRUSH OnCtlColorStatic(HDC hDC, HWND hWnd)
{
RECT rc;
GetRelativeClientRect(hWnd, m_hWnd, &rc);
BitBlt(hDC, 0, 0, rc.right - rc.left, rc.bottom - rc.top, m_mdc, rc.left, rc.top, SRCCOPY);
SetBkColor(hDC, GetSysColor(COLOR_BTNFACE));
if (IsAppThemed())
SetBkMode(hDC, TRANSPARENT);
return (HBRUSH)GetStockObject(NULL_BRUSH);
}
virtual HBRUSH OnCtlColorBtn(HDC hDC, HWND hWnd)
{
return OnCtlColorStatic(hDC, hWnd);
}
The code uses some in-house classes and functions similar to MFC, but I think you should get the idea. As you can see it draws the background of these controls from the memory dc, that's key.
Give this a try and see if it works!
EDIT: If you add a tab control to the dialog and put the controls on the tab (that was the case in my app) you must capture it's background and copy it to the memory dc of the dialog. It's a bit of an ugly hack but it works, even if the machine is running some extravagant theme that uses a gradient tab background:
// calculate tab dispay area
RECT rc;
GetClientRect(m_tabControl, &rc);
m_tabControl.AdjustRect(false, &rc);
RECT rc2;
GetRelativeClientRect(m_tabControl, m_hWnd, &rc2);
rc.left += rc2.left;
rc.right += rc2.left;
rc.top += rc2.top;
rc.bottom += rc2.top;
// copy that area to background
HRGN hRgn = CreateRectRgnIndirect(&rc);
GetRelativeClientRect(m_hWnd, m_tabControl, &rc);
SetWindowOrgEx(m_mdc, rc.left, rc.top, NULL);
SelectClipRgn(m_mdc, hRgn);
SendMessage(m_tabControl, WM_PRINTCLIENT, (WPARAM)(HDC)m_mdc, PRF_CLIENT);
SelectClipRgn(m_mdc, NULL);
SetWindowOrgEx(m_mdc, 0, 0, NULL);
DeleteObject(hRgn);
Another interesting point, while we're busy now, to get it all non-flickering create the parent and children (buttons, statics, tabs etc) with the WS_CLIPCHILDREN and WS_CLIPSIBLINGS style. The the order of creation is essential: First create the controls you put on the tabs, then create the tab control. Not the other way around (although it feels more intuitive). That because the tab control should clip the area obscured by the controls on it :)
I can't immediately try this out, but so far as I recall, you don't need owner draw. You need to do this:
Return 1 from WM_ERASEBKGND.
Call DrawThemeParentBackground from WM_CTLCOLORSTATIC to draw the background there.
Return GetStockObject(NULL_BRUSH) from WM_CTLCOLORSTATIC.
Knowing the sizes and coordinates radio button, we will copy the
image to them closed.
Then we create a brush by means of
BS_PATTERN style CreateBrushIndirect
Farther according to the
usual scheme - we return handle to this brush in reply to COLOR -
the message (WM_CTLCOLORSTATIC).
I have no idea why you are doing it so difficult, this is best solved via CustomDrawing
This is my MFC Handler to draw a Notebook on a CTabCtrl control. I'm not really sure why i need to Inflate the Rectangle, because if i don't do it a black border is drawn.
And another conceptional bug MS made is IMHO that i have to overwrite the PreErase drawing phase instead of the PostErase. But if i do the later the checkbox is gone.
afx_msg void AguiRadioButton::OnCustomDraw(NMHDR* notify, LRESULT* res) {
NMCUSTOMDRAW* cd = (NMCUSTOMDRAW*)notify;
if (cd->dwDrawStage == CDDS_PREERASE) {
HTHEME theme = OpenThemeData(m_hWnd, L"Button");
CRect r = cd->rc; r.InflateRect(1,1,1,1);
DrawThemeBackground(theme, cd->hdc, TABP_BODY, 0, &r,NULL);
CloseThemeData(theme);
*res = 0;
}
*res = 0;
}
Related
So I'm using the code written here to redraw a static text but I've noticed that everytime I do so the system take it to the back(behind a static picture) and I can't see it. Is there a way to put it back in position or to prevent this action?
The code I use to redraw it:
vHWND = Control's HWND
wHWND = Window's HWND
RECT rect;
GetClientRect(vHWND, &rect);
InvalidateRect(vHWND, &rect, TRUE);
MapWindowPoints(vHWND, wHWND, (POINT *)&rect, 2);
RedrawWindow(wHWND, &rect, NULL, RDW_ERASE | RDW_INVALIDATE);
The code to have transparent bg:
case WM_CTLCOLORSTATIC: //Draw views transparent background
{
SetBkMode((HDC)wParam, TRANSPARENT); //BG Transp
return (LRESULT)GetStockObject(HOLLOW_BRUSH);
break;
}
Nevermind, I solved it using SetWindowPos.
I am trying to make a search edit control in MFC that has an icon displayed in the control window all the time (regardless the state and text of the control). I have written something like this many years ago and worked very well, but the code no longer works on Windows 7 and newer (maybe even Vista, but did not try that). What happens is that the image shown in the control is overlapped with the input area (see the picture below).
The idea behind the code:
have a class derived from CEdit (that handles painting in OnPaint)
the icon is displayed on the right and the edit area is shrunk based on the size of the icon
resizing is done differently for single-line and multiline edits. For single line I call SetMargins and for multiline edits I call SetRect.
this edit resizing is applied in PreSubclassWindow(), OnSize() and OnSetFont()
This is how the edit input size is applied:
void CSymbolEdit::RecalcLayout()
{
int width = GetSystemMetrics( SM_CXSMICON );
if(m_hSymbolIcon)
{
if (GetStyle() & ES_MULTILINE)
{
CRect editRect;
GetRect(&editRect);
editRect.right -= (width + 6);
SetRect(&editRect);
}
else
{
DWORD dwMargins = GetMargins();
SetMargins(LOWORD(dwMargins), width + 6);
}
}
}
The following image shows the problem with the single line edits (the images have been zoomed in for a better view). The yellow background is for highlighting purposes only, in real code I am using the COLOR_WINDOW system color. You can see that when the single line edit has text and has the input the left side image is painted over. This does not happen with the multiline edit where SetRect correctly sets the formatting rectangle.
I have tried using ExcludeClipRect to remove the area of the edit where the image is being displayed.
CRect rc;
GetClientRect(rc);
CPaintDC dc(this);
ExcludeClipRect(dc.m_hDC, rc.right - width - 6, rc.top, rc.right, rc.bottom);
DWORD dwMargins = GetMargins();
SetMargins(LOWORD(dwMargins), width + 6);
This does not seem to have any effect on the result.
For reference, this is the painting method, written years ago and used to work well on Windows XP, but not correct any more.
void CSymbolEdit::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect( &rect );
// Clearing the background
dc.FillSolidRect( rect, GetSysColor(COLOR_WINDOW) );
DWORD dwMargins = GetMargins();
if( m_hSymbolIcon )
{
// Drawing the icon
int width = GetSystemMetrics( SM_CXSMICON );
int height = GetSystemMetrics( SM_CYSMICON );
::DrawIconEx(
dc.m_hDC,
rect.right - width - 1,
1,
m_hSymbolIcon,
width,
height,
0,
NULL,
DI_NORMAL);
rect.left += LOWORD(dwMargins) + 1;
rect.right -= (width + 7);
}
else
{
rect.left += (LOWORD(dwMargins) + 1);
rect.right -= (HIWORD(dwMargins) + 1);
}
CString text;
GetWindowText(text);
CFont* oldFont = NULL;
rect.top += 1;
if(text.GetLength() == 0)
{
if(this != GetFocus() && m_strPromptText.GetLength() > 0)
{
oldFont = dc.SelectObject(&m_fontPrompt);
COLORREF color = dc.GetTextColor();
dc.SetTextColor(m_colorPromptText);
dc.DrawText(m_strPromptText, rect, DT_LEFT|DT_SINGLELINE|DT_EDITCONTROL);
dc.SetTextColor(color);
dc.SelectObject(oldFont);
}
}
else
{
if(GetStyle() & ES_MULTILINE)
CEdit::OnPaint();
else
{
oldFont = dc.SelectObject(GetFont());
dc.DrawText(text, rect, DT_SINGLELINE | DT_INTERNAL | DT_EDITCONTROL);
dc.SelectObject(oldFont);
}
}
}
I have looked at other implementations of similar edit controls and they all have the same fault now.
Obviously, the question is how do I exclude the image area from the input area of the control?
I think what's going on is that CPaintDC calls BeginPaint(), which sends a WM_ERASEBKGND to the edit box. I wasn't able to ignore it, so I guess it's being sent to maybe an internal STATIC window? Not sure.
Calling ExcludeClipRect() in your OnPaint() handler won't do anything because EDIT will reset the clipping region to the whole client area in either BeginPaint() or its own WM_PAINT handler.
However, EDIT sends a WM_CTRCOLOREDIT to its parent just before painting itself, but seemingly after setting the clipping region. So you can call ExcludeClipRect() there. Sounds like an implementation detail which may change with future versions of the common controls. Indeed, it seems to have done so already.
I did a quick test without MFC on Windows 7, here's my window procedure:
LRESULT CALLBACK wnd_proc(HWND h, UINT m, WPARAM wp, LPARAM lp)
{
switch (m)
{
case WM_CTLCOLOREDIT:
{
const auto dc = (HDC)wp;
const auto hwnd = (HWND)lp;
RECT r;
GetClientRect(hwnd, &r);
// excluding the margin, but not the border; this assumes
// a one pixel wide border
r.left = r.right - some_margin;
--r.right;
++r.top;
--r.bottom;
ExcludeClipRect(dc, r.left, r.top, r.right, r.bottom);
return (LRESULT)GetStockObject(DC_BRUSH);
}
}
return ::DefWindowProc(h, m, wp, lp);
}
I then subclassed the EDIT window to draw my own icon in WM_PAINT, and then forwarded the message so I didn't have to draw everything else myself.
LRESULT CALLBACK edit_wnd_proc(
HWND h, UINT m, WPARAM wp, LPARAM lp,
UINT_PTR id, DWORD_PTR data)
{
switch (m)
{
case WM_PAINT:
{
const auto dc = GetDC(h);
// draw an icon
ReleaseDC(h, dc);
break;
}
}
return DefSubclassProc(h, m, wp, lp);
}
Note that I couldn't call BeginPaint() and EndPaint() (the equivalent of constructing a CPaintDC) in WM_PAINT because the border wouldn't get drawn. I'm guessing it has something to do with calling BeginPaint() twice (once manually, once by EDIT) and the handling of WM_ERASEBKGND. YMMV, especially with MFC.
Finally, I set the margins right after creating the EDIT:
SendMessage(
e, EM_SETMARGINS,
EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELPARAM(0, margin));
You might also have to update the margins again if the system font changes.
Have a look at this tutorial... from www.catch22.net. It gives a clear picture of how to insert a button into edit control. Though it is an Win32 example, this can be improvised to MFC as the basic structure of MFC is using win32 apis.
http://www.catch22.net/tuts/win32/2001-05-20-insert-buttons-into-an-edit-control/#
It uses WM_NCCALCSIZE to restrict the text control.
I have window with gradient background. Combobox have own background brush.
How can I remove white corner in combobox? How can I change brush or another way.
On picture white corner marked by red frame.
I create combobox as:
DWORD dwStyle = WS_CHILD | CBS_DROPDOWNLIST;
if (m_bVisible) dwStyle |= WS_VISIBLE;
m_hWnd = CreateWindow(WC_COMBOBOX, NULL, dwStyle,
m_posX, m_posY, m_width, m_height, m_hParent, (HMENU)m_id, m_hInstance, NULL);
I tried changing background brush with message WM_CTLCOLOREDIT, but no effect:
case WM_CTLCOLOREDIT:
if ((HWND)lParam == m_hSrcListBox)
{
return (LRESULT)m_hBrush;
}
break;
=== SOLVED. WORK VERSION ===
First way.
In parent WndProc:
case WM_CTLCOLORSTATIC:
if ((HWND)lParam == m_hSrcListBox)
{
return (LRESULT)m_pSrcListBox->GetHbrush();
}
break;
In my class:
//
// CListBox::GetHbrush().
//
// Get brush.
//
HBRUSH CListBox::GetHbrush()
{
if (!m_hBrush)
{
m_hBrush = CreateTransparentBackgroundBrush(m_hParent, m_hWnd);
}
return m_hBrush;
}
Create transparent background:
//
// CListBox::CreateTransparentBackgroundBrush().
//
// Create transparent background for element.
//
HBRUSH CListBox::CreateTransparentBackgroundBrush(HWND parent, HWND client)
{
RECT rct;
POINT p1;
POINT p2;
GetWindowRect(client, &rct);
p1.x = rct.left;
p1.y = rct.top;
ScreenToClient(parent, &p1);
p2.x = rct.right;
p2.y = rct.bottom;
ScreenToClient(parent, &p2);
HDC hdcParent = GetDC(parent);
HDC hdcClient = GetDC(client);
HDC hdcmem = CreateCompatibleDC(hdcClient);
HBITMAP hbitmap = CreateCompatibleBitmap(hdcClient, p2.x - p1.x, p2.y - p1.y);
SelectObject(hdcmem, hbitmap);
BitBlt(hdcmem, 0, 0, p2.x - p1.x, p2.y - p1.y, hdcParent, p1.x, p1.y, SRCCOPY);
HBRUSH pattern = CreatePatternBrush(hbitmap);
DeleteDC(hdcmem);
DeleteObject(hbitmap);
ReleaseDC(client, hdcClient);
ReleaseDC(parent, hdcParent);
return pattern;
}
Second way.
In parent WndProc draw background in WM_ERASEBKGND message, then the corners will not.
case WM_ERASEBKGND:
m_hdc = (HDC)wParam;
// draw background.
return TRUE;
break;
The result of both methods:
For dialog boxes, handle WM_CTLCOLORDLG and return a background brush for the combobox
If you are displaying this combobox in a dialog, the trick is actually to handle the WM_CTLCOLORDLG message in your dialog's window procedure. In response to this message, you return a handle to a brush that the dialog box will use to paint its background.
case WM_CTLCOLORDLG:
{
// NOTE: This code is wrong because it creates a new brush object each time it processes
// the message, which it promptly leaks. It is merely for demonstration purposes.
// Normally, you would create the brush once, in response to WM_INITDIALOG,
// cache it away, and return that same cached handle each time, finally destroying
// the brush in response to WM_NCDESTROY.
HBRUSH hBrush = CreateSolidBrush(RGB(255, 120, 0));
return reinterpret_cast<INT_PTR>(hBrush);
}
This is the standard, documented way of changing the background color of a dialog box, and it also solves the problem with the combobox. Apparently, for whatever reason, combobox controls also use this brush to paint their background. I suppose they send a WM_CTLCOLORDLG message to their parent when they are painting themselves.
Of course, this limits you to the graphics capabilities of a GDI brush. You can draw any system or solid color that you want, or even use a hatch or pattern/bitmap brush, but there is no simple way of creating a gradient brush. (GDI+ has one, but not GDI.) Normally it wouldn't matter—you'd just call the GradientFill function in your WM_PAINT (or even WM_ERASEBKGND) message handler. That works fine for the dialog's background, but the combobox still draws its background with the brush returned by WM_CTLCOLORDLG, so it still has those 4 dots on its corners drawn in COLOR_3DFACE (which is the brush that the default dialog procedure returns).
Returning a null brush (NULL_BRUSH/HOLLOW_BRUSH) from WM_CTLCOLORDLG doesn't work, either. It changes the appearance slightly, such that the upper-right and lower-left corner pixels are now filled with something that looks like COLOR_3DSKSHADOW, but they are still visibly filled with a color other than the actual background gradient.
So if you really want it to look nice, you are left with only a single option: returning a handle to a GDI brush. And of course, it needs to be the same brush as is used to draw the dialog's background.
If you want a gradient fill, the only solution I can think of is using a pattern/bitmap brush, where the bitmap (DDB or DIB) is your gradient. Not great, but at least the days of Windows 9x limiting us to 8×8 patterns are long gone. Maybe someone more inventive than me can use this information to think of a better workaround?
For other windows, handle WM_CTLCOLORSTATIC and return a background brush for the combobox
All of that for a dialog box. But what about if you are displaying the combobox in a standard window (i.e., something other than a dialog box)? The WM_CTLCOLORDLG message is never sent in this case.
Instead, the combobox sends a WM_CTLCOLORSTATIC message to its parent window, and then uses the brush handle returned in response to that message to paint its background.
This is weird, I know. I only stumbled across it by conducting empirical tests, and I'm not sure quite sure what the rationale was. If I had to guess, I'd say that the CBS_DROPDOWNLIST style makes the combobox non-editable (i.e., it's not a true combobox because there is no Edit control), so instead of WM_CTLCOLOREDIT, it uses WM_CTLCOLORSTATIC. A disabled Edit box sends WM_CTLCOLORSTATIC, too, and so does a disabled combobox with the "normal" CBS_SIMPLE and CBS_DROPDOWN styles.
Weirder still, this only happens when the Aero theme is enabled (Vista and 7). It doesn't happen on Windows 10, or with the Luna theme (Visual Styles under XP), or with the Classic theme. (I didn't test on Windows 8 or 8.1.) Not that it matters, I suppose, since all of those other themes draw a simple rectangular combobox, leaving no corner pixels for the background to show through.
Whatever the logic, the solution remains to handle the WM_CTLCOLORSTATIC message and return the brush you wish the combobox to use to paint its background.
The same considerations apply here as those discussed above for the dialog box. If your window uses a solid-color background or a system color, you are home-free. Simply return a handle to the same brush that you set as the window class's background brush. If you want to use a gradient, you'll need to figure out a way to represent that gradient in the form of a GDI brush.
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDR_APPLICATION));
wcex.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDR_APPLICATION_SMALL));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_3DDKSHADOW + 1); // background brush
wcex.lpszMenuName = NULL;
wcex.lpszClassName = TEXT("My Colored Window Class");
RegisterClassEx(&wcex);
case WM_CTLCOLORSTATIC:
{
// NOTE: No leak here because we're using a system brush in this example.
return reinterpret_cast<LRESULT>(GetSysColorBrush(COLOR_3DDKSHADOW)); // background brush
}
How can i remove white corner in combobox?
I don't know if there is a more official way to get rid of it, but one option would be to create a Region with rounded corners using CreateRoundRectRgn(), and then apply it to the ComboBox using SetWindowRgn(). That will mask off the corners.
I have my CListCtrlEx derived from CListCtrl. This list have style LVS_REPORT, LVS_OWNERDRAWFIXED and LVS_EX_GRIDLINES. I have added possibility to change font for this list. This works fine, but there is one bad thing - if I change font and before that I have not been scrolling list, then all list items redraws right, but if I have done scrolling before font changing, then list items redraws a little bit upper or lower than list grid horizontal lines, i. e. items text becomes overlapped by grid lines.
Here is how I changing list font:
LRESULT CListCtrlEx::OnSetFont(WPARAM wParam, LPARAM)
{
LRESULT res = Default();
CRect rc;
GetWindowRect(&rc);
WINDOWPOS wp;
wp.hwnd = m_hWnd;
wp.cx = rc.Width();
wp.cy = rc.Height();
wp.flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER;
SendMessage(WM_WINDOWPOSCHANGED, 0, (LPARAM)&wp);
return res;
}
void CListCtrlEx::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
HDC hDC = ::GetDC(NULL);
CFont* pFont = GetFont();
HFONT hFontOld = (HFONT)SelectObject(hDC, pFont->GetSafeHandle());
CRect rect;
DrawText(hDC, _T(" "), 1, rect, DT_SINGLELINE | DT_CALCRECT);
lpMeasureItemStruct->itemHeight = rect.bottom - rect.top;
SelectObject(hDC, hFontOld);
::ReleaseDC(NULL, hDC);
}
UPD:
three people have clicked button UP and nobody knows what it can be? :(
UPD 1:
here's the class code
http://pastebin.com/UdXYEpF7 .h
http://pastebin.com/2HYe5AEd .cpp
I tried your code, it looks like ListView is exchanging messages with scroller, the header is also being resized, it's not really worth investigating. It's fine if you just set position to zero, you can save the old position and put it back.
void CListCtrlEx::SetupFont(int nSize, const CString& strName)
{
int saveIndex = GetTopIndex();
EnsureVisible(0, 0);
if (m_pFont.get()) m_pFont.get()->DeleteObject();
VERIFY(m_pFont.get()->CreatePointFont(nSize, strName));
SetFont(m_pFont.get());
//This scrolls to bottom, it ensures saveIndex will end up on top
//once the next EnsureVisible is called
if (GetItemCount())
EnsureVisible(GetItemCount() - 1, 1);
EnsureVisible(saveIndex, 1);
}
Here is how the program looks as it starts :
http://oi58.tinypic.com/2dwgs4h.jpg (can't upload images if I have less than 10 rep, sorry)
As soon as I hover with the cursor to change tab, every control which is not a child of the tabcontrol gets painted over in white as shown here :
http://oi61.tinypic.com/2yvuiwl.jpg
Here is how I created the TabControl in WM_CREATE:
RECT client_rect;
GetClientRect(hwnd, &client_rect);
TabControl = CreateWindowEx(WS_EX_COMPOSITED, WC_TABCONTROL, L"", WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN,
10, 10, client_rect.right - client_rect.left - 20,
client_rect.bottom - client_rect.top - 20, hwnd, (HMENU)110, GetModuleHandle(NULL), NULL);
TCITEM tab_info;
memset(&tab_info, 0, sizeof(tab_info));
tab_info.mask = TCIF_TEXT;
tab_info.pszText = L"Encoder";
tab_info.cchTextMax = 5;
SendMessage(TabControl, TCM_INSERTITEM, 0, (LPARAM)&tab_info);
tab_info.pszText = L"Decoder";
SendMessage(TabControl, TCM_INSERTITEM, 1, (LPARAM)&tab_info);
tab_info.pszText = L"Info Tab ";
SendMessage(TabControl, TCM_INSERTITEM, 2, (LPARAM)&tab_info);
RECT tab_rectangle;
GetClientRect(TabControl, &tab_rectangle);
SendMessage(TabControl, TCM_ADJUSTRECT, FALSE, (LPARAM)&tab_rectangle);
DefaultTabProc = (WNDPROC)SetWindowLongPtr(TabControl, GWL_WNDPROC, (LONG_PTR)TabProc);
Every other control is a child of the main window, except for the two buttons on the left which are defined as childs of the TabControl it self.
I've subclassed the TC to be able to paint that 50x50 px bitmap in bottom left and I'm handling the WM_PAINT of the subclassed wndproc this way :
case WM_PAINT:
{
if (!BitmapCall) //bool to decide whether I invalidated this rect to print the bitmap or not
CallWindowProc(DefaultTabProc, hwnd, uMsg, wParam, lParam);
if (EncodingTab) //we are in the Encoding tab, so print the bitmap
{
HDC hdc = GetDC(hwnd);
Graphics grap(hdc);
grap.DrawImage(&(*bitmap), 20, 248);
ReleaseDC(hwnd, hdc);
BitmapCall = false;
}
break;
}
Strangely, if I call Graphics grap(GetDC(hwnd)); the problem does not arise, and I'd take this route if it wasn't for the great memory leak i'm facing over few dozens of seconds after the application starts.
I tried to handle Tabcontrol's WM_ERASEBKGND and just return TRUE without luck. I could then make every control child of the TC but apparently groupboxes and radio buttons doesn't like the idea and gets painted all in black leaving me with even more troubles.
Painting the bitmap from the main frame window looks impossible since it gets overpainted by again said TC,
I may finally try to handle the order in which everything gets painted but I have no clue on where to start/how to do
Thanks in advance