4

I have a class that is derived from CComboBoxEx and I'm trying to change the background color. I was thinking that it would work like a ComboBox (using the SetBkColor function), but it doesn't change the background color.

Here's what I have tried :

    BEGIN_MESSAGE_MAP(CMyComboBoxEx, CComboBoxEx)   
       ON_WM_CTLCOLOR()
    END_MESSAGE_MAP()

     void CMyComboBoxEx::SetBkColor(COLORREF backgroundColor)
         {
            m_backgroundColor = backgroundColor;
            m_brBkgnd.DeleteObject();
            m_brBkgnd.CreateSolidBrush(backgroundColor);
         }    
     HBRUSH CMyComboBoxEx::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
         {
            HBRUSH brush = __super::OnCtlColor(pDC, pWnd, nCtlColor);
            pDC->SetBkColor(RGB(255,0,0));

            return brush;
         }

I've tried with OnEraseBkgnd() too and it didn't worked either.

Do I need to subclass a derived CComboBox class and set the background color in that class?

Thx.

Emile
  • 592
  • 10
  • 36
  • Do you have a `WM_CTL_COLOR()` entry in your message map? – sergiol Nov 07 '19 at 18:38
  • The `WM_CTLCOLOR` message is sent to the parent dialog box; but I think it isn't sent for the 'list box' part of a combo. What I have done to achieve this is to declare the combo as 'owner drawn' and then handle that. – Adrian Mole Nov 07 '19 at 18:39
  • @sergiol Yes, I'll edit my question, Thx :) – Emile Nov 07 '19 at 18:40
  • @Adrian-ReinstateMonica Ok, so you are handling the DrawItem() function ? – Emile Nov 07 '19 at 18:43
  • The inner combobox is returned by `CComboBoxEx::GetComboBoxCtrl` – sergiol Nov 07 '19 at 18:45
  • May be you need to implement your own class of inner combobox and after do the `SubclassWindow` – sergiol Nov 07 '19 at 18:47
  • Yes, I already have my own derived class of combobox, I'll try that ! – Emile Nov 07 '19 at 18:49
  • Emile - Yes, I add the `CBS_OWNERDRAWFIXED` style in the resource script and handle draw the combo *via* the `OnDrawItem()` member of the containing dog-box. – Adrian Mole Nov 07 '19 at 19:16

3 Answers3

1

The problem here is that WM_CTLCOLOR messages are sent to the parent window (dialog box, probably) of your combo control, not to the control itself; also, in the case of the drop-down 'list-box' part of the combo, this message is not sent (as the dialog doesn't need to draw it unless the control has been activated).

The way I have achieved what you want is by making the control owner-draw and then (manually) drawing each item in the list.

First, you need to add the CBS_OWNERDRAWFIXED style to your control in the .rc/.rc2 script; like this, for a typical combo:

COMBOBOX  IDC_IGONG, 224, 68, 52,120,
    CBS_DROPDOWNLIST | CBS_HASSTRINGS | CBS_OWNERDRAWFIXED | WS_VSCROLL | WS_TABSTOP

Then, you need to add ON_WM_DRAWITEM() to the message map for your dialog class, and override its OnDrawItem() member. Note that the message is sent once for each item in the drop-down list, when the list is made visible by user-action:

void MyDialog::OnDrawItem(int nIDCtl, DRAWITEMSTRUCT *pDIS)
{
    switch (pDIS->CtlType) { // You can switch on the ID if it's only one combo!
    case ODT_COMBOBOX:
        DrawDropDownBox(this, nIDCtl, pDIS);
        break;
    default:
        CDialogEx::OnDrawItem(nIDCtl, pDIS);
        break;
    }
}

The DrawDropDownBox() does all the hard work:


void MyDialog::DrawDropDownBox(CWnd *box, int nID, DRAWITEMSTRUCT *pDIS)
{
    CComboBox *pCBC = dynamic_cast<CMyComboBoxEx *>(box->GetDlgItem(nID));
    if (pCBC == nullptr) return; // Skip if we can't get handle to the control
    CDC *pDC = CDC::FromHandle(pDIS->hDC);
    wchar_t buffer[4096]; // Or just char if you ain't using Unicode
    if (pCBC->GetLBText(int(pDIS->itemID), buffer) == CB_ERR) return; // Maybe called during WM_DELETEITEM
    int dcSave = pDC->SaveDC(); // Save DC state for later restoration
    CPen pen(PS_SOLID, 0, ListColor); // ListColor is COLORREF for your desired b/g
    if (pDIS->itemState & ODS_DISABLED) {
        pDC->SelectStockObject(NULL_PEN);
        pDC->SelectObject(BackBrush); // A CBrush for disabled: defined/created elsewhere
        pDC->SetBkMode(TRANSPARENT);
    }
    else {
        pDC->SelectObject(&pen);
        pDC->SelectObject(ListBrush); // A CBrush that draws your desired b/g
        pDC->SetBkMode(OPAQUE);
    }
    CRect rc(pDIS->rcItem); pDC->Rectangle(&rc); // This draws the b/g
    if (pDIS->itemState & ODS_DISABLED) {
        pDC->SetTextColor(GetSysColor(COLOR_GRAYTEXT));
    }
    else if (pDIS->itemState & ODS_SELECTED) { // Use Windows defaults if selected...
        pDC->SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT));
        pDC->SetBkColor(GetSysColor(COLOR_HIGHLIGHT));
    }
    else {
        pDC->SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
        pDC->SetBkColor(ListColor); // Custom b/g color
    }
    unsigned format = DT_SINGLELINE | DT_VCENTER; // You desired text alignment
    pDC->DrawText(CString(buffer), rc, format);
    pDC->RestoreDC(dcSave); // Restore DC's saved state...
    pDC->Detach();          // ...then 'release it'
    return;
}

