c++winapigdiwm-paint

GradientFill API doesn't perform as it should


INTRODUCTION AND RELEVANT INFORMATION:

I want to create a static control with gradient background.

I want to do it the following way:

Create the gradient at the main window’s background, and then place transparent static control on top of that background.

To do that, I have created RECT variable in WM_PAINT handler that positions the gradient at the position where static control should be.

Also, I try to use double buffering to avoid flickering ( I have also handled WM_ERASEBKGND, removed flags CS_VREDRAW and CS_HREDRAW from window class ).

I have also handled WM_SIZE message to invalidate the window, and to reposition static control properly.

In my WM_CTLCOLORSTATIC handler, I have returned NULL_BRUSH.

I have made a demo application to illustrate this, via application wizard in Visual Studio.

I work on Windows XP, using pure Win32 API and C++.

Bellow I will submit the modified code for WM_PAINT, and snippets for other handlers listed above:

    case WM_CREATE:
         {
             // get rectangle dimensions of the main window

             RECT rec;
             GetClientRect( hWnd, &rec );

             /******* main window's static control ******/

             HWND StaticControl = CreateWindowEx( 0, L"Static", L"", 
                                     WS_VISIBLE | WS_CHILD | SS_NOTIFY,
                                     ( 3 * ( rec.right - rec.left ) / 4 - 340 ) / 3,
                                     120 + ( rec.bottom - rec.top - 450 ) / 3, 
                                     150, 150, hWnd, (HMENU)4000, hInst, 0);
         }
         return (LRESULT)0;

    case WM_SIZE:
        {
             RECT rec; // main window's client rectangle

             GetClientRect( hWnd, &rec ); 

             SetWindowPos( GetDlgItem( hWnd, 4000 ),
                           NULL,
                           ( 3  * ( rec.right - rec.left ) / 4 - 340 ) / 3, 
                           120 + ( rec.bottom - rec.top - 450 ) / 3, 150, 150,
                           SWP_NOZORDER );

            InvalidateRect( hWnd, NULL, FALSE); 
        }
        return (LRESULT)0;

    case WM_ERASEBKGND:
        return (LRESULT)1;

    case WM_CTLCOLORSTATIC:
        return (LRESULT)( (HBRUSH)GetStockObject(NULL_BRUSH) );

    case WM_PAINT:
        {
            hdc = BeginPaint(hWnd, &ps);
            // TODO: Add any drawing code here...

            RECT r; // rectangle for main window's client area

            GetClientRect( hWnd, &r);

            HDC MemDC = CreateCompatibleDC(hdc); // back buffer

            // compatible bitmap for MemDC

            HBITMAP bmp = CreateCompatibleBitmap( hdc,
                                                  r.right - r.left, 
                                                  r.bottom - r.top ),
                    oldBmp = (HBITMAP)SelectObject( MemDC, bmp ); // needed for cleanup

            /***** draw a reference header at the top of the window *******/

            // position it properly at the top

            RECT rect;

            rect.left = r.left;
            rect.top = r.top;
            rect.right = r.right;
            rect.bottom = 120;

            FillRect( MemDC, &rect, (HBRUSH)GetStockObject(LTGRAY_BRUSH) );

            /**** main window's gradient background *******/

            //============ down triangle =========//

            TRIVERTEX vertex[3];

            vertex[0].x     = r.right;
            vertex[0].y     = r.bottom - r.top;
            vertex[0].Red   = 0xDB00;
            vertex[0].Green = 0xE500;
            vertex[0].Blue  = 0xF100;
            vertex[0].Alpha = 0x0000;

            vertex[1].x     = r.left;
            vertex[1].y     = r.bottom - r.top;
            vertex[1].Red   = 0x9500;
            vertex[1].Green = 0xB300;
            vertex[1].Blue  = 0xD700;
            vertex[1].Alpha = 0x0000;

            vertex[2].x     = r.left;
            vertex[2].y     = r.top + 120; 
            vertex[2].Red   = 0xDB00;
            vertex[2].Green = 0xE500;
            vertex[2].Blue  = 0xF100;
            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);

            //=============== upper triangle =================//

            TRIVERTEX vertex1[3];

            vertex1[0].x     = r.right;
            vertex1[0].y     = r.bottom - r.top;
            vertex1[0].Red   = 0xDB00;
            vertex1[0].Green = 0xE500;
            vertex1[0].Blue  = 0xF100;
            vertex1[0].Alpha = 0x0000;

            vertex1[1].x     = r.right;
            vertex1[1].y     = r.top + 120;
            vertex1[1].Red   = 0x9500;
            vertex1[1].Green = 0xB300;
            vertex1[1].Blue  = 0xD700;
            vertex1[1].Alpha = 0x0000;

            vertex1[2].x     = r.left;
            vertex1[2].y     = r.top + 120; 
            vertex1[2].Red   = 0xDB00;
            vertex1[2].Green = 0xE500;
            vertex1[2].Blue  = 0xF100;
            vertex1[2].Alpha = 0x0000;

            // Draw a shaded triangle.

            GradientFill( MemDC, vertex1, 3, &gTriangle, 1, GRADIENT_FILL_TRIANGLE);

            //**** draw background of the static control ****//

            //position it properly 

            rect.left = ( 3  * ( r.right - r.left ) / 4 - 340 ) / 3;
            rect.top = 120 + ( r.bottom - r.top - 450 ) / 3;
            rect.right = 150 + ( 3  * ( r.right - r.left ) / 4 - 340 ) / 3;
            rect.bottom = 270 + ( r.bottom - r.top - 450 ) / 3; // this one fails!!!

            //FillRect( MemDC, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH) );

            // vertexes for static's gradient color

            //================= top rectangle =====================//

            TRIVERTEX vertexS[2], vertex1S[2] ;

            vertexS[0].x     = rect.left;
            vertexS[0].y     = rect.top;
            vertexS[0].Red   = 0x9500;
            vertexS[0].Green = 0xB300;
            vertexS[0].Blue  = 0xD700;
            vertexS[0].Alpha = 0x0000;

            vertexS[1].x     = rect.right;
            vertexS[1].y     = ( rect.bottom - rect.top ) / 2; 
            vertexS[1].Red   = 0x4F00;
            vertexS[1].Green = 0x8B00;
            vertexS[1].Blue  = 0xBD00;
            vertexS[1].Alpha = 0x0000;

            //================== bottom rectangle ====================//

            vertex1S[0].x     = rect.left;
            vertex1S[0].y     = ( rect.bottom - rect.top ) / 2; 
            vertex1S[0].Red   = 0x4F00;
            vertex1S[0].Green = 0x8B00;
            vertex1S[0].Blue  = 0xBD00;
            vertex1S[0].Alpha = 0x0000;

            vertex1S[1].x     = rect.right;
            vertex1S[1].y     = rect.bottom;
            vertex1S[1].Red   = 0x9500;
            vertex1S[1].Green = 0xB300;
            vertex1S[1].Blue  = 0xD700;
            vertex1S[1].Alpha = 0x0000;

            // Create a GRADIENT_RECT structure that 
            // references the TRIVERTEX vertices.

            GRADIENT_RECT gRect;

            gRect.UpperLeft  = 0;
            gRect.LowerRight = 1;

            // Draw a shaded rectangle. 

            GradientFill( MemDC, vertexS, 2, &gRect, 1, GRADIENT_FILL_RECT_V );
            GradientFill( MemDC, vertex1S, 2, &gRect, 1, GRADIENT_FILL_RECT_V );

            /****** draw back buffer on the screen DC *****************/

            BitBlt( hdc, 0, 0, r.right - r.left, r.bottom - r.top,
                    MemDC, 0, 0, SRCCOPY );

            /************** cleanup *******************/

            SelectObject( MemDC, oldBmp );

            DeleteObject(bmp); // compatible bitmap for MemDC

            DeleteDC(MemDC);

            EndPaint(hWnd, &ps);
        }
        return (LRESULT)0;

PROBLEM:

When I use GradientFill API in WM_PAINT handler, I get a bigger rectangle on the screen than it should be.

Bellow picture illustrates this result:

enter image description here

If I try to fill the same rectangle with some solid brush, everything works fine.

Bellow picture illustrates this result:

enter image description here

MY EFFORTS TO SOLVE PROBLEM:

I have placed breakpoints at the places where rectangle’s coordinates are set and haven't seen any problems.

Also, GradientFill returns TRUE, so it doesn't fail.

QUESTION:

How to fix this?

Thank you in advance.

Regards.


Solution

  • I think it's simply that your calculations for the vertices are wrong:

            vertexS[1].y     = ( rect.bottom - rect.top ) / 2; 
            /* .... */
            vertex1S[0].y     = ( rect.bottom - rect.top ) / 2;
    

    Should they be rect.top + ( rect.bottom - rect.top ) / 2 or something similar? Or even just rect.bottom and rect.top respectively?

    It depends what you're trying to achieve, but either way I think it's currently not what you want. You would need to use rect.bottom and rect.top to get the same shape that your commented-out FillRect draws.