Windows 移动/调整窗口大小时闪烁
我开发了一个显示jpeg图像的应用程序。它可以显示4幅图像,屏幕的每个象限一幅。它使用了4个窗口。窗口没有边框(边框)或标题栏。 加载新图像时,将为新图像调整窗口大小,然后显示图像 尤其是当窗口变大时,常常会出现闪烁。我的眼睛盯着狭缝,似乎在显示新内容之前调整大小时,旧内容会被移动 我查阅了很多资料,使用了所有的技巧:Windows 移动/调整窗口大小时闪烁,windows,image,winapi,flicker,Windows,Image,Winapi,Flicker,我开发了一个显示jpeg图像的应用程序。它可以显示4幅图像,屏幕的每个象限一幅。它使用了4个窗口。窗口没有边框(边框)或标题栏。 加载新图像时,将为新图像调整窗口大小,然后显示图像 尤其是当窗口变大时,常常会出现闪烁。我的眼睛盯着狭缝,似乎在显示新内容之前调整大小时,旧内容会被移动 我查阅了很多资料,使用了所有的技巧: 该窗口只有样式CS_DBLCLKS(无CS_HREDRAW或CS_VREDRAW) 背景笔刷为空 WM_ERASEBKGND返回1 WM_NCPAINT返回0 WM\u NCC
- 该窗口只有样式
(无CS_DBLCLKS
或CS_HREDRAW
)CS_VREDRAW
- 背景笔刷为空
返回1WM_ERASEBKGND
返回0WM_NCPAINT
告诉它对齐未移动的一侧(你能告诉它放弃客户区吗?)WM\u NCCALCSIZE
返回0WM_WINDOWPOSCHANGING
- SetWindowPos具有标志
SWP_NOCOPYBITS | SWP_DEFERERASE | SWP_NOREDRAW | SWP_NOSENDCHANGING
- 将WindowPos设置为新的大小和位置
- 无效(hWnd、NULL、FALSE)
- 更新窗口(hWnd)
WM_PAINT
WM_PAINT
hDC= BeginPaint (hWnd, &ps);
hMemDC= CreateCompatibleDC (hDC);
hOldBitmap = SelectObject (hMemDC, hNewBitmap);
BitBlt (hDC,...,hMemDC,0,0,SRCCOPY);
SelectObject (hMemDC, hOldBitmap);
DeleteDC (hMemDC);
EndPaint (hWnd, &ps);
有谁能告诉我,我是否/在哪里犯了导致窗口旧内容移动的错误
硬件等:HP Elitebook Core7上的Windows 7 64位,带有NVIDIA Quadro K1000m驱动程序9.18.13.3265(更新为341.44)。
更新(7月17日) 我在另一台Windows计算机(Windows 8/10)上也看到了pogram的行为。它看起来不是英伟达显示驱动程序。 将平铺到屏幕中心(右下角=w/2,h/2)的窗口调整到左侧或左上角(0,0)时,该行为最为明显
我可能对WM_NCCALCSIZE消息告诉Windows不要做任何事情的计算有问题。有人能给我举个计算的例子吗?另请参见您有一个令人印象深刻的防闪烁技巧列表:-) 我不知道这是否重要(因为这取决于工具窗口的创建方式,特别是它们是否是公共父窗口的子窗口): 尝试在工具窗口的父窗口(如果有)中设置窗口样式
WS_CLIPCHILDREN
如果未设置,父窗口将擦除其(整个)背景,然后将绘制消息转发给子窗口,这将导致闪烁。如果设置了
WS_CLIPCHILDREN
,则父窗口不会对子窗口占用的客户端区域执行任何操作。因此,子窗口区域不会被绘制两次,也不会出现闪烁现象。这是一个理论,而不是一个答案:
默认情况下,在现代Windows中,您的窗口只是视频卡上的纹理,桌面窗口管理器正在将其映射到屏幕上的矩形。您似乎已经做了一切必要的工作,以确保纹理一下子得到更新
但是,当您调整窗口大小时,桌面合成器可能会立即更新其几何体,导致(仍保持不变)纹理显示在屏幕上的新位置。只有在以后进行绘制时,纹理才会更新
您可以通过暂时关闭桌面合成来测试这一理论。在Windows 7上,导航到“系统属性”,选择“高级”选项卡,在“性能选择设置…”下,在“视觉效果”选项卡上,取消选择“启用桌面合成”设置。然后尝试重现问题。如果它消失了,那就支持(但不能完全证明)我的理论
(请记住重新启用合成,因为大多数用户大部分时间都是这样运行的。)
如果这个理论是正确的,那么我们的目标似乎是在调整窗口大小后尽快开始绘制。如果时间窗口足够小,则两者都可能在监视器刷新周期内发生,并且不会出现闪烁
具有讽刺意味的是,您消除闪烁的努力在这里可能对您不利,因为您故意抑制了通常由SetWindowPos产生的无效和重画,直到您在稍后的步骤中手动执行为止
调试提示:尝试在过程中的关键点引入延迟(例如,
Sleep(1000);
),这样您就可以看到调整大小和重画是否作为两个不同的步骤在屏幕上实际呈现。只是技巧列表之外的一个步骤。在使用左/上边缘调整窗口大小时,我的Dell XPS笔记本上的闪烁也存在同样的问题。你提到的所有把戏我都试过了。据我所知,窗口边框是在GPU中绘制的,窗口内容是在GDI子系统中准备的,并传输到视频内存中进行窗口合成(DWM在Windows 8.1中引入)。我尝试完全删除GDI渲染(设置WS_EX_NoreditionBitmap样式),这会使窗口不包含任何内容,然后直接使用DXGI子系统(使用CreateSwapChainForComposition,很少有示例说明如何执行此操作)创建内容曲面。但问题依然存在。渲染窗口框架和调整/显示内容表面之间仍然存在延迟
然而,它可能会解决你的问题,因为你没有边界,你不会注意到这个滞后。但您将完全控制窗口重新绘制,并在GPU端进行重新绘制。您确实掌握了一套不错的技巧 首先,我可以建议一些现有技巧的变体,特别是在XP/Vista/7上,它们可能会有所帮助;其次,我想提及在Win8/10上看到的持续闪烁的位置
case WM_NCCALCSIZE:
{
RECT ocr; // old client rect
if (wParam)
{
NCCALCSIZE_PARAMS *np = (NCCALCSIZE_PARAMS *)lParam;
// np->rgrc[0] is new window rect
// np->rgrc[1] is old window rect
// np->rgrc[2] is old client rect
ocr = np->rgrc[2];
}
else
{
RECT *r = (RECT *)lParam;
// *r is window rect
}
// first give Windoze a crack at it
lRet = DefWindowProc(hWnd, uMsg, wParam, lParam);
if (wParam)
{
NCCALCSIZE_PARAMS *np = (NCCALCSIZE_PARAMS *)lParam;
// np->rgrc[0] is new client rect computed
// np->rgrc[1] is going to be dst blit rect
// np->rgrc[2] is going to be src blit rect
//
ncr = np->rgrc[0];
RECT &dst = np->rgrc[1];
RECT &src = np->rgrc[2];
// FYI DefWindowProc gives us new client rect that
// - in y
// - shares bottom edge if user dragging top border
// - shares top edge if user dragging bottom border
// - in x
// - shares left edge if user dragging right border
// - shares right edge if user dragging left border
//
src = ocr;
dst = ncr;
// - so src and dst may have different size
// - ms dox are totally unclear about what this means
// https://docs.microsoft.com/en-us/windows/desktop/
// winmsg/wm-nccalcsize
// https://docs.microsoft.com/en-us/windows/desktop/
// api/winuser/ns-winuser-tagnccalcsize_params
// - they just say "src is clipped by dst"
// - they don't say how src and dst align for blt
// - resolve ambiguity
// essentially disable BitBlt by making src/dst same
// make it 1 px to avoid waste in case Windoze still does it
dst.right = dst.left + 1;
dst.bottom = dst.top + 1;
src.right = dst.left + 1;
src.bottom = dst.top + 1;
lRet = WVR_VALIDRECTS;
}
else // wParam == 0: Windoze wants us to map a single rect w->c
{
RECT *r = (RECT *)lParam;
// *r is client rect
}
return lRet;