Changing Dialog Box/CFormView Background Color |
|
Every once in a while there is a reason to change the background of a dialog or CFormView-derived window. This is not quite as trivial as it might appear, but it is actually only a couple extra lines of code to do the job right.
The first-cut solution would be to
create an OnEraseBkgnd handler in the dialog class, simply to paint
the entire dialog the desired color.BOOL CfvcolorView::OnEraseBkgnd(CDC* pDC) { CRect r; GetClientRect(&r); if((HBRUSH)brush == NULL) brush.CreateSolidBrush(MYCOLOR); pDC->FillRect(&r, &brush); return TRUE; } The result is a bit unfortunate using this simple approach. The dialog comes out looking like the dialog on the left. In this example, I also show, top to bottom, left to right
The Good News
The Bad News
|
|
We'd like to get the background of
those CStatic controls to look correct. We could subclass each
of the controls and have them do their own OnEraseBkgnd handlers, but
the idea is to look for a solution that does not require creating new
classes, or if for some reason these are already derived from other classes
(and assuming those classes do not themselves implement OnEraseBkgnd
handlers) the apparent solution is to create an OnCtlColor
handler which will return a background brush no matter which of the child controls
has generated a request.
The code would beHBRUSH CfvcolorView::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { if((HBRUSH)brush == NULL) brush.CreateSolidBrush(MYCOLOR); return (HBRUSH) brush; } This looks good in that it will now change the background color of the controls, but unfortunately, it isn't quite that simple, because the result of that code is shown to the left. Note that there are some changes. But while part of the static control (the part to the right) is now in the correct color, the text is still wrong. The area around the radio button and check box are the right color, but the text is still wrong (although note that some of the background of the static control, and the radio button and check box, has the right color, but the text is still wrong. And the background for the edit control, for the two edit controls in the combo boxes, for the list box and for the tree control are all wrong. Note that in some controls, part of the control (surrounding the text) is the correct color for the normal window background, white or dialog-box-color, but the rest of the control is the chosen MYCOLOR background. While this is an improvement for the static controls, it is definitely not an improvement for the other controls. The Good News
The Bad News
|
|
It is now obvious that it is the text itself that has the
wrong color, so the correct solution would be to SetBkColor to solve
this problem. The code would beHBRUSH CfvcolorView::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CFormView::OnCtlColor(pDC, pWnd, nCtlColor); pDC->SetBkColor(MYCOLOR); if((HBRUSH)brush == NULL) brush.CreateSolidBrush(MYCOLOR); return (HBRUSH) brush; } The Good News
The Bad News
|
|
Let's make edit controls a special exception.HBRUSH CfvcolorView::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CFormView::OnCtlColor(pDC, pWnd, nCtlColor); if(c_EnableCtlColor.GetCheck() != BST_CHECKED) return hbr; TCHAR classname[MAX_PATH]; if(::GetClassName(pWnd->m_hWnd, classname, MAX_PATH) == 0) return hbr; if(_tcsicmp(classname, _T("EDIT")) == 0) return hbr; pDC->SetBkColor(MYCOLOR); if((HBRUSH)brush == NULL) brush.CreateSolidBrush(MYCOLOR); return (HBRUSH) brush; } The Good News
The Bad News
|
|
But we haven't shown one other problem. If we look at the dropdown lists themselves, we find that the dropdown list backgrounds are still wrong.
but at least they are consistent with the simple dropdown presentation as well. But what is the type of this dropdown control? Well, a TRACE statement added after the ::GetClassName reveals that the type is COMBOLBOX. So we can add that as another case. HBRUSH CfvcolorView::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CFormView::OnCtlColor(pDC, pWnd, nCtlColor); TCHAR classname[MAX_PATH]; if(::GetClassName(pWnd->m_hWnd, classname, MAX_PATH) == 0) return hbr; if(_tcsicmp(classname, _T("EDIT")) == 0) return hbr; if(_tcsicmp(classname, _T("COMBOLBOX")) == 0) return hbr; pDC->SetBkColor(MYCOLOR); if((HBRUSH)brush == NULL) brush.CreateSolidBrush(MYCOLOR); return (HBRUSH) brush; } and indeed we have made progress. The Combo part of the Simple ComboBox on the left is now the right color, and as we see from these examples, the dropdown part of the DropDown and DropList controls is also the correct color.
The Good News
The Bad News
|
|
That's progress, but the ComboBox part of the DropList is
still wrong. But this can be solved by excluding COMBOBOX from
the background override.HBRUSH CfvcolorView::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CFormView::OnCtlColor(pDC, pWnd, nCtlColor); TCHAR classname[MAX_PATH]; if(::GetClassName(pWnd->m_hWnd, classname, MAX_PATH) == 0) return hbr; if(_tcsicmp(classname, _T("EDIT")) == 0) return hbr; if(_tcsicmp(classname, _T("COMBOBOX")) == 0) return hbr; if(_tcsicmp(classname, _T("COMBOLBOX")) == 0) return hbr; pDC->SetBkColor(MYCOLOR); if((HBRUSH)brush == NULL) brush.CreateSolidBrush(MYCOLOR); return (HBRUSH) brush; } The Good News
The Bad News
|
|
We can now tackle those last two elements. Rather than
keep the suspense up, we'll just add LISTBOX and... What's the name of a tree control? Well, we could use Spy++ to look it up, and type it in, but there is actually a symbol for it. We can look it up in commctrl.h, and find that it is WC_TREEVIEW. So the entire OnCtlColor handler is shown below. HBRUSH CfvcolorView::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CFormView::OnCtlColor(pDC, pWnd, nCtlColor); TCHAR classname[MAX_PATH]; if(::GetClassName(pWnd->m_hWnd, classname, MAX_PATH) == 0) return hbr; if(_tcsicmp(classname, _T("EDIT")) == 0) return hbr; if(_tcsicmp(classname, _T("COMBOBOX")) == 0) return hbr; if(_tcsicmp(classname, _T("COMBOLBOX")) == 0) . return hbr; if(_tcsicmp(classname, _T("LISTBOX")) == 0) return hbr; if(_tcsicmp(classname, WC_TREEVIEW) == 0) return hbr; pDC->SetBkColor(MYCOLOR); if((HBRUSH)brush == NULL) brush.CreateSolidBrush(MYCOLOR); return (HBRUSH) brush; } The Good News
The Bad News
|
There is no download because you can just highlight the body of the above function, copy it, and paste it into your own handler.
I use a member variable of the class, CBrush brush; to hold the brush. If the only code required here was to just paint the background in OnEraseBkgnd, I could have done this just with CDC::FillSolidRect which does not require that I have a brush. But since I am going to need the brush in the OnCtlColor handler, I chose to create the brush once.
I could have created the brush in the class constructor, but I chose to illustrate how to do it dynamically.
I do want to point out that I often rail against the use of OnCtlColor handlers to handle the background of controls. That is because if the background rules apply to one control, or even a small set of controls, then it is inappropriate to handle this in the parent, because the parent rarely, if ever, should know what color a control needs to paint itself. The approach of checking for control IDs in the OnCtlColor handler and taking different actions based on the control IDs is very poor modular programming. However, in this case, the rules are being applied to all controls (or all instances of a class of controls, without regard to their specific IDs), and is thus more robust because it does not do different things for individual selected controls based on their control IDs. Note that it does not look at control IDs at all.
The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.