6

INTRODUCTION AND RELEVANT INFORMATION:

I have a complex painting to implement in my main window’s WM_PAINT handler.

I have submitted a picture bellow to illustrate it:

enter image description here

Main window has static controls, instead of buttons, which have style SS_NOTIFY.

When user clicks on them, certain actions occur in program, which are irrelevant for now.

The following picture shows where static controls in the main window are:

enter image description here

Map on the orange panel is an EMF file ,top left and right logos are PNG files, and other pictures are bitmaps.

Visual Styles are enabled via #pragma directive. I also use GDI+, along with GDI.

Project was created as empty project and I have coded everything from “scratch”.

In order to implement this task, I have decided to draw the entire picture in WM_PAINT, and to put transparent static controls over the images on the picture that correspond them.

In order to keep my code clean and simple, I have made functions that implement the above, so my WM_PAINT handler can be as small as possible.

UPDATE #1( updated on December 17th, 2013 ):

In order to implement the advice gained from member arx I am posting a single source code that can be compiled and that can reproduce the problem:

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <gdiplus.h>

#pragma comment( linker, "/manifestdependency:\"type='win32' \
                 name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
                 processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
                 language='*'\"" )

#pragma comment( lib, "comctl32.lib")
#pragma comment( lib, "Msimg32.lib" ) 
#pragma comment( lib, "Gdiplus.lib" )

using namespace Gdiplus;

// variable for storing the instance

static HINSTANCE hInst; 

// variables for painting the window

static HBRUSH hbPozadina, // gray background brush for grid on the top
   BlueFrame, // needed to frame blue static controls
       hbr; // orange brush for orange panel

/********* helper functions for WM_PAINT **********/

// 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);
 }

 // draws the background for the part of the window between header and footer

 void drawBackground( HDC MemDC, RECT r )
 {
      /******* main window's gradient background ********/

      GradientTriangle( MemDC, r.right, r.bottom - r.top - 30, 
                               r.left, r.bottom - r.top - 30,
                               r.left, r.top + 120,
                               RGB( 0x95, 0xB3, 0xD7 ), 
                               RGB( 0xDB, 0xE5, 0xF1 ) );

      GradientTriangle( MemDC, r.right, r.bottom - r.top - 30, 
                               r.right, r.top + 120,
                               r.left, r.top + 120, 
                               RGB( 0x95, 0xB3, 0xD7 ), 
                               RGB( 0xDB, 0xE5, 0xF1 ) );
}

// draws the header of the main window

void drawHeader( HDC MemDC, RECT rect, HBRUSH hbPozadina )
{
    FillRect( MemDC, &rect, hbPozadina );
}

// fills rectangle with gradient brush

void GradientRectangle( HDC MemDC, 
                        LONG x1, LONG y1,
                        LONG x2, LONG y2,
                        COLORREF top,
                        COLORREF bottom )
{
    // vertexes for static's gradient color

    TRIVERTEX vertexS[2];

    vertexS[0].x     = x1;
    vertexS[0].y     = y1;
    vertexS[0].Red   = GetRValue(top) << 8;
    vertexS[0].Green = GetGValue(top) << 8;
    vertexS[0].Blue  = GetBValue(top) << 8;
    vertexS[0].Alpha = 0x0000;

    vertexS[1].x     = x2;
    vertexS[1].y     = y2; 
    vertexS[1].Red   = GetRValue(bottom) << 8;
    vertexS[1].Green = GetGValue(bottom) << 8;
    vertexS[1].Blue  = GetBValue(bottom) << 8;
    vertexS[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 );
}

// fills the "button" with blue gradient and frames it with blue brush

void FillButton( HDC MemDC, RECT rect, HBRUSH BlueFrame )
{
    // fill upper half of the rectangle

    GradientRectangle( MemDC, 
                       rect.left, rect.top, 
                       rect.right, rect.top + ( rect.bottom - rect.top ) / 2,
                       RGB( 0x95, 0xB3, 0xD7 ), 
                       RGB( 0x4F, 0x8B, 0xBD ) );

    // fill bottom half of the rectangle

    GradientRectangle( MemDC, 
                       rect.left, rect.top + ( rect.bottom - rect.top ) / 2,
                       rect.right, rect.bottom, 
                       RGB( 0x4F, 0x8B, 0xBD ),
                       RGB( 0x95, 0xB3, 0xD7 ) );

    FrameRect( MemDC, &rect, BlueFrame );
}

// draws the "status bar" at the bottom of the main window

