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:
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:
Map on the orange panel is an EMF
file ,top left and right logos are PNG
files, and other pictures are bitmap
s.
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 control
s 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 :
Repositioned static controls using
SetWindowPos(...)
API and reduced flickering by adding theSWP_NOCOPYBITS
flag.Invalidated the entire window with the
InvalidateRect( hWnd, NULL, FALSE )
, so this API does not sendWM_ERASEBKGND
when invalidating ( 3rd parameter isFALSE
), but even if I try withTRUE
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.
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.