我所遇到的问题似乎微不足道,但我无法找到解决问题的方法。这里是。我有一个窗口,里面有一些图形。
为简单起见,我们可以说它是一个绿色的矩形矩形,它填满了窗口的整个客户区域。我希望重绘这个矩形,并在每次窗口改变其大小时填充整个窗口。我最初做的是这个。我从 WM_SIZE 处理程序发布了 WM_PAINT 消息。
它可以工作,但如果我快速移动鼠标,我会在绿色矩形周围看到一些未上漆的(白色)区域(实际上只有一侧或两侧,靠近鼠标的位置)。我对该问题的理解是处理用户输入(鼠标)的系统线程比我的WM_PAINT消息处理程序工作得更快。这意味着当我开始绘制更新的矩形(其大小取自WM_SIZE)时,鼠标实际上移动了一点,系统绘制了一个新的窗口框架,这与我试图用绿色填充的不同。这会在调整大小时移动边框旁边创建未填充区域。
当我停止调整大小时,绿色最终会填满整个窗口,但在调整大小时,接近边界会发生一些闪烁,这很烦人。为了解决这个问题,我尝试了以下内容。
bool finishedPainting;
RECT windowRect;
case WM_PAINT :
// ..... painting here
finishedPainting = TRUE;
break;
case WM_SIZE :
// .... some actions
// posting WM_PAINT
InvalidateRect(hWnd, NULL, FALSE);
PostMessage(hWnd, WM_PAINT, 0, 0);
break;
case WM_SIZING :
// this supposedly should prevent the system from passing
// new window size to WM_SIZE
if (!finishedPainting) memcpy((void*)lParam, &windowRect, sizeof(windowRect));
else {
// remember current window size for later use
memcpy(&windowRect, (void*)lParam, sizeof(windowRect));
finishedPainting = FALSE;
}
return TRUE;
它不起作用。作为一个微小的变化,我也试过了。
bool finishedPainting;
POINT cursorPos;
case WM_PAINT :
// ..... painting here
finishedPainting = TRUE;
break;
case WM_SIZE :
if (!finishedPainting) SetCursorPos(cursorPos.x, cursorPos.y);
else {
finishedPainting = FALSE;
GetCursorPos(&cursorPos);
// .... some actions
InvalidateRect(hWnd, NULL, FALSE);
PostMessage(hWnd, WM_PAINT, 0, 0);
}
break;
这也行不通。据我所知,问题的解决方法在于以某种方式减慢鼠标的速度,使其在绘画完成后移动到屏幕上的下一个位置(用它拖动窗口的角落或侧面)。
任何想法如何实现这一目标?或者也许我看到问题的方式存在根本性的错误,解决方案还存在于其他地方?
// ============================================= =======
更新
我做了一些实验,这是我发现的
1)调整大小时,消息序列为WM_SIZING - WM_NCPAINT - WM_SIZE - WM_PAINT。这看起来有点奇怪。我希望WM_SIZE能够在不中断WM_NCPAINT
的情况下遵循WM_SIZING2)在每个消息处理程序中,我在调整大小期间检查窗口的宽度(为简单起见,我只是改变宽度)。令人惊讶的是,WM_SIZE中测量的宽度与WM_SIZING中的宽度不同,但与WM_NCPAINT和WM_PAINT中的宽度相同。这不是问题,只是一个奇怪的事实。
3)我得出的结论是,窗口边界附近发生闪烁有两个主要原因。第一个是WM_NCPAINT在WM_PAINT之前。想象一下,你正在伸展你的窗户。首先出现新帧(首先是WM_NCPAINT),然后WM_PAINT填充客户区。当新帧已经在屏幕上时,人眼会捕捉到这么短的时间段,但它是空的。即使您指定在重新绘制之前不想删除窗口背景,仍然新添加的区域为空,您可以暂时看到它。当您抓住右侧窗口边缘并快速向右移动时,可以最好地演示闪烁的原因。闪烁效果的另一个原因不那么明显,当您抓住左侧窗口边缘并将其向左移动时最佳。在此移动过程中,您将看到沿着右边缘的未填充区域。据我所知,效果是由此引起的。当用户进行调整大小时,Windows执行以下操作:A)它发送WM_NCPAINT以绘制新帧,B)它将旧客户区域的内容复制到新的左上角窗口(在我们的例子中,它移动到左侧), C)它发送WM_PAINT以填充新的客户区域。然而,在阶段B由于某种原因,Windows会在右边缘生成那些未填充区域,尽管看起来不应该这样,因为旧内容应该保持原样,直到它在WM_PAINT期间重新绘制。好的,问题仍然存在 - 如何在调整大小期间摆脱那些人工制品。据我所知,现在使用标准技术和功能是不可能的,因为它们是由Windows在调整大小期间执行的步骤序列引起的。交换WM_NCPAINT和WM_PAINT可能会有所帮助,但这似乎超出了我们的控制范围(除非有一种简单的方法可以做到这一点,我不知道)。
答案 0 :(得分:4)
您不应自行发布或发送WM_PAINT消息。相反,使用:: InvalidateRect使窗口的某些部分无效,让Windows决定何时发送WM_PAINT消息。
答案 1 :(得分:2)
Windows故意以这种方式工作。通常认为响应用户(即鼠标)比拥有完全最新的彩绘窗口更重要。
如果您总是在WM_PAINT处理程序中绘制整个窗口,则可以通过覆盖WM_ERASEBKGND处理程序并在不执行任何操作的情况下返回来消除大量闪烁。
如果您真的坚持更喜欢窗口更新而不是鼠标响应,请使用RDW_UPDATENOW标志将InvalidateRect调用替换为RedrawWindow调用。
编辑:你的观察是有道理的。 WM_SIZING在窗口调整大小之前来,以便您有机会修改大小和/或位置。您可以尝试在WM_NCPAINT处理程序中绘制客户区域,甚至在绘制边框之前。在绘制它之后,您可以验证客户区域以防止它再次绘制。
答案 2 :(得分:1)
手动发布 WM_PAINT 或 WM_SIZE 是个不错的主意。你可以做的一件奇怪的hacky疯狂的事情,就是在 WM_SIZE 的RECT
中记录新调整大小的坐标,使用MoveWindow
来将窗口的大小更改为上一个,然后使用 WM_PAINT 消息中的MoveWindow
再次手动调整 > 之后你已经完成了这幅画。
另一种可能的解决方案是持续用颜色填充窗口,无论屏幕是否调整大小。即
// in the WinMain function
if (GetMessage(&msg,NULL,NULL,0))
{
TranslateMessage(&msg,NULL,NULL);
DispatchMessage(&msg,NULL,NULL);
}
else
{
// fill your window with the color in here
}
或者,当然,您可以将窗口的背景颜色设置为绿色,而不是自己完成所有绘画:
// Before RegisterClass or RegisterClassEx
// wincl is a WNDCLASS or WNDCLASSEX
wincl.hbrBackground = CreateSolidBrush(RGB(50, 238, 50));