Плавное изменение размеров окна в Windows (с использованием Direct2D 1.1)?

Меня раздражает, что изменение размеров окон в Windows не такое «плавное», как хотелось бы (так обстоит дело с программами Windows в целом, а не только с моими. Visual Studio — хороший пример). Это заставляет ОС и ее программы чувствовать себя «хрупкими» и «дешевыми» (да, меня интересует, как программы и пользовательские интерфейсы Чувствовать, точно так же я забочусь о звуке и ощущении закрытия дверцы машины. Это отражение качества сборки), что, на мой взгляд, влияет на общий UX и, в конечном счете, на восприятие бренда.

Перерисовка содержимого окна просто не поспевает за движением мыши во время изменения размера. Всякий раз, когда я изменяю размер окна, возникает эффект «заикания» / «мерцания», по-видимому, из-за того, что содержимое предыдущего размера окна перерисовывается в новой рамке окна с измененным размером до рисования нового содержимого с измененным размером.

Я создаю приложение Win32 (x64), которое использует Direct2D 1.1 для рисования своего пользовательского интерфейса, и учитывая скорость Direct2D, я думаю, что в 2014 году такие артефакты в ОС должны быть излишними. Я сам на Windows 8.1, но нацеливаюсь на Windows 7 и выше с этим приложением.

Эффект «предыдущего размера» особенно заметен при максимизации небольшого окна (поскольку разница в размере окна достаточно велика, чтобы легко сопоставить изображение старого контента, так как оно кратковременно мигает в верхнем левом углу большего окна с новым контентом впоследствии закрашиваю его).

Вот что происходит:

  1. (Допустим, на экране есть полностью визуализированное окно размером 500 x 500 пикселей).
  2. Я максимизирую окно:
  3. Рамка окна максимально увеличена
  4. Старый контент 500 х 500 рисуется в новом фрейме, перед тем как ..
  5. ..развернутое окно перекрашивается с использованием содержимого правильного размера.

Мне интересно, есть ли способ смягчить это (например, избавиться от шага 4) — например, путем перехвата сообщения Windows — и избежать перекрашивания окна в новом размере со старым содержимым перед окончательной повторной визуализацией новый контент случается. Это похоже на то, как Windows выполняет перерисовку окна, используя любую имеющуюся графику, ПЕРЕД тем, что надоедает попросить меня предоставить обновленный контент с сообщением WM_PAINT или аналогичным.

Это можно сделать?

Редактировать: кажется, что WM_WINDOWPOSCHANGING / WM_SIZING обеспечивает «ранний доступ» к новым данным размера, но мне все еще не удалось подавить рисование старого содержимого.

мой WndProc выглядит так:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_ERASEBKGND:
return 1;
case WM_PAINT:
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
D2DRender();
EndPaint(hWnd, &ps);
return 0;
case WM_SIZE:
if (DeviceContext && wParam != SIZE_MINIMIZED)
{
D2DResizeTargetBitmap();
D2DRender();
}
return 0;
case WM_DISPLAYCHANGE:
D2DRender();
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}

Окно не имеет CS_HREDRAW или же CS_VREDRAW задавать. Swapchain с двойной буферизацией и Present вызов сделан с SyncInterval = 0.

Я знаю, что воссоздание буферов swapchain каждый раз, когда изменяется размер окна, создает немного накладные расходы по сравнению с простой перерисовкой на поверхности статического окна. Однако «заикание» не вызвано этим, как это происходит, даже когда изменение размера буфера отключено, а существующее содержимое окна просто масштабируется во время изменения размера окна (хотя это делает заставьте его идти в ногу с движением мыши).

11

Решение

При звонке CreateSwapChainForHwndубедитесь, что вы установили описание цепочки обмена Scaling собственность на DXGI_SCALING_NONE, Это поддерживается только в Windows 7 с обновлением платформы, поэтому вам может потребоваться вернуться к настройкам по умолчанию DXGI_SCALING_STRETCH (последнее — то, что вызывает мерцание).

1

Другие решения

Установите для WM_SETREDRAW значение ЛОЖЬ, измените размер, затем включите рисование, сделайте окно недействительным, и ОС его перезапустит.

Я сделал это для кнопок включения и отключения кнопок при выборе различных элементов из списка, но не для всего окна.

0

Что если у вас есть дочернее окно без полей (тип, который отображается только внутри родительского объекта) с фиксированным размером (то же, что и в полноэкранном разрешении), вы должны получить намного более плавные результаты, потому что нет перераспределения памяти (что, как мне кажется, вызывает дрожание) ).

