Reputation: 149
Is it possible to have in a dialog-based MFC app both:
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));
}
}
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);
}
}
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;
}
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
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