I have a dialog box that should have a custom image as a background.
I do not have the whole image as a bitmap or any other format, so I have to draw it from scratch.
I use an icon
(the male in the left bottom of the screenshot) , one EMF
file (the map on the screen shot below), and the rest consists of a green gradient brush, light gray hatched brush, and text - all being drawn using GDI
. The result of my drawing looks like this (the screen shot includes the controls too):
The problem I face manifests when I move the dialog box to the left and then move it back to its original position. The artifacts happening are illustrated at the bottom of the picture in the next screenshot:
The dialog box is the modeless one, and I draw the entire image in WM_CTLCOLORDIALOG
returning NULL_BRUSH
afterwards.
Here is the relevant code snippet for the WM_CTLCOLORDIALOG
handler ( note that I draw directly on the DC, there is no double buffering. The reason for it is that this was a quick test code things went wrong quickly):
case WM_CTLCOLORDLG:
{
RECT rect; // dialog's client rectangle
GetClientRect( hwnd, &rect );
// ligh gray brush for hatched brush
HBRUSH hbPozadina = CreateSolidBrush( RGB( 242, 242, 242 ) );
FillRect( (HDC)wParam, &rect, hbPozadina );
// cleanup
DeleteObject( hbPozadina );
// draw grid "manualy"
LOGBRUSH lbPozadina;
HGDIOBJ hPenPozadina = NULL, hOldPenPozadina;
lbPozadina.lbColor = RGB( 255, 255, 255 );
lbPozadina.lbHatch = 0;
lbPozadina.lbStyle = BS_SOLID;
hPenPozadina = ExtCreatePen( PS_COSMETIC | PS_SOLID, 1, &lbPozadina, 0, NULL);
hOldPenPozadina = SelectObject((HDC)wParam, hPenPozadina);
// draw vertical lines
for( int i = rect.left + 12; i< rect.right; i += 12)
{
MoveToEx((HDC)wParam, i, rect.top, NULL );
LineTo((HDC)wParam, i, rect.bottom - rect.top + 1 );
}
// draw horizontal lines
for( int i = rect.top + 12; i< rect.bottom; i += 12)
{
MoveToEx((HDC)wParam, rect.left, i, NULL );
LineTo((HDC)wParam, rect.right - rect.left + 1, i );
}
//clean up
SelectObject((HDC)wParam, hOldPenPozadina);
DeleteObject(hPenPozadina);
// draw metafile of the map
HENHMETAFILE hemf = GetEnhMetaFile( L".\\resources\\KartaDlg.emf" );
ENHMETAHEADER emh;
GetEnhMetaFileHeader( hemf, sizeof(emh), &emh );
// remove the "status bar" from the calculation
RECT r;
r.top = rect.top;
r.bottom = rect.bottom - 30;
r.left = rect.left;
r.right = rect.right;
// calculate rescaled metafile
UINT o_height = emh.rclFrame.bottom - emh.rclFrame.top,
o_width = emh.rclFrame.right - emh.rclFrame.left;
float scale = 0.5;
scale = (float)( r.right - r.left ) / o_width;
if( (float)( r.bottom - r.top ) / o_height < scale )
scale = (float)( r.bottom - r.top ) / o_height;
int marginX = ( r.right - r.left ) - (int)( o_width * scale );
int marginY = ( r.bottom - r.top ) - (int)( o_height * scale );
marginX /= 2;
marginY /= 2;
r.left = r.left + marginX;
r.right = r.right - marginX;
r.top = r.top + marginY;
r.bottom = r.bottom - marginY;
// Draw the picture.
PlayEnhMetaFile( (HDC)wParam, hemf, &r );
// Release the metafile handle.
DeleteEnhMetaFile(hemf);
// this function draws green gradient and icon
drawFooter( (HDC)wParam, rect,
RGB( 0x48, 0xAC, 0xC6), RGB( 0x31, 0x83, 0x99 ) );
//========= draw right text in status bar =============//
SetBkMode( (HDC)wParam, TRANSPARENT );
SIZE sBaner; // needed for proper positioning
HFONT hf, hfOld;
long lfHeight;
lfHeight = -MulDiv( 8, GetDeviceCaps( (HDC)wParam, LOGPIXELSY), 72 );
hf = CreateFont( lfHeight, 0, 0, 0, FW_BOLD, TRUE,
0, 0, 0, 0, 0, 0, 0, L"Arial Black" );
hfOld = (HFONT)SelectObject( (HDC)wParam, hf ); // needed for proper cleanup
GetTextExtentPoint32( (HDC)wParam,
L"ЦЕНТАР ЗА ОБНОВЉИВЕ ВОДНЕ ЕНЕРГЕТСКЕ РЕСУРСЕ",
wcslen(L"ЦЕНТАР ЗА ОБНОВЉИВЕ ВОДНЕ ЕНЕРГЕТСКЕ РЕСУРСЕ"),
&sBaner );
// position it properly
r.bottom = rect.bottom;
r.right = rect.left + sBaner.cx + 30;
r.left = rect.left + 30;
r.top = rect.bottom - rect.top - 30;
// draw it
DrawTextEx( (HDC)wParam,
L"РУДАРСКО ГЕОЛОШКИ ФАКУЛТЕТ\nЦЕНТАР ЗА ОБНОВЉИВЕ ВОДНЕ ЕНЕРГЕТСКЕ РЕСУРСЕ",
wcslen(L"РУДАРСКО ГЕОЛОШКИ ФАКУЛТЕТ\nЦЕНТАР ЗА ОБНОВЉИВЕ ВОДНЕ ЕНЕРГЕТСКЕ РЕСУРСЕ"),
&r, DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_WORDBREAK, 0 );
SelectObject( (HDC)wParam, hfOld ); // proper cleanup
DeleteObject( hf );
//============== right text in the status bar ==================//
lfHeight = -MulDiv( 10, GetDeviceCaps( (HDC)wParam, LOGPIXELSY), 72 );
hf = CreateFont( lfHeight, 0, 0, 0, FW_BOLD, TRUE,
0, 0, 0, 0, 0, 0, 0, L"Arial" );
hfOld = (HFONT)SelectObject( (HDC)wParam, hf ); // needed for proper cleanup
GetTextExtentPoint32( (HDC)wParam,
L" Дејан Миленић & Ана Врањеш © 2013 сва права задржана",
wcslen(L" Дејан Миленић & Ана Врањеш © 2013 сва права задржана"),
&sBaner );
// position it properly
r.bottom = rect.bottom;
r.right = rect.right - 10;
r.left = rect.right - rect.left - sBaner.cx - 10;
r.top = rect.bottom - rect.top - sBaner.cy;
// draw it
DrawTextEx( (HDC)wParam,
L" Дејан Миленић & Ана Врањеш © 2013 сва права задржана",
wcslen(L" Дејан Миленић & Ана Врањеш © 2013 сва права задржана"),
&r, DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_WORDBREAK | DT_NOPREFIX, 0 );
// perform proper cleanup
SelectObject( (HDC)wParam, hfOld );
DeleteObject(hf);
}
return (INT_PTR)GetStockObject(NULL_BRUSH);
To make this even more complete, I submit the drawFooter
function and its helper functions:
// Fills triangle with gradient brush
void GradientTriangle( HDC MemDC,
LONG x1, LONG y1, LONG x2, LONG y2, LONG x3, LONG y3,
COLORREF top, COLORREF bottom )
{
TRIVERTEX vertex[3];
vertex[0].x = x1;
vertex[0].y = y1;
vertex[0].Red = GetRValue(bottom) << 8;
vertex[0].Green = GetGValue(bottom) << 8;
vertex[0].Blue = GetBValue(bottom) << 8;
vertex[0].Alpha = 0x0000;
vertex[1].x = x2;
vertex[1].y = y2;
vertex[1].Red = GetRValue(top) << 8;
vertex[1].Green = GetGValue(top) << 8;
vertex[1].Blue = GetBValue(top) << 8;
vertex[1].Alpha = 0x0000;
vertex[2].x = x3;
vertex[2].y = y3;
vertex[2].Red = GetRValue(bottom) << 8;
vertex[2].Green = GetGValue(bottom) << 8;
vertex[2].Blue = GetBValue(bottom) << 8;
vertex[2].Alpha = 0x0000;
// Create a GRADIENT_TRIANGLE structure that
// references the TRIVERTEX vertices.
GRADIENT_TRIANGLE gTriangle;
gTriangle.Vertex1 = 0;
gTriangle.Vertex2 = 1;
gTriangle.Vertex3 = 2;
// Draw a shaded triangle.
GradientFill( MemDC, vertex, 3, &gTriangle, 1, GRADIENT_FILL_TRIANGLE);
}
// draw the window's footer ( "status bar" )
void drawFooter( HDC MemDC, RECT r, COLORREF top, COLORREF bottom )
{
// bottom triangle
GradientTriangle( MemDC,
r.right, r.bottom,
( r.right - r.left ) / 2,
r.bottom - r.top - 15,
r.left,
r.bottom,
top, bottom );
// upper triangle
GradientTriangle( MemDC,
r.right, r.bottom - r.top - 30,
( r.right - r.left ) / 2,
r.bottom - r.top - 15,
r.left,
r.bottom - r.top - 30,
top, bottom );
// left triangle
GradientTriangle( MemDC,
r.left, r.bottom,
( r.right - r.left ) / 2,
r.bottom - r.top - 15,
r.left,
r.bottom - r.top - 30,
top, bottom );
// right triangle
GradientTriangle( MemDC,
r.right,
r.bottom - r.top - 30,
( r.right - r.left ) / 2,
r.bottom - r.top - 15,
r.right,
r.bottom,
top, bottom );
// draw icon
DrawIconEx( MemDC, r.left, r.bottom - r.top - 30,
hiAdmin, // a global variable for icon
30, 30, NULL, NULL, DI_NORMAL );
}
Visual Styles are enabled - this might matter, I do not know.
I did not handle WM_ERASEBKGND
nor WM_SIZE
or WM_MOVE
( dialog box can not be resized. ). I have tried but it did not help ( returning TRUE
for WM_ERASEBKGND
, and InvalidateRect
for WM_SIZE
and WM_MOVE
). I have found nothing on the internet to help me.
Question: How to change my code to fix the error I face?
You are abusing the WM_CTLCOLORDLG
message. It is intended to provide an easy way to change the background color of the dialog, not to custom paint it.
You should just return NULL_BRUSH
there, or even ignore the message altogether, and do your background painting in WM_ERASEBKGND
.
Or maybe even better, you can ignore WM_ERASEBKGND
and do your painting in WM_PAINT
, just as any other window.
UPDATE: After a few comments below the problem seems to be the order of the vertices in the call to GradientFill()
triangles. That is:
Or maybe is the other way around, I never can tell...
Anyway, there is still the mystery of why sometimes it works no matter the order and why sometimes it only works with a specific order. And moreover, is this documented anywhere?
I'm guessing that it may be a driver/2D-acceleration issue... so it will depend on whether the DC is on display or on memory, but it is difficult to say.