C++ 在Windows中平滑调整窗口大小(使用Direct2D 1.1)?
在windows中调整窗口的大小并不像我希望的那样“平滑”,这让我很恼火(windows程序通常都是这样,而不仅仅是我自己的。Visual Studio就是一个很好的例子)。它让操作系统及其程序感觉“脆弱”和“廉价”(是的,我关心程序和用户界面的感觉,就像我关心关车门的声音和感觉一样。这反映了构建质量),在我看来,这会影响整个用户体验,并最终影响品牌的感知 窗口内容的重画根本跟不上调整大小期间鼠标的移动。每当我调整窗口大小时,都会出现“断断续续”/“闪烁”的效果,这似乎是由于在绘制新的、调整大小的内容之前,在新的、调整大小的窗口框架中重新绘制了窗口先前的大小内容 我正在构建一个使用Direct2D 1.1绘制UI的Win32应用程序(x64),考虑到Direct2D的速度,我认为在2014年的操作系统中应该没有必要出现这种瑕疵。我自己也使用Windows 8.1,但目标是Windows 7及更高版本的应用程序 当最大化一个小窗口时,“以前的大小”效果尤其明显(因为窗口大小的差异足够大,以便在较大窗口的左上角短暂闪烁的旧内容图像与随后在其上绘制的新内容图像形成对比) 这似乎是正在发生的事情:C++ 在Windows中平滑调整窗口大小(使用Direct2D 1.1)?,c++,windows,winapi,paint,direct2d,C++,Windows,Winapi,Paint,Direct2d,在windows中调整窗口的大小并不像我希望的那样“平滑”,这让我很恼火(windows程序通常都是这样,而不仅仅是我自己的。Visual Studio就是一个很好的例子)。它让操作系统及其程序感觉“脆弱”和“廉价”(是的,我关心程序和用户界面的感觉,就像我关心关车门的声音和感觉一样。这反映了构建质量),在我看来,这会影响整个用户体验,并最终影响品牌的感知 窗口内容的重画根本跟不上调整大小期间鼠标的移动。每当我调整窗口大小时,都会出现“断断续续”/“闪烁”的效果,这似乎是由于在绘制新的、调整大小
WM_WINDOWPOSCHANGING
/WM_size
提供了对新尺寸数据的“早期访问”,但我仍然没有成功抑制旧内容的绘制
我的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\u HREDRAW
或CS\u VREDRAW
。swapchain是双缓冲的,Present
调用是在SyncInterval=0的情况下进行的
我知道,与静态窗口表面上的普通重画相比,每次窗口大小更改时重新创建swapchain缓冲区确实会产生一些开销。然而,“口吃”并不是由此引起的,因为即使在禁用缓冲区大小调整并且在窗口大小调整过程中简单地缩放现有窗口内容时也会发生这种情况(尽管这确实使其更好地跟上鼠标移动)。尽管您的目标值得称赞,我怀疑,任何这样做的企图都会以你和Windows之间的争斗而告终——你不会赢(尽管你可能会设法以体面的方式赢得平局)。很抱歉是否定的。将WM_SETREDRAW设置为FALSE,进行大小调整,然后重新启用绘图,使窗口无效,操作系统将使其无效
当从列表中选择不同的项目时,我已经对按钮启用和禁用按钮执行了此操作,而不是对整个窗口执行此操作。如果您有一个固定大小(与全屏分辨率相同)的无边框子窗口(仅在父窗口内呈现的类型),该怎么办,您应该会得到更平滑的结果,因为没有内存重新分配(我认为这是导致紧张的原因) 如果它仍然不完美,请查看WM_大小和WM_大小,并检查您是否可以对它们施展魔法。例如,在WM_SIZING上,您可以返回true,告诉Windows您处理了消息(保持窗口不变),然后使用WM_SIZING提供的大小将UI重新呈现到缓冲区,完成后,您可以发送自己的WM_SIZING,但在WPARAM中有一个被操纵的未使用位(及其以前的内容)这就告诉你你有一个预渲染的缓冲区,你可以直接将其删除。从msdn上的WM_尺寸文档来看,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 = 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;
}
/*平滑调整GDI+MDI窗口的大小
*
*单击窗口以调整大小,单击Escape或Alt+F4退出
*
*字符类型设置为多字节
*项目->属性->配置属性->常规->字符集=多字节
*
*Pritam 2014*/
//包括
#包括
#包括
#pragma注释(lib,“Gdiplus.lib”)
使用名称空间Gdiplus;
//最大分辨率
#定义XRES 1600
#定义YRES 900
//全球的
bool-resizing=false;
HWND父项,子项;//子窗口是画布窗口,父窗口提供子窗口的剪辑
位图*缓冲区;
//渲染
void Render(){
//获取父客户端大小
RECT-rc;
GetClientRect(父级和rc);
//拉回缓冲器
Graphics*g=Graphics::FromImage(缓冲区);
//清除缓冲区
g->Clear(颜色(100100100));
//灰色边框
钢笔(科罗拉多州)
// 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;
}