The code shown handles both disabled combos and selected items in the list; you could possibly skip some of these, if you want to simplify the operation.

Feel free to ask for further explanation and/or clarification.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • I can't code inside the dialog, can I do the same thing inside my class ? – Emile Nov 07 '19 at 19:54
  • @Emile Probably! You'll still have to add the `CBD_OWNERDRAWFIXED` or `CBS_OWNERDRAWVARIABLE` style in the resource script (or somewhere), and provide your own `void CMyComboBox::DrawItem(LPDRAWITEMSTRUCT pDIS)` class member. You should be able to use the `pDIS` in much the same way as the code I've shown; and the `pCBC` then just equates to `this` for your class object. – Adrian Mole Nov 07 '19 at 20:02
1

If it's all about just changing Bk color of the control, then you have to handle WM_CTLCOLOR message in control's parent window:

HBRUSH CMyDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

    if (pWnd->GetDlgCtrlID() == IDC_MY_CONTROL)
    {
        pDC->SetBkColor(RGB(0, 0, 0)); //Black color
        hbr = m_hbrBlack;              //Black brush.
    }

    return hbr;
}

Otherwise, you have to draw your control totally yourself in your derived class, with CBS_OWNERDRAWFIXED or CBS_OWNERDRAWVARIABLE style, which is way more complicated, but ofc possible.

Jovibor
  • 759
  • 2
  • 6
  • 16
  • I can't do it in the parent's dialog because I don't want to copy the code everytime I use a ComboBoxEx :/ – Emile Nov 15 '19 at 15:47
1

I'm surprised that all the answers you've got so far suggest either handling WM_CTLCOLOR in parent window or use one of OWNERDRAW styles.

Handling WM_CTLCOLOR in parent window means you'll need to duplicate that code in each parent window's class where you'll use such combobox. This is obviously a bad solution if you want to use combobox more than once.

Adding OWNERDRAW style may have impact on other existing controls that you'd like to subclass and you may need to handle additional problems. That's also far from a sinmple solution.

Fortunately, there's another way to solve it - use Message Reflection. And all you need to do is to add ON_WM_CTLCOLOR_REFLECT() entry to the message map and CtlColor handler.

In case of combobox control I'd do it like this:

MyComboBoxEx.h

class CMyComboBoxEx : public CComboBoxEx
{
public:
    CMyComboBoxEx();
    virtual ~CMyComboBoxEx();

protected:

    CBrush m_BkBrush;

    DECLARE_MESSAGE_MAP()
public:
    afx_msg HBRUSH CtlColor(CDC* pDC, UINT nCtlColor);
    afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
};

MyComboBoxEx.cpp

CMyComboBoxEx::CMyComboBoxEx()
{
    m_BkBrush.CreateSolidBrush(RGB(0, 255, 0)); 
}

CMyComboBoxEx::~CMyComboBoxEx()
{
}

BEGIN_MESSAGE_MAP(CMyComboBoxEx, CComboBoxEx)
    ON_WM_CTLCOLOR_REFLECT()
    ON_WM_CTLCOLOR()
END_MESSAGE_MAP()

HBRUSH CMyComboBoxEx::CtlColor(CDC* pDC, UINT nCtlColor)
{
    pDC->SetTextColor(RGB(255, 0, 0));
    pDC->SetBkColor(RGB(0, 255, 0));
    return m_BkBrush;
}

HBRUSH CMyComboBoxEx::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    return CtlColor(pDC, nCtlColor);
}

Here's how such combobox looks:

enter image description here

If you want to have custom color for borders and glyph then you need to handle WM_PAINT yourself.

Community
  • 1
  • 1
  • Hi, sorry for the delay, this seems to be the best solution, but when I try it, it doesn't work for some reason. My control is a CMyComboBoxEx and I've done the exact same thing as your code example. Am I missing something ? :S – Emile Nov 15 '19 at 15:46
  • @Emile I added working example (but haven't changed anything in `CtlColor` logic) – Sergey Shevchenko Nov 15 '19 at 16:26