MFC CButton background ignored by Windows 7

5.5k views Asked by At

To change the appearance (background color and text foreground color) of a MFC checkbox and a radiobutton, I used the following implementation which worked fine in Windows2000, half OK in Windows XP, but not OK in Windows 7:

BEGIN_MESSAGE_MAP(mycheckbox, CButton)
  ...
  ON_WM_CTLCOLOR_REFLECT()
  ...
END_MESSAGE_MAP()

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

This works fine as long as the Windows Classic theme is used. However, when using a different theme:

  • Symptoms in Windows XP: SetBkColor works but SetTextColor is ignored
  • Symptoms in Windows 7: both SetBkColor and SetTextColor are ignored

I tried OnEraseBkgnd to fill the background with a custom color (pDC->FillSolidRect) but even this had no effect.

I want to avoid using ownerdrawn OnPaint so the check and radio marks keep following the theme that is active in Windows. As written before, this code is used in W2000, Windows Xp, Vista and Windows 7... I just want to change the background color and text color.

4

There are 4 answers

0
Jan Van Overbeke On BEST ANSWER

I have written a CButton that will use ownerdraw when theming is active in Windows (that is not the case when Windows Classic is used), and will do so dynamically. This sample code is not complete but it demonstrates everything needed to get the results.

The difficult part is that you need to represent highlighted and pressed states, see the parameters for DrawCheckBox. I am ignoring them as they will not be entirely missed in my application.

IMPLEMENT_DYNAMIC(mycheckbox, CButton)

mycheckbox::mycheckbox()
  : mv_bIsChecked( false )
{
  m_brush.CreateSolidBrush( RGB( 0,0,255) );
}

mycheckbox::~mycheckbox()
{
}

BEGIN_MESSAGE_MAP(mycheckbox, CButton)
  ON_WM_CTLCOLOR_REFLECT()
  ON_WM_PAINT()
  ON_CONTROL_REFLECT(BN_CLICKED, &mycheckbox::OnBnClicked)
END_MESSAGE_MAP()

HBRUSH mycheckbox::CtlColor(CDC* pDC, UINT nCtlColor)
{
  pDC->SetBkColor( RGB( 255, 0, 0 ) );   // text background color
  pDC->SetTextColor( RGB( 0, 255, 0 ) ); // text foreground color
  return m_brush;                        // control background
}

void mycheckbox::DrawItem(LPDRAWITEMSTRUCT)
{
}

void mycheckbox::OnPaint()
{
  if( ( GetStyle() & BS_OWNERDRAW ) == BS_OWNERDRAW )
  {
    CPaintDC dc( this );

    RECT rect;
    GetClientRect( & rect );
    rect.right = rect.left + 20;
    CMFCVisualManager::GetInstance()->DrawCheckBox(
                & dc
              , rect
              , false                               // highlighted
              , mv_bIsChecked ? 1 : 0 // state
              , true                                // enabled
              , false                               // pressed
              );

    // draw text next to the checkbox if you like
  }
  else
    __super::OnPaint();
}

  // when BS_OWNERDAW is active,
  // GetCheck() is reporting a wrong value
  // so we have to do a little bookkeeping ourselves
void mycheckbox::OnBnClicked()
{
  mv_bIsChecked = ! mv_bIsChecked;
}

BOOL mycheckbox::PreCreateWindow( CREATESTRUCT & cs )
{
  CString lv_szValue;
  CSettingsStore lv_Registry( FALSE, TRUE );
  lv_Registry.Open( _T("Software\\Microsoft\\Windows\\CurrentVersion\\ThemeManager") );
  lv_Registry.Read( _T("ThemeActive"), lv_szValue );
  lv_Registry.Close();
  if( lv_szValue == _T("1") )
    cs.style |= BS_OWNERDRAW;

  return CButton::PreCreateWindow(cs);
}

I even tried runtime theme switching, however that gives undesired effect when switching from Windows 7 theme to Windows Classic (checkbox then looks like a regular button). So I am not using this but maybe it is interesting to share:

void mycheckbox::OnNMThemeChanged( NMHDR * pNMHDR, LRESULT * pResult )
{
  CString lv_szValue;
  CSettingsStore lv_Registry( FALSE, TRUE );
  lv_Registry.Open( _T("Software\\Microsoft\\Windows\\CurrentVersion\\ThemeManager") );
  lv_Registry.Read( _T("ThemeActive"), lv_szValue );
  lv_Registry.Close();

  ModifyStyle( BS_OWNERDRAW, 0 ); // turn off
  if( lv_szValue == _T("1") )
    ModifyStyle( 0, BS_OWNERDRAW ); // turn on

  // This feature requires Windows XP or greater.
  // The symbol _WIN32_WINNT must be >= 0x0501.
  // TODO: Add your control notification handler code here
  *pResult = 0;
}
2
Mark Ransom On

CButton doesn't do anything more than call the Windows default window proc for drawing the button. You can override the OnPaint code to do your own thing, but I can understand why you would want to avoid that - it's a lot of work to get the proper look under every different circumstance.

MFC provides another class CMFCButton that has an overrideable method OnFillBackground, see if that works for you.

0
Ragesh Chakkadath On

According to this Microsoft article:

To change the background color of a button control in Windows 3.0 and later, it is necessary to create an owner draw button.

If you want specific controls to appear differently, I think it is better to subclass it.

This article will be helpful to understand subclassing.

Here to change the background and text color of a button you will need to derive a new class from CButton say MyButton and override its DrawItem function to add the code for drawing that particular control in your way.

Note: you will need to set owner draw property (BS_OWNERDRAW) to those controls.

0
l33t On

Trust me, the best solution here is to have a CStatic with the text and a CButton containing only the check box. As of Vista, changing the background of checkboxes is a problem, to say the least.