在Windows中平滑调整窗口大小(使用Direct2D 1.1)?

它让我感到困扰,Windows中调整窗口的大小不像我想要的那样“顺利”(这是Windows程序的一般情况,不仅仅是我自己的情况,Visual Studio就是一个很好的例子)。 它使操作系统及其程序感觉“脆弱”和“便宜”(是的,我关心程序和用户界面的感受 ,同样关心关门的声音和感觉,这是构build质量),这在我看来影响了整体用户体验,最终影响了品牌。

重新调整窗口内容简直跟不上鼠标的移动。 无论何时调整窗口大小,都会产生“口吃”/“闪烁”效果,这是因为在新的resize的内容被绘制之前,窗口的先前大小的内容被重新绘制在新的resize的窗口框架中。

我正在构build一个使用Direct2D 1.1绘制其UI的Win32应用程序(x64),并且考虑到Direct2D的速度,我认为在2014年应该没有必要在操作系统中遭受这种工件。我自己在Windows 8.1上, Windows 7和这个应用程序。

当最大化一个小窗口时,“以前的大小”效果是特别明显的(因为窗口大小的差异足够大以便容易地将在较大窗口的左上angular闪烁的旧内容的图像与新内容随后被涂在它上面)。

这似乎正在发生:

  1. (比方说,屏幕上有一个完全呈现的窗口,大小为500 x 500像素)。
  2. 我最大化的窗口:
  3. 窗框被最大化
  4. 旧的500 x 500内容在新框架中绘制,之前..
  5. ..最大化的窗口重新绘制与适当大小的内容。

我想知道是否有任何方法来缓解这个问题(例如,通过拦截Windows消息),并避免在最终重新呈现之前用新内容重新绘制窗口。新的内容发生。 这就像Windows重新绘制自己的窗口,使用任何已经可用的graphics,在使用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_HREDRAWCS_VREDRAW 。 交换链是双缓冲的, Present呼叫是用SyncInterval = 0进行的。

我意识到,每当窗口大小发生变化时,重新创build交换链缓冲区相比静态窗口表面上的普通重绘会产生一些开销。 然而,“口吃”并不是由此引起的,因为即使在调整窗口大小的时候,即使缓冲区大小调整被禁用,现有的窗口内容也被简单地缩放 (尽pipe这样会使鼠标移动更好)。

虽然你的目标是值得称道的,但我怀疑,任何试图这样做的尝试最终都只会成为你和Windows之间的一场战斗 – 你不会赢(尽管你可能会设法通过一个光荣的平局)。 抱歉是负面的。

设置WM_SETREDRAW为FALSE,做你的大小调整,然后重新启用绘图,使窗口无效,操作系统将blit它。

我已经做了这个按钮启用和禁用按钮时,从列表中选择不同的项目,从来没有整个窗口。

如果你有一个固定大小(与全屏分辨率相同)的无边界子窗口(只在父窗口内呈现的类型),你应该得到更平滑的结果,因为没有内存重新分配(我认为是什么导致了jitterieness )。

如果它还不完美,请查看WM_SIZE和WM_SIZING,并检查是否可以用它们做一些魔法。 例如,在WM_SIZING上,你可以返回true来告诉你Windows处理了这个消息(离开这个窗口),并且把你的UI重新渲染到一个由WM_SIZING提供的大小的缓冲区,当这个完成时,你发送你自己的WM_SIZING,但是在WPARAM中使用未使用的位(以及其以前的内容),告诉您您有一个预渲染的缓冲区,您可以将其删除。 从msdn上的WM_SIZING文档看来,WPARAM应该有一些可供您使用的功能。

希望这可以帮助。

这是我想到的最好的,调整大小,虽然backbuffer blitting导致一些边缘闪烁,尚未用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 = GetmoduleeHandle(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 = px - framePos.x; mouseOffset.y = py - framePos.y; move = true; break; case WM_RBUTTONDOWN: p = MAKEPOINTS(lparam); mouseOffset.x = framePos.x + frameSize.x - px; mouseOffset.y = framePos.y + frameSize.y - py; 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 = GetmoduleeHandle(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; } 

调用CreateSwapChainForHwnd ,请确保已将交换链描述Scaling属性设置为DXGI_SCALING_NONE 。 这仅在使用“平台更新”的Windows 7上受支持,因此您可能需要回退到默认的DXGI_SCALING_STRETCH (后者是导致闪烁的原因)。