void drawFooter( HDC MemDC, RECT r, COLORREF top, COLORREF bottom )
{
    // down 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 orange panel on which map and 3 static controls will be drawn

void drawOrangePanel( HDC MemDC, RECT r, COLORREF top, COLORREF bottom )
{
    // down triangle

    GradientTriangle( MemDC, 
                      r.right, r.bottom, 
                      r.left + ( r.right - r.left ) / 2,
                      r.top + ( r.bottom - r.top ) / 2,
                      r.left, r.bottom, 
                      top, bottom );

    // upper triangle

    GradientTriangle( MemDC, 
                      r.right, r.top, 
                      r.left + ( r.right - r.left ) / 2, 
                      r.top + ( r.bottom - r.top ) / 2,
                      r.left, r.top, 
                      top, bottom );

    // left triangle

    GradientTriangle( MemDC, 
                      r.left, r.bottom, 
                      r.left + ( r.right - r.left ) / 2, 
                      r.top + ( r.bottom - r.top ) / 2,
                      r.left, r.top, 
                      top, bottom );

   // right triangle

   GradientTriangle( MemDC, 
                     r.right, r.top,
                     r.left + ( r.right - r.left ) / 2, 
                     r.top + ( r.bottom - r.top ) / 2, 
                     r.right, r.bottom, 
                     top, bottom );
}

void onPaint( HWND hwnd, WPARAM wParam, LPARAM lParam ) 
{
    PAINTSTRUCT ps;

    HDC hdc = BeginPaint( hwnd, &ps);

    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 background for middle part of the window

    drawBackground( MemDC, r );

    // draw header with grid lines

    RECT rect; // position it properly at the top

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

    drawHeader( MemDC, rect, hbPozadina );

    // draw "status bar"

    drawFooter( MemDC, r, RGB( 0x48, 0xAC, 0xC6), RGB( 0x31, 0x83, 0x99 ) );

    /******* draw static control's background ****/

    //======== top left 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;

    // draw gradient button

    FillButton( MemDC, rect, BlueFrame );

    //======================== draw orange panel =================//

    //position it properly

    rect.left = 3 * ( r.right - r.left ) / 4 - 40;
    rect.top = r.top + 140;
    rect.right = rect.left + ( r.right - r.left ) / 4;
    rect.bottom = rect.top + ( r.bottom - r.top - 190 );

    drawOrangePanel( MemDC, rect, RGB( 0xFF, 0xC8, 0xAA ), RGB( 0xFF, 0x96, 0x48 ) );

    /****** 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);
}

// WinMain's procedure

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
    case WM_CREATE:
        {
             //******** brushes ***********//

             // load gray background brush for the top banner

             hbPozadina = CreateSolidBrush( RGB( 230, 230, 230 ) );

             // brush for orange panel that holds 3 static controls and a map

             hbr = CreateSolidBrush( RGB( 255, 163, 94 ) ); 

             // blue frame for blue static controls

             BlueFrame = CreateSolidBrush( RGB(79, 129, 189) ); 

             /*******************************************/
        }
        return (LRESULT)0;

    case WM_ERASEBKGND:
        return (LRESULT)1; // so we avoid flicker ( all painting is in WM_PAINT )

    case WM_PAINT:
        {
             // paint the picture
             onPaint( hwnd, wParam, lParam );
        }
        return (LRESULT)0;

    case WM_SIZE:
        InvalidateRect( hwnd, NULL, FALSE ); 
        return (LRESULT)0;

    case WM_CLOSE:

        // destroy brushes

        DeleteObject(hbPozadina);
        DeleteObject(hbr);
        DeleteObject(BlueFrame);

        DestroyWindow(hwnd);

        return (LRESULT)0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return (LRESULT)0;

    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

// WinMain

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, 
               int nCmdShow)
{
    // store hInstance in global variable for later use

    hInst = hInstance;

    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    /**** variables for GDI+ initialization ******/

    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR           gdiplusToken;

    /********* Initialize GDI+. ********/

    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    /**** finished GDI+ initialisation *****/

    // initialize common controls

    INITCOMMONCONTROLSEX iccex;
    iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    iccex.dwICC = ICC_STANDARD_CLASSES ;
    InitCommonControlsEx(&iccex);

    // register main window class

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInst;
    wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = NULL;//(HBRUSH)GetStockObject( WHITE_BRUSH );
    wc.lpszMenuName = NULL;
    wc.lpszClassName = L"Main_Window";
    wc.hIconSm = LoadIcon (NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
         MessageBox( NULL, L"Window Registration Failed!", L"Error!", 
                     MB_ICONEXCLAMATION | MB_OK );

         return 0;
    }

    // create main window

    hwnd = CreateWindowEx( 0, // WS_EX_COMPOSITED "improved" drawing of the edges
                           L"Main_Window", 
                           L"Геотермист", 
                           WS_OVERLAPPEDWINDOW,
                           ( GetSystemMetrics(SM_CXMAXIMIZED) - 1020 ) / 2, 
                           ( GetSystemMetrics(SM_CYMAXIMIZED) - 600 ) / 2, 
                           1020, 600, NULL, NULL, hInstance, 0 );

    if(hwnd == NULL)
    {
          MessageBox( NULL, L"Window creation failed!", L"Error!", 
                      MB_ICONEXCLAMATION | MB_OK );

          return 0; 
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }

    // shutdownd GDI+

    GdiplusShutdown(gdiplusToken);

    return Msg.wParam;
}

