سؤال

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):

enter image description here

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:

enter image description here

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:

  • The vertices are listed clockwise: the triangle is drawn.
  • The vertices are listed counter-clockwise: the triangle is not drawn.

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.

نصائح أخرى

You're handling the wrong message.

You shouldn't paint anything on WM_CTLCOLORDLG message. Just move your painting code to WM_ERASEBKGND handler.

OK. Your next problem is you're repainting the complete window. Note that the left portion of your window, that did needed updating, is painted OK. And the problem is the main (right) portion of the window, that did not actually needed to be updated after moving the window from the left.

While inside the WM_ERASEBKGND handler, you can call GetUpdateRect() to get the rectangle that needs updating.

If you don't want to change your code too much, you can at least call e.g. IntersectClipRect() API before drawing anything. This will clip all your painting to that rectangle.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top