Thursday, July 24, 2008

CStatic control loses transparency in Visual C++

Problem

In my current Visual C++ 2008 project, I’m attempting to display dynamic text on a custom control while maintaining a transparent background. For this purpose, I’ve subclassed a CStatic control, creating a derived class named StaticCtrl.

Initially, when the control is rendered, it correctly appears with a transparent background as intended. However, the issue arises when I update the control’s content—specifically, when I set new text. At that point, the control loses its transparency and reverts to the default opaque appearance typical of a standard CStatic control. This behavior undermines the visual consistency I’m aiming for.

To address this, I’ve overridden the OnPaint method in my StaticCtrl class. This is the only customization I’ve made—no other message handlers or style modifications are involved. My goal was to handle the painting manually to preserve transparency while updating the displayed content. Below is the implementation of the OnPaint method:

void StaticCtrl::OnPaint()
{
    CPaintDC dc(this); // device context for painting
   
    // Where to draw text
    CRect clientRect;
    GetClientRect(clientRect);
   
    // Get the caption
    CString strTitle;
    GetWindowText(strTitle);
   
    // Get the font
    CFont *pFont, *pOldFont;
    pFont = GetFont();
    pOldFont = dc.SelectObject(pFont);
   
    DWORD dwStyle = GetStyle(), dwText = 0;
   
    // Map "Static Styles" to "Text Styles"
    #define MAP_STYLE(src, dest)
        if(dwStyle & (src)) dwText |= (dest)
    #define NMAP_STYLE(src, dest)
        if(!(dwStyle & (src))) dwText |= (dest)
   
    MAP_STYLE(SS_RIGHT, DT_RIGHT);
    MAP_STYLE(SS_CENTER, DT_CENTER);
    MAP_STYLE(SS_CENTERIMAGE, DT_VCENTER | DT_SINGLELINE);
    MAP_STYLE(SS_NOPREFIX, DT_NOPREFIX);
    MAP_STYLE(SS_WORDELLIPSIS, DT_WORD_ELLIPSIS);
    MAP_STYLE(SS_ENDELLIPSIS, DT_END_ELLIPSIS);
    MAP_STYLE(SS_PATHELLIPSIS, DT_PATH_ELLIPSIS);
    NMAP_STYLE(SS_LEFTNOWORDWRAP | SS_CENTERIMAGE |
               SS_WORDELLIPSIS | SS_ENDELLIPSIS |
               SS_PATHELLIPSIS, DT_WORDBREAK );
   
    // Set transparent background
    dc.SetBkMode(TRANSPARENT);
   
    // Draw the text
    if(GetDlgCtrlID() == IDC_STATIC_CANCEL)
    dc.SetTextColor(RGB(255, 0, 0));
   
    if(GetDlgCtrlID() == IDC_CONNECTIONTXT ||
       GetDlgCtrlID() == IDC_RATETXT)
    dc.SetTextColor(RGB(250, 100, 0));
   
    if(GetDlgCtrlID() == IDC_PERCENT ||
       GetDlgCtrlID() == IDC_REMAINING ||
       GetDlgCtrlID() == IDC_TIMETXT)
       dc.SetTextColor(RGB(250, 100, 0));

    dc.DrawText(strTitle, clientRect, dwText);
   
    // Select old font
    dc.SelectObject(pOldFont);
}

Solution

When customizing the background color of a CDialog, you might notice that certain controls—especially CStatic text labels—continue to display their default background color, typically COLOR_BTNFACE. This mismatch can disrupt the visual consistency of your dialog, especially if you're aiming for a custom theme or aesthetic.

To resolve this, you can make the background of these controls transparent so they inherit the dialog’s background color. This is particularly useful for static text controls that should blend seamlessly with the rest of the interface.

In a subclassed dialog (e.g., CMyDialog derived from CDialog), you can override the OnCtlColor() message handler to apply transparency to all CStatic controls. Here’s how you can implement it:

HBRUSH CMyDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    CBrush m_brHollow = m_brHollow.CreateStockObject(HOLLOW_BRUSH);

    if (nCtlColor == CTLCOLOR_EDIT) {
        pDC->SetTextColor(RGB(0, 0, 0));  // text color
        pDC->SetBkMode(TRANSPARENT);      // background mode
        hbr = m_brHollow;                 // return NULL brush
    }
   
    return hbr;
}

When you customize your dialog background to white, the OnCtlColor() implementation I shared earlier works perfectly for CStatic controls. They correctly inherit the dialog’s background and appear transparent.

However, things behave differently with a CSliderCtrl. Using the same approach, you’ll notice that the slider’s background turns completely black instead of blending with the dialog’s white background. This happens because returning a NULL_BRUSH in OnCtlColor() doesn’t interact well with certain controls like sliders, which then default to a solid black fill.

A simple workaround is to return a handle to a white brush rather than a NULL_BRUSH. This ensures that the slider control background matches the dialog’s white background consistently. The following snippet demonstrates the fix, assuming CMyDialog is derived from CDialog and the dialog background has already been painted white by overriding CDialog::OnEraseBkgnd():

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

    // dialog background WHITE
    m_brBgColor.CreateSolidBrush(RGB(255, 255, 255));
   
    if (nCtlColor == CTLCOLOR_EDIT) {
        pDC->SetTextColor(RGB(0, 0, 0)); // text color
        pDC->SetBkMode(TRANSPARENT);     // background mode
        hbr = m_brBgColor;               // return WHITE brush
    }
   
    return hbr;
}

This adjustment ensures that both CStatic and CSliderCtrl controls render correctly against a white dialog background, maintaining a consistent and clean appearance across your UI. Refer this.



No comments:

Post a Comment