I work on Windows XP, using MS Visual Studio C++ 2008 Express Edition and pure Win32 API.

One note: since Express edition of VS doesn't have resource editor, resource file and resource header were created using ResEdit from here: http://www.resedit.net/.

PROBLEM:

In order to avoid flickering, I have used double buffering, which I have learned from *Paul Watt’*s articles on CodeProject, Charles Petzold’s Programming Windows 5th edition and Forger’s WIN32 tutorial.

Theoretically speaking, everything is OK, and my code compiles without any errors.

I have pasted the code here: http://pastebin.com/zSYT1i8L

My English is not good enough to accurately describe the problems that I face ( all I can say is that edges of the main window and static control's redraw “slow” and they flicker ) so I have created a demo application that demonstrates them: http://www.filedropper.com/geotermistgrafika

MY EFFORTS TO SOLVE THE PROBLEM:

I have handled WM_ERASEBKGND ( returned (LRESULT)1 ), and I have excluded styles CS_VREDRAW and CS_HREDRAW from my window class-therefore flickering should not be caused because of this.

My window doesn’t have WS_CLIPCHILDREN style because part of the desktop picture is seen where static controls are.

In my WM_SIZE handler I have :

  1. Repositioned static controls using SetWindowPos(...) API and reduced flickering by adding the SWP_NOCOPYBITS flag.

  2. Invalidated the entire window with the InvalidateRect( hWnd, NULL, FALSE ), so this API does not send WM_ERASEBKGND when invalidating ( 3rd parameter is FALSE ), but even if I try with TRUE the effect is the same.

I have implemented double buffering for WM_PAINT handler like in the examples found in the above books/articles/tutorials ( by doing everything in the memory DC and do BitBlt(...) on screen DC in order to avoid flickering ).

I didn’t handle WM_SIZING, nor WM_WINDOWPOSCHANGING, or WM_MOVING message.

I have used the tool GDIView ( http://www.nirsoft.net/utils/gdi_handles.html ) to track down GDI leaks.

Each time I resize/maximize my window, GDIView shows +4 in column for regions, which should mean that I leak regions but I can’t figure out how is this possible, since I do not use API’s that manipulate with regions, and have double checked everything.

In my opinion everything should be fine and maybe this is irrelevant, but I just thought to mention it,maybe it is important.

If I add WS_EX_COMPOSITED style to the main window, the performance does not improve.

I have tried to find online example that would help me to solve my problem but all the tutorials are simple and do not cover this type of complex pictures.

IMPORTANT NOTE:

After leaving my WM_PAINT handler empty and calling onPaint function in WM_ERASEBKGND with the device context obtained with GetDC(..) API, the flickering disappeared, but during the resizing, redrawing of the window was “spiky” and the problem with the edges of the main window was not solved.

Still, this is much better improvement than the original code.

QUESTION:

How to get rid of the problems demonstrated in my demo application, provided above?

I hereby thank anyone who invests its time and efforts to try to help me.

Best regards.

AlwaysLearningNewStuff
  • 2,939
  • 3
  • 31
  • 84
  • You've certainly gotten a lot of mileage out of those sample pictures. – Mark Ransom Dec 17 '13 at 18:44
  • Resize just about any window on a Windows system and you'll see the edges flicker. Windows just isn't up to the level of performance I think you're expecting. – Jonathan Potter Dec 17 '13 at 18:50
  • @JonathanPotter The problem lies also in the edges of the static controls ( blue ones ) and the orange panel on which the map and strings lie. That should not be the Windows problem right? It must be me doing something wrong, or am I mistaking? Anyway, thank you for your comment. Best regards. – AlwaysLearningNewStuff Dec 17 '13 at 18:58
  • You might want to get rid of the static controls and paint those boxes in your main `WM_PAINT` handler. – Jonathan Potter Dec 17 '13 at 19:01
  • @JonathanPotter I did paint them in `WM_PAINT`, and `static controls` are placed on top of those boxes, since they are transparent. By using the answer [from this question](http://stackoverflow.com/questions/20362622/static-controls-slightly-flicker-when-main-window-is-resized?rq=1), I have reduced flicker significantly.I believe that the code is "theoreticaly" sound, but I just do not understand what the problem could be. That is why I have made a demo application to demonstrate the problem in hope that someone can point me in the right direction. Thank you. Regards. – AlwaysLearningNewStuff Dec 17 '13 at 19:07
  • This is most likely to get solved if someone either reviews or runs your code. As it stands, it is too long and has too many dependencies for anyone to do either without a deal of work. The best thing you can do is create a much simpler sample that only has one static control, only uses one source file and still illustrates the problem. If you don't your question might be closed by the SSCCE police. At the very least put a zip file somewhere (that doesn't need a logon) that contains your existing code and its dependencies. – arx Dec 17 '13 at 19:38
  • @MarkRansom, I just try to present the problems I face as thoroughly as possible. The "mileage" I have gotten so far is, **in my humble opinion**, gained thanks to the thorough preparation of the question and the show of genuine try to solve the problem independently, but failing to achieve so. I thank you for your compliment. Best regards. – AlwaysLearningNewStuff Dec 17 '13 at 20:47
  • @JonathanPotter I have edited my question with the minimal source code that reproduces the problem. It is in the introductory section of the post. Hopefully we shall "talk" again. Best regards. – AlwaysLearningNewStuff Dec 17 '13 at 21:02

1 Answers1

3

I compiled and ran the code on Windows 7. Using the standard theme (which uses the DWM) it looked fine while resizing.

I switched to the Windows Classic theme (which disables the DWM) and there was a great deal of tearing along the edges of the buttons when the window resized. I suspect this is the problem you are seeing.

Tearing occurs when painting is not synchronised with the physical update of the screen. This results in part of the screen showing the old image and part of the screen showing the new image. The effect is especially noticeable on vertical lines moving horizontally.

Are you still using Windows XP?

As far as I know the only way to prevent tearing on XP is to use DirectX for your painting and explicitly synchronise with VSYNC. Though you might be able to use your existing painting code and just paint the final bitmap with DirectX. Or there may be some other mechanism for synchronisation. I don't know.

However, since the problem fixes itself on later versions of Windows I wouldn't do anything.

arx
  • 16,686
  • 2
  • 44
  • 61
  • THANK YOU SO MUCH! I came to the same conclusion browsing through the Internet ( I think it was on the Microsoft's forum, but I am not 100% sure ) but was not 100% sure so I have decided to ask here for help just in case it was my mistake! I will test this on Windows 7 on my laptop. We shall stay in touch. Thank you again. Best regards. – AlwaysLearningNewStuff Dec 17 '13 at 22:04
  • I have just tried my first demo application ( the one with all static controls, pictures and strings ) on the Windows 7 on my laptop. Tearing is not evident, but when I resize there is still flicker like effect. This especially happens when I resize from left to right, and from top to bottom. Isn't there any way to fix this, other than DirectX? Again, thank you for your efforts, I just want to do all in my power to make this visually as appealing as I can get. Regards. – AlwaysLearningNewStuff Dec 17 '13 at 22:18
  • You calculations are a bit iffy. For example, the orange box is supposed to be a fixed distance from the right but it shifts about by a few pixels. I tried the version with text. I think the text flickers a little because the sub-pixel anti-aliasing changes as the text moves. You can see this for yourself by making the text larger: only the edges flicker. Also, you'll get some choppiness if your frame rate is inadequate. You could try measuring how fast you're updating. – arx Dec 17 '13 at 22:27
  • I have never tried measuring the speed of updating. As for my math, I guess it can be improved. I haven't noticed the flickering of the text, thank you for pointing that out. I think that your answer is the one I have sought so I will accept it. One last favor: can you give me some tips/pointers on how to improve my code ( regarding my iffy math and stuff like that since I really want to do this project right) ? Thank you. You have +1 from me. Best regards. – AlwaysLearningNewStuff Dec 17 '13 at 22:37