У меня есть сложное рисование для реализации в обработчике WM_PAINT моего главного окна.
Я представил картинку ниже, чтобы проиллюстрировать это:
Главное окно имеет статические элементы управления вместо кнопок, которые имеют стиль SS_NOTIFY
,
Когда пользователь нажимает на них, в программе происходят определенные действия, которые пока неактуальны.
На следующем рисунке показано, где находятся статические элементы управления в главном окне:
Карта на оранжевой панели представляет собой EMF
файл, верхний левый и правый логотипы PNG
файлы и другие картинки bitmap
s.
Visual Styles
включены через #pragma
директивы. Я также использую GDI+
, вместе с GDI
,
Проект был создан как пустой проект, и я все кодировал с нуля.
Для реализации этой задачи я решил нарисовать вся картина в WM_PAINT
, и к поставить прозрачным static control
над изображениями на картинке, которая им соответствует.
Чтобы сохранить мой код чистым и простым, я сделал функции, которые реализуют вышеупомянутое, поэтому мой WM_PAINT
Обработчик может быть как можно меньше.
Для того, чтобы реализовать совет, полученный от члена ARX Я публикую один исходный код, который можно скомпилировать и который может воспроизвести проблему:
#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;
}
я работаю на Windows XP
, с помощью MS Visual Studio C++ 2008 Express Edition
а также pure Win32 API
,
Одна запись: с экспресс-издания VS не иметь редактор ресурсов, файл ресурсов и заголовок ресурса были созданы с использованием ResEdit отсюда: http://www.resedit.net/.
Чтобы избежать мерцания, я использовал двойную буферизацию, о которой я узнал из статей Пола Ватта о CodeProject, Программирование Чарльза Петцольда для Windows 5-е издание а также Учебник по Forger для WIN32.
Теоретически все в порядке, и мой код компилируется без ошибок.
Я вставил код здесь: http://pastebin.com/zSYT1i8L
Мой английский не достаточно хорош, чтобы точно описать проблемы, с которыми я сталкиваюсь (все, что я могу сказать, это то, что края основного окна и перерисовка статического элемента управления «медленно» и они мерцают), поэтому я создал демонстрационное приложение, которое демонстрирует их: http://www.filedropper.com/geotermistgrafika
Я справился WM_ERASEBKGND
(вернулся (LRESULT)1
) и я исключил стили CS_VREDRAW
а также CS_HREDRAW
из моего окна класса — поэтому мерцание не должно быть вызвано из-за этого.
Мое окно не имеет WS_CLIPCHILDREN
стиль, потому что часть изображения рабочего стола видна там, где находятся статические элементы управления.
В моем WM_SIZE
У меня есть обработчик:
Переставлен статический контроль с использованием SetWindowPos(...)
API и уменьшенное мерцание, добавляя SWP_NOCOPYBITS
флаг.
Недействительно все окно с InvalidateRect( hWnd, NULL, FALSE )
, так что этот API не отправляет WM_ERASEBKGND
при аннулировании (3-й параметр FALSE
), но даже если я попробую с TRUE
Эффект тот же.
Я реализовал двойную буферизацию для WM_PAINT
обработчик, как в примерах, приведенных в вышеупомянутых книгах / статьях / учебных пособиях (делая все в памяти DC и делать BitBlt(...)
на экране DC во избежание мерцания).
Я не справился WM_SIZING
ни WM_WINDOWPOSCHANGING
, или же WM_MOVING
сообщение.
Я использовал инструмент GDIView ( http://www.nirsoft.net/utils/gdi_handles.html ) отслеживать GDI leaks
,
Каждый раз, когда я изменяю размер / увеличиваю окно, GDIView показывает +4 в столбце для регионов, что должно означать, что я пропускаю регионы, но я не могу понять, как это возможно, так как я не использую API, которые манипулируют регионами, и дважды проверил все.
На мой взгляд, все должно быть хорошо, и, возможно, это не имеет значения, но я просто подумал упомянуть об этом, может быть, это важно.
Если я добавлю WS_EX_COMPOSITED
стиль главного окна, производительность не улучшается.
Я попытался найти в Интернете пример, который помог бы мне решить мою проблему, но все учебные пособия просты и не охватывают этот тип сложных картинок.
ВАЖНАЯ ЗАМЕТКА:
Оставив мой WM_PAINT
обработчик пустой и вызывающий onPaint
функция в WM_ERASEBKGND
с контекстом устройства, полученным с GetDC(..)
API, мерцание исчезло, но во время изменения размера, перерисовка окна была «остроконечной», и проблема с краями основного окна не была решена.
Тем не менее, это гораздо лучшее улучшение, чем оригинальный код.
Как избавиться от проблем, продемонстрированных в моем демонстрационном приложении, представленном выше?
Я благодарю всех, кто вкладывает свое время и усилия, чтобы попытаться помочь мне.
С наилучшими пожеланиями.
Я скомпилировал и запустил код на Windows 7. Используя стандартную тему (которая использует DWM) это выглядело хорошо при изменении размера.
Я переключился на тему Windows Classic (которая отключает DWM), и по краям кнопок было много разрывов при изменении размера окна. Я подозреваю, что это проблема, которую вы видите.
Разрыв происходит, когда рисование не синхронизировано с физическим обновлением экрана. Это приводит к тому, что часть экрана показывает старое изображение, а часть экрана показывает новое изображение. Эффект особенно заметен на вертикальных линиях, движущихся горизонтально.
Вы все еще используете Windows XP?
Насколько я знаю, единственный способ предотвратить разрыв на XP — это использовать DirectX для рисования и явно синхронизироваться с VSYNC. Хотя вы можете использовать существующий код рисования и просто нарисовать окончательный рисунок с помощью DirectX. Или может быть какой-то другой механизм для синхронизации. Я не знаю.
Однако, поскольку проблема устраняется в более поздних версиях Windows, я ничего не буду делать.
Других решений пока нет …