Если он все еще не совершенен, посмотрите на WM_SIZE и WM_SIZING и проверьте, можете ли вы поработать с ними. Например, в WM_SIZING вы можете вернуть true, сообщая Windows, что вы обработали сообщение (оставив окно как есть), и вы повторно визуализируете свой пользовательский интерфейс в буфер с размером, предоставленным WM_SIZING, и когда это будет сделано, вы отправите свой собственный WM_SIZING, но с манипулируемым неиспользованным битом в WPARAM (вместе с его предыдущим содержимым), который сообщает вам, что у вас есть предварительно обработанный буфер для этого, который вы можете просто отключить. Из документации WM_SIZING для msdn кажется, что WPARAM должен иметь пару битов в вашем распоряжении.

Надеюсь это поможет.

0

Это лучшее из того, что я придумал, и оно отлично меняет размеры, хотя блики в буфере буфера вызывают некоторое мерцание, они еще не тестировались с DX или OGL, но они должны работать еще лучше с аппаратным ускорением. Это немного громоздко, но подойдет как доказательство концепции.

Если бы холст можно было обрезать без использования MDI, это было бы еще лучше, как, например, использование буфера битовой маски.

Одна вещь, которая меня не устраивает, это координаты положения дочернего окна, потому что они могут работать не во всех системах, но комбинация вызовов GetSystemMetrics для получения размеров границ и заголовков должна исправить это.

