Saliom
Saliom

Reputation: 149

Transparent Controls and a Flicker-Free MFC Dialog

Is it possible to have in a dialog-based MFC app both:

  1. Transparent static controls
  2. A flicker-free continuously changing background (edit: background might be complex with different colors/shapes at the same time)

So far I have only managed to achieve one at a time. For the static control transparency, I simply add a Static text control and override the dialog's OnCtlColor handler function to set the background mode to TRANSPARENT for static controls like the following:

// In the header
afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);

// In the source file
BEGIN_MESSAGE_MAP(CMyMFCDialog, CDialogEx)
    ON_WM_CTLCOLOR() // Add this handle
END_MESSAGE_MAP()

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

    if (nCtlColor == CTLCOLOR_STATIC)
    {
        SetBkMode(*pDC, TRANSPARENT);
        return (HBRUSH)GetStockObject(NULL_BRUSH);
    }

    return hbr;
}

It works like a charm, we can see it even more cleary by changing the Dialog's background color in the OnPaint handler function:

int r = 255, g = 0, b = 0;

void CMyMFCDialog::OnPaint()
{
    if (IsIconic())
    {
        // ... Unchanged code
    }
    else
    {
        CDialogEx::OnPaint();

        CDC* dc = this->GetDC();
        CRect cr;
        GetClientRect(&cr);
        dc->FillSolidRect(cr, RGB(r, g, b));
    }
}

Basic MFC Dialog with a transparent text and a red background

And for the continuously changing background, I simply change the RGB values of the precedently filled background very rapidly. In order to do that I will simply add a button control, set its ID to IDC_BUTTON1 and handle its OnBnClicked event to change the RGB values around every 1ms like so:

// In the header 
afx_msg void OnBnClickedButton1();

// In the source file
BEGIN_MESSAGE_MAP(CMyMFCDialog, CDialogEx)
    ON_BN_CLICKED(IDC_BUTTON1, &CMyMFCDialog::OnBnClickedButton1) // Add this handle
END_MESSAGE_MAP()

void CMyMFCDialog::OnBnClickedButton1()
{
    while (true)
    {
        if (r > 0 && b == 0) {
            r--; g++;
        }
        if (g > 0 && r == 0) {
            g--; b++;
        }
        if (b > 0 && g == 0) {
            r++; b--;
        }

        Invalidate();
        UpdateWindow();
        Sleep(1);
    }
}

Basic MFC Dialog with a transparent text and a background changing colors and flickering

And as one could expect, it does flicker like crazy. I tried multiple things (more details below) and what worked the best was to edit the .rc file to add WS_CLIPCHILDREN to the dialog's style flags:

IDD_MYMFCDIALOG_DIALOG DIALOGEX 0, 0, 320, 200
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,209,179,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,263,179,50,14
    CTEXT           "TODO: Place dialog controls here.",IDC_STATIC,10,96,300,8
    PUSHBUTTON      "Button1",IDC_BUTTON1,239,46,50,14
END

And override the OnEraseBkgnd handler function to return TRUE or FALSE:

// In the header 
afx_msg BOOL OnEraseBkgnd(CDC* pDC);

// In the source file
BEGIN_MESSAGE_MAP(CMyMFCDialog, CDialogEx)
    ON_WM_ERASEBKGND() // Add this handle
END_MESSAGE_MAP()

BOOL CMyMFCDialog::OnEraseBkgnd(CDC* pDC)
{
    return FALSE;
}

Basic MFC Dialog with a not-transparent text and a color changing background with no flickering

It successfully removed the flickering effect, but now our freshly made-transparent static text control has its white background back. I tried making a custom CStatic class to override its OnPaint and OnCtlColor in order to set its Background Mode to TRANSPARENT but to no avail, the white background is still there.

I also tried disabling WS_CLIPCHILDREN to implement double buffering using multiple methods, first, manually:

void CMyMFCDialog::OnPaint()
{
    if (IsIconic())
    {
        // ... Unchanged code
    }
    else
    {
        CDialogEx::OnPaint();

        CDC* dc = this->GetDC();
        CDC memDC;
        CBitmap bmpDC, *pOldBmp;

        memDC.CreateCompatibleDC(dc);
        CRect cr;
        GetClientRect(&cr);
        bmpDC.CreateCompatibleBitmap(dc, cr.Width(), cr.Height());
        pOldBmp = memDC.SelectObject(&bmpDC);

        memDC.FillSolidRect(cr, RGB(r, g, b));

        dc->BitBlt(cr.left, cr.top, cr.Width(), cr.Height(), &memDC, 0, 0, SRCCOPY);
        memDC.SelectObject(pOldBmp);
    }
}

It unfortunately still flickers, I've tried copying that double buffering code into custom CStatic classes OnPaint handle functions to see if it would work better but nothing changed pretty much.

I tried then to use some already implemented double buffering, first by using the CMemeDC header made by Keith Rule in 2002, and renaming both the file and class to MyCMemDC since there seems to be a conflict with an already existing CMemDC class:

// In pch.h
#include "mycmemdc.h"

// In the main source file
void CMyMFCDialog::OnPaint()
{
    if (IsIconic())
    {
        // ... Unchanged code
    }
    else
    {
        CDialogEx::OnPaint();

        CDC* dc = this->GetDC();
        MyCMemDC memDC(&dc);

        memDC.FillSolidRect(cr, RGB(r, g, b));
    }
}

Flicker is still happening, so I tried to dig in the official CMemDC class, and it seems to also be a double buffering implementation, so I tried to use it as well:

void CMyMFCDialog::OnPaint()
{
    if (IsIconic())
    {
        // ... Unchanged code
    }
    else
    {
        CDialogEx::OnPaint();

        CDC* dc = this->GetDC();
        CRect cr;
        GetClientRect(&cr);
        CMemDC memDC(*dc, &cr);
        CDC* pDC = &memDC.GetDC();

        pDC->FillSolidRect(cr, RGB(r, g, b));
    }
}

And the result was still flickery sadly.

I might have missed something or poorly implemented double buffering, the code above should allow for an easy reproduction of my problem, I tried to explain almost every step, even though some can be skipped by using the Class Wizard. Feel free to ask for any details I may have forgot to include.

Upvotes: 4

Views: 288

Answers (1)

selbie
selbie

Reputation: 104569

I think you want to have a message handler to WM_CTLCOLORSTATIC and return a brush of the exact same color as the background drawn by your paint function.

https://learn.microsoft.com/en-us/windows/win32/controls/wm-ctlcolorstatic

Upvotes: 1

Related Questions