/* Smooth resizing of GDI+ MDI window
*
* Click window to resize, hit Escape or Alt+F4 to quit
*
* Character type is set to multibyte
* Project->Properties->Config Properties->General->Character Set = Multibyte
*
* Pritam 2014 */// Includes
#include <Windows.h>
#include <gdiplus.h>
#pragma comment (lib,"Gdiplus.lib")
using namespace Gdiplus;// Max resolution
#define XRES 1600
#define YRES 900// Globals
bool resizing = false;
HWND parent, child;        // child is the canvas window, parent provides clipping of child
Bitmap * buffer;// Render
void Render() {

// Get parent client size
RECT rc;
GetClientRect(parent, &rc);

// Draw backbuffer
Graphics * g = Graphics::FromImage(buffer);

// Clear buffer
g->Clear(Color(100, 100, 100));

// Gray border
Pen pen(Color(255, 180, 180, 180));
g->DrawRectangle(&pen, 10, 10, rc.right - 20, rc.bottom - 20);
pen.SetColor(Color(255, 0, 0, 0));
g->DrawRectangle(&pen, 0, 0, rc.right - 1, rc.bottom - 1);

// Draw buffer to screen
PAINTSTRUCT ps;
HDC hdc = BeginPaint(child, &ps);
Graphics graphics(hdc);

graphics.DrawImage(buffer, Point(0, 0));

// Free
EndPaint(child, &ps);
}// MDI Callback
LRESULT CALLBACK MDICallback(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
switch(message) {
case WM_LBUTTONDOWN:
resizing = true; // Start resizing
return 0;
break;
case WM_KEYDOWN:
if(wparam == VK_ESCAPE) { // Exit on escape
PostQuitMessage(0);
}
TranslateMessage((const MSG *)&message);
return 0;
break;
case WM_PAINT:
Render();
return 0;
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
}

return DefMDIChildProc(hwnd, message, wparam, lparam);
}// Parent window callback
LRESULT CALLBACK WndCallback(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
return DefFrameProc(hwnd, child, message, wparam, lparam);
}// Create windows
bool CreateWindows(void) {

// Parent class
WNDCLASSEX wndclass;
ZeroMemory(&wndclass, sizeof(wndclass)); wndclass.cbSize = sizeof(wndclass);

wndclass.style = CS_NOCLOSE;
wndclass.lpfnWndProc = WndCallback;
wndclass.hInstance = GetModuleHandle(NULL);
wndclass.lpszClassName = "WNDCALLBACKPARENT";
wndclass.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

if(!RegisterClassEx(&wndclass)) return false;

// MDI class
wndclass.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
wndclass.lpfnWndProc = MDICallback;
wndclass.lpszClassName = "MDICALLBACKCANVAS";

if(!RegisterClassEx(&wndclass)) return false;// Parent window styles
DWORD style = WS_POPUP | WS_CLIPCHILDREN;
DWORD exstyle = 0;

// Set initial window size and position
RECT rc;
rc.right = 640;
rc.bottom = 480;

AdjustWindowRectEx(&rc, style, false, exstyle);

rc.left = 20;
rc.top = 20;

// Create window
if(!(parent = CreateWindowEx(exstyle, "MDICLIENT", "MDI Resize", style, rc.left, rc.top, rc.right, rc.bottom, NULL, NULL, wndclass.hInstance, NULL))) return false;// MDI window styles
style = MDIS_ALLCHILDSTYLES;
exstyle = WS_EX_MDICHILD;

// Set MDI size
rc.left = - 8; // The sizes occupied by borders and caption, if position is not correctly set an ugly caption will appear
rc.top = - 30;
rc.right = XRES;
rc.bottom = YRES;
AdjustWindowRectEx(&rc, style, false, exstyle);

// Create MDI child window
if(!(child = CreateWindowEx(exstyle, "MDICALLBACKCANVAS", "", style, rc.left, rc.top, rc.right, rc.bottom, parent, NULL, wndclass.hInstance, NULL))) return 8;

// Finalize
ShowWindow(child, SW_SHOW);
ShowWindow(parent, SW_SHOWNORMAL);

// Success
return true;
}// Resize
void Resize(void) {

// Init
RECT rc, rcmdi;
GetClientRect(child, &rcmdi); // Use mdi window size to set max resize for parent
GetWindowRect(parent, &rc);

// Get mouse position
POINT mp;
GetCursorPos(&mp);

// Set new size
rc.right = mp.x - rc.left + 10;
rc.bottom = mp.y - rc.top + 10;

// Apply min & max size
if(rc.right < 240) rc.right = 240; if(rc.bottom < 180) rc.bottom = 180;
if(rc.right > rcmdi.right) rc.right = rcmdi.right; if(rc.bottom > rcmdi.bottom) rc.bottom = rcmdi.bottom;

// Update window size
SetWindowPos(parent, NULL, rc.left, rc.top, rc.right, rc.bottom, SWP_NOZORDER | SWP_NOMOVE);

// Make sure client is entirely repainted
GetClientRect(child, &rc);
InvalidateRect(child, &rc, false);
UpdateWindow(child);

// Stop resizing if mousebutton is up
if(!(GetKeyState(VK_LBUTTON) & 1 << (sizeof(short) * 8 - 1)))
resizing = false;
}// Main
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE pinstance, LPSTR cmdline, int cmdshow) {

// Initiate GDI+
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

buffer = new Bitmap(XRES, YRES, PixelFormat24bppRGB);

// Create windows
if(!CreateWindows()) return 1;// Main loop
bool running = true;
MSG message;
while(running) {

// Check message or pass them on to window callback
if(PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {
if(message.message == WM_QUIT) {
running = false;
} else {
if(!TranslateMDISysAccel(child, &message)) {
TranslateMessage(&message);
DispatchMessage(&message);
}
}
}

// Resize
if(resizing)
Resize();

// Sleep a millisecond to spare the CPU
Sleep(1);
}// Free memmory and exit
delete buffer;
GdiplusShutdown(gdiplusToken);
return 0;
}

Редактировать: еще один пример с использованием «битовой маски» / многослойного окна.

// Escape to quit, left mousebutton to move window, right mousebutton to resize.
// And again char set must be multibyte

// Include
#include <Windows.h>
#include <gdiplus.h>
#pragma comment (lib,"Gdiplus.lib")
using namespace Gdiplus;// Globals
Bitmap * backbuffer;
int xres, yres;
bool move, size;
POINT framePos, frameSize, mouseOffset;

// Renders the backbuffer
void Render(void) {
if(!backbuffer) return;

// Clear window with mask color
Graphics * gfx = Graphics::FromImage(backbuffer);
gfx->Clear(Color(255, 0, 255));

// Draw stuff
SolidBrush brush(Color(120, 120, 120));
gfx->FillRectangle(&brush, framePos.x, framePos.y, frameSize.x, frameSize.y);
}

// Paints the backbuffer to window
void Paint(HWND hwnd) {
if(!hwnd) return;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Graphics gfx(hdc);
gfx.DrawImage(backbuffer, Point(0, 0));
EndPaint(hwnd, &ps);
}void HandleMove(HWND hwnd) {

// Get mouse position
POINT mouse;
GetCursorPos(&mouse);

// Update frame position
framePos.x = mouse.x - mouseOffset.x;
framePos.y = mouse.y - mouseOffset.y;

// Redraw buffer and invalidate & update window
Render();
InvalidateRect(hwnd, NULL, false);
UpdateWindow(hwnd);

// Stop move
if(!(GetKeyState(VK_LBUTTON) & 1 << (sizeof(short) * 8 - 1)))
move = false;
}

void HandleSize(HWND hwnd) {

// Get mouse position
POINT mouse;
GetCursorPos(&mouse);

// Update frame size
frameSize.x = mouse.x + mouseOffset.x - framePos.x;
frameSize.y = mouse.y + mouseOffset.y - framePos.y;

//frameSize.x = mouse.x + mouseOffset.x;
//frameSize.y = mouse.y + mouseOffset.y;

// Redraw buffer and invalidate & update window
Render();
InvalidateRect(hwnd, NULL, false);
UpdateWindow(hwnd);

// Stop size
if(!(GetKeyState(VK_RBUTTON) & 1 << (sizeof(short) * 8 - 1)))
size = false;
}LRESULT CALLBACK WindowCallback(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {

POINTS p;

switch(msg) {
case WM_KEYDOWN:
if(wparam == VK_ESCAPE) PostQuitMessage(0);
return 0;
break;
case WM_LBUTTONDOWN:
p = MAKEPOINTS(lparam); // Get mouse coords
mouseOffset.x = p.x - framePos.x;
mouseOffset.y = p.y - framePos.y;
move = true;
break;
case WM_RBUTTONDOWN:
p = MAKEPOINTS(lparam);
mouseOffset.x = framePos.x + frameSize.x - p.x;
mouseOffset.y = framePos.y + frameSize.y - p.y;
size = true;
break;
case WM_PAINT:
Paint(hwnd);
return 0;
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}// Main
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE pinstance, LPSTR cmdline, int cmdshow) {

// Init resolution, frame
xres = GetSystemMetrics(SM_CXSCREEN);
yres = GetSystemMetrics(SM_CYSCREEN);

move = false; size = false;
framePos.x = 100; framePos.y = 80;
frameSize.x = 320; frameSize.y = 240;
mouseOffset.x = 0; mouseOffset.y = 0;

// Initiate GDI+
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

// Init backbuffer
backbuffer = ::new Bitmap(xres, yres, PixelFormat24bppRGB);
Render();// Window class
WNDCLASSEX wc; ZeroMemory(&wc, sizeof(wc)); wc.cbSize = sizeof(wc);

wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
wc.lpfnWndProc = WindowCallback;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = "SingleResizeCLASS";
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);

if(!RegisterClassEx(&wc)) return 1;// Create window
HWND hwnd;
DWORD style = WS_POPUP;
DWORD exstyle = WS_EX_LAYERED;
if(!(hwnd = CreateWindowEx(exstyle, wc.lpszClassName, "Resize", style, 0, 0, xres, yres, NULL, NULL, wc.hInstance, NULL)))
return 2;

// Make window fully transparent to avoid the display of unpainted window
SetLayeredWindowAttributes(hwnd, 0, 0, LWA_ALPHA);

// Finalize
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);

// Make window fully opaque, and set color mask key
SetLayeredWindowAttributes(hwnd, RGB(255, 0, 255), 0, LWA_COLORKEY);// Main loop
MSG msg;
bool running = true;
while(running) {

// Check message
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if(msg.message == WM_QUIT) {
running = false;
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

// Move or size frame
if(move) { HandleMove(hwnd); }
if(size) { HandleSize(hwnd); }

Sleep(1);
}

// Free memory
::delete backbuffer;
backbuffer = NULL;
GdiplusShutdown(gdiplusToken);

// Exit
return 0;
}
0

Есть способ предотвратить ненужный BitBlt, упомянутый в шаге 4 выше.

До Windows 8 это можно было сделать либо путем создания собственной пользовательской реализации WM_NCCALCSIZE сказать Windows, чтобы ничего не стряхнулось (или не перевернул один пиксель поверх себя), или вы могли бы перехватить WM_WINDOWPOSCHANGING (сначала передавая его на DefWindowProc) и установить WINDOWPOS.flags |= SWP_NOCOPYBITS, который отключает BitBlt внутри внутреннего звонка SetWindowPos() что Windows делает во время изменения размера окна. Это имеет тот же возможный эффект пропуска BitBlt,

Однако ничто не может быть так просто. С появлением Windows 8/10 Aero приложения теперь извлекаются из буфера вне экрана, который затем создается новым, злым оконным менеджером DWM.exe. И оказывается, что DWM.exe иногда делает свое дело BitBlt введите операцию поверх той, что уже была сделана в устаревшем коде XP / Vista / 7. А помешать DWM делать это намного сложнее; до сих пор я не видел каких-либо полных решений.

Так что вам нужно пройти через оба слоя. Пример кода, который пробьет уровень XP / Vista / 7 и, по крайней мере, улучшит производительность уровня 8/10, см. В следующих статьях:

Как сгладить уродливый джиттер / мерцание / прыжок при изменении размеров окон, особенно перетаскивая левую / верхнюю границу (Win 7-10; bg, bitblt и DWM)?

0

Хотя ваша цель достойна похвалы, я подозреваю, что любая попытка сделать это просто закончится борьбой между вами и Windows — которую вы не выиграете (хотя вам, возможно, удастся пробиться к достойной ничьей). Извините за негатив

-1
По вопросам рекламы [email protected]