问题是:当我抓取Windows应用程序的调整大小边框,特别是顶部或左侧边框,并调整窗口大小时,窗口的内容确实会随着我的拖动而“实时”调整大小,但它们以一种可怕的方式调整大小,即使是最新手用户也会觉得这是一个明显的错误:窗口边缘的内容与我拖动的边缘相反,抖动/闪烁/来回跳跃。根据情况,该现象可能看起来像:
当我停止拖动时,这种丑陋的现象就会停止,但在拖动过程中,它使应用程序看起来既业余又不专业。
毫不夸张地说,这个Windows问题已经让成千上万的应用程序开发人员发疯了。
下面是这一现象的两张示例图片,是罗曼·斯塔科夫(Roman Starkov)为回答一个相关问题而精心准备的:
另一个显示邪恶的“重影”现象的例子(注意快速闪光)来自刘肯尼:
这里是另一个使用任务管理器的示例视频。
问题:任何经历过这个问题的开发人员都会很快发现,至少有30个堆栈溢出问题,有些是最近的,有些是2008年的,其中充满了听起来很有希望但很少奏效的答案。现实情况是,这一问题有很多原因,现有的堆栈溢出问题/答案永远无法说明更广泛的上下文。这个问题旨在回答:
(这是一个规范的Q
这个问题的范围:对于这个问题的范围,这个现象发生在:
不在这个问题的范围内:
>
如果您的应用程序有一个或多个子窗口(子HWND),则此问题中的信息对您很有用(因为我们将描述的导致抖动的BitBlts
与父窗口一起应用于您的子窗口),但是在调整窗口大小的过程中,您还有一个问题需要处理,这超出了这个问题的范围:您需要使所有子窗口自动移动,并与父窗口同步。对于此任务,您可能需要<code>beginderwindowpos/DeferWindowPos/EndDeferWindowPos
这个问题假设如果您的应用程序使用GDI、DirectX或OpenGL绘制窗口,那么您已经在< code>wndproc中实现了一个仅返回1的< code>WM_ERASEBKGND处理程序。< code>WM_ERASEBKGND是Windows 3.1遗留下来的一个神秘的窗口,它出现在< code>WM_PAINT之前,让您的应用程序有机会在您绘制窗口之前“擦除”您窗口的背景...嗯哼。如果您让< code>WM_ERASEBKGND消息进入< code>DefWindowProc(),这将导致您的整个窗口在每次重绘时都被涂成纯色,通常是白色,包括在调整活动窗口大小时发生的重绘。结果是一个丑陋的全窗口闪烁,但不是我们在这个问题中谈论的抖动/闪烁/跳跃类型。拦截< code>WM_ERASEBKGND会立即修复这个问题。
这个问题主要是关于用鼠标拖动窗口边框来实时调整大小。然而,这里写的许多内容也适用于当应用程序使用< code>SetWindowPos()手动进行一次性窗口调整时看到的难看的工件。但是这些不太明显,因为它们只在屏幕上滑动一瞬间,而不是长时间拖动。
这个问题不是关于如何使特定于应用程序的绘图代码运行得更快,即使这样做在许多情况下可能是解决丑陋的大小调整问题的方法。如果你的应用确实需要花费大量时间才能在实时窗口大小调整期间重新显示其内容,请考虑在一般情况下优化绘图代码,或者至少在调整大小期间通过拦截WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE
消息来检测大小调整,从而切换到更快、更低质量的绘图模式。
如果您的应用程序在调整应用程序大小的过程中根本无法调整大小(例如,在调整大小过程中“挂起”,特别是如果它是使用GLFW或其他库的OpenGL),请参阅这些其他问题,这些问题解释了微软在拖动过程中在<code>WM_SYSCOMMAND
通过查看原始资料,您也许能够收集到我遗漏的想法:
2014带2017更新:拖动窗口左边框时无法摆脱抖动:可能是最新的问题但仍然缺乏上下文;建议一个创造性的,但相当疯狂的黑客有两个窗口,并在实时调整大小时交替取消隐藏它们!我发现的唯一一个问题的答案提到了DWM中的竞争条件和< code > DwmGetCompositionTimingInfo()的部分计时修复。
2014为什么每次调整WPF窗口大小时都会出现黑色延迟?:是的,WPF也这样做。没有有用的答案
2009如何修复WPF表单调整大小-控件滞后和黑色背景?:控件滞后和黑色背景?“多HWND示例。提到<code>WM_ERASEBKGND
2018有没有办法在使用WPF时减少或防止表单闪烁?:是的,截至2018年仍未修复。
2018使用SetWindowPos更改窗口左边缘时减少闪烁:未回答的问题得到了许多过时的建议,例如WM_NCCALCSIZE
2012 OpenGL闪烁/损坏,窗口大小调整和DWM激活:良好的问题陈述,回答者完全误解了上下文,并提供了不适用的答案。
2012 如何避免 GUI 调整大小时的瞬时更新?:提到拦截WM_WINDOWPOSCHANGING
和设置 WINDOWPOS.flags |= SWP_NOCOPYBITS
的技巧。
2016 Unity错误报告:“窗口大小调整非常不稳定,断断续续(边框不能平滑地跟随鼠标)”在数百个应用程序中发现的典型错误报告,部分原因是该错误报告中的问题,部分原因是某些应用程序绘制缓慢。我找到的唯一一个文件实际上说Windows 10 DWM夹紧并扩展了旧窗口的外部像素,我可以确认这一点。
2014 从 Windows-8 之前的答案(包括CS_HREDRAW/CS_VREDRAW和WM_NCCALCSIZE
)从左侧调整大小时,
窗口上闪烁。
2013 调整窗口大小导致右边界附近的涂抹,使用老式的Win-7-only解决方案来禁用Aero。
2018无闪烁扩展(resize)窗口向左的一个例子是多窗口(multi-HWND)情况,没有真正的答案。
2013 WinAPI C:重新编程窗口调整大小:问得太模糊,无法判断是客户端区域闪烁(如此问题)还是非客户端区域闪烁。
2018 GLFW错误“在windows 10上调整窗口大小显示跳跃行为”是许多此类错误中的一个,它们从未解释上下文,就像许多StackOverflow帖子一样
2008 “Flicker Free Main Frame Resize” CodeProject,它实际上做了一个 StretchBlt,但在 Windows 8 世界中不起作用,在 Windows 8 世界中,当屏幕上显示不正确的像素时,应用程序无法控制。
2014 Windows中平滑窗口大小调整(使用Direct2D 1.1)?:关于Windows 8 DWM副本的明确但未解决的问题
2010当用户调整我的对话框大小时,如何强制Windows不重绘对话框中的任何内容?:WM_NCCALCSIZE修复禁用在Windows 8中不再工作的bitblt,因为DWM会在应用程序有机会显示之前损坏屏幕。
2014移动/调整窗口时闪烁:在Windows 8中不起作用的先前修复程序的综述。
2007 WinXP时代“减少闪烁”html" target="_blank">代码项目推荐WM_ERASEBKGND SWP_NOCOPYBITS
2008年新Vista DWM问题的早期Google错误报告
StackOverflow中关于平滑调整大小的问题有太多的歧义和不清晰,我们需要建立一个通用的词汇来帮助人们使他们的答案更加清晰。
这就是我们在本节中将要执行的操作。
为了简单起见,我们将解释仅在水平维度上平滑调整大小的问题,但这里的所有内容都同样适用于垂直调整大小。
下面我们将参考一个窗口的
> < li>
“非工作区:”窗口中由Windows管理的部分,包括顶部的标题栏和所有边缘周围的窗口边框,以及
“客户区”:您负责的窗口的主要部分
假设您有一个应用程序:
无论窗口如何调整大小。
你的应用程序可能会绘制L/R本身(例如,在一个窗口中使用GDI/OpenGL/DirectX),或者L/R可能是一些Microsoft控件(它有自己的HWND,与你的主窗口HWND分开);没关系。
下面是应用窗口的工作区的简化表示形式。如您所见,我们在客户区的最左侧有三列范围的LLL,在客户区的最右边有三列范围的RRR,其他各种客户区域内容用“-”表示(请忽略StackOverflow坚持添加的灰色背景;L 和 R 位于客户专区的最左边和右边):
LLL-----------RRR
现在想象一下,您抓住此窗口的左边框或右边框并拖动它以使窗口变大或变小。
想象一下,你的应用程序绘制速度非常快,因此它总是能在1毫秒内响应用户的拖动动作,操作系统让你的应用快速绘制,而不需要在屏幕上绘制任何其他东西来“帮助”你。
拖动应用边框时,用户将在屏幕上看到以下内容(这些数字的每一行代表一个时刻):
向右拖动右边框(放大宽度):
(Figure 1a-1) LLL-----------RRR (initially, when you click the mouse) LLL------------RRR (as you drag the mouse) LLL-------------RRR (as you drag the mouse) LLL--------------RRR (when you release the mouse)
向左拖动右边框(缩小宽度):
(Figure 1a-2) LLL-----------RRR LLL----------RRR LLL---------RRR LLL--------RRR
向左拖动左边框(放大宽度):
(Figure 1a-3) LLL-----------RRR LLL------------RRR LLL-------------RRR LLL--------------RRR
向右拖动左边框(缩小宽度):
(Figure 1a-4) LLL-----------RRR LLL----------RRR LLL---------RRR LLL--------RRR
这些看起来都很好,很光滑:
到目前为止还不错。
现在,想象一下,您的应用程序绘图速度如此之慢,以至于当您用鼠标拖动时,应用程序无法跟上您的步伐。是的,最终,您的绘图会赶上,但我们正在讨论您用手拖动鼠标期间会发生什么。显然,计算机无法伸出手抓住您的手来减慢鼠标移动速度,因此关键问题是:
例如,向右拖动右边框时(放大宽度):
(Figure 1b-1) LLL-----------RRR ?????????????????? (what should show here?) ??????????????????? (what should show here?) LLL--------------RRR (app catches up)
作为另一个示例,当向左拖动左边框时(缩小宽度):
(Figure 1b-2) LLL-----------RRR ???????????????? (what should show here?) ??????????????? (what should show here?) LLL--------RRR (app catches up)
这些是决定运动是否平稳的关键问题,也是整个StackOverflow问题围绕的关键问题。
不同版本的 Windows 在不同的上下文中为这些问题提供了不同的答案,这意味着更平滑地调整大小的解决方案取决于您所处的情况。
在用户开始拖动鼠标以调整窗口大小之后,但在您的应用程序以新大小绘制窗口之前,有多种选择。
屏幕可以保持原样,直到应用程序跟上(无论是客户端像素还是非客户端区域的窗口边框都不会改变):
向右拖动右边框(扩大宽度)的示例:
(Figure 1c1-1) LLL-----------RRR LLL-----------RRR LLL-----------RRR LLL--------------RRR (app catches up)
向左拖动左边框(缩小宽度)时的示例:
(Figure 1c1-2) LLL-----------RRR LLL-----------RRR LLL-----------RRR LLL--------RRR (app catches up)
这种方法的明显缺点是,在所讨论的期间,应用程序似乎“挂起”并且似乎对您的鼠标移动没有反应,因为R、“-”、L和窗口边框都没有移动。
微软经常因为Windows是一个无响应的操作系统而被选中(有时这是他们的错,有时是应用程序开发人员的错),所以自从微软推出live-resize(Windows XP?)以来,微软本身就从未使用过“什么都不做”的方法。
“什么都不做”的方法对用户来说很烦人,看起来不专业,但事实证明(非常不明显),它并不总是最糟糕的选择。继续阅读...
另一种可能性是Windows总是可以让窗口边框立即跟随您的鼠标移动(因为Windows本身有足够的处理能力至少可以及时绘制非客户端区域),并且在等待您的应用程序时,Windows可以获取客户端区域的旧像素并向上或向下缩放这些像素,就像您缩放/放大图像时一样,以便它们“适合”在更小或更大的空间中。
这种技术通常比任何其他技术都差,因为它会导致原始内容的模糊图像,这可能是不成比例的。因此,任何情况下都不应该这样做。但是,正如我们将在第2部分中看到的,有时微软会这样做。
在放大窗口时可以工作的另一种技术是:Windows始终可以使窗口边框立即跟随鼠标移动,并且Windows可以使用一些临时背景色B填充现在更大的工作区的新像素:
例如,向右拖动右边框时(放大宽度):
(Figure 1c3-1) LLL-----------RRR LLL-----------RRRB LLL-----------RRRBB LLL--------------RRR (app catches up)
此方法的优点是,在相关时间段内,至少您的窗口边框正在移动,因此应用程序感觉响应迅速。
另一个不错的功能是,在拖动期间,L保持静止,就像它应该的那样。
有点奇怪的是,你在拖动时创建的新空间被一些随机的颜色填充,更奇怪的是R直到后来才真正移动(请注意,R在最后一刻向右猛击了3列),但至少R只朝着正确的方向移动。这是一个部分改进。
一个巨大而重要的问题是:新填写的背景颜色B应该是什么颜色?如果B碰巧是黑色,而您的应用程序碰巧大部分是白色背景,反之亦然,它将比B匹配您现有内容的背景颜色要丑得多。正如我们将在第2部分中看到的,Windows已经部署了几种不同的策略来改进B的选择。
现在考虑同样的想法,但是将它应用到我们向左拖动左边界(扩大宽度)的情况。
合乎逻辑的做法是在窗口左侧填写新的背景颜色:
(Figure 1c3-2) LLL-----------RRR BLLL-----------RRR BBLLL-----------RRR LLL--------------RRR (app catches up)
这将是合乎逻辑的,因为R将保持不变,就像它应该的那样。L将具有与我们在上面的图1c3-1中描述的相同的奇怪性(L将保持静止,然后在最后一刻突然向左猛拉3列),但至少L只会朝着正确的方向移动。
然而,在你必须处理的几个重要案例中,Windows并没有做合乎逻辑的事情,这将是一个真正的震惊。
相反,Windows有时会填充右侧的背景像素B,即使您拖动的是窗口的左边框:
(Figure 1c3-3) LLL-----------RRR LLL-----------RRRB LLL-----------RRRBB LLL--------------RRR (app catches up)
是的,这太疯狂了。
考虑一下这对用户来说是什么样子的:
>
L似乎在一个方向上以恒定的速度非常平稳地移动,所以这实际上是好的,但是
看看R在做什么:
RRR RRR RRR RRR (app catches up)
这看起来很可怕,可怕,糟糕,恶心,...甚至没有言语来形容这看起来有多糟糕。
人眼对运动非常敏感,即使是在几帧时间内发生的运动。我们的眼睛立即注意到R的这种奇怪的来回运动,我们立即知道有严重的错误。
因此,在这里,您可以开始了解为什么只有在拖动左(或上)边框而不是右(或下)边框时,才会出现一些难看的调整大小问题。
实际上,这两种情况(图1c3-2和图1c3-3)都有奇怪的地方。在图1c3-2中,我们临时添加了一些不属于这里的背景像素B。但是这种怪异的行为比图1c3-3的前后运动要不容易被注意到。
这种来回运动是许多StackOverflow问题所涉及的抖动/闪烁/跳跃。
因此,任何解决平滑调整大小问题的解决方案都必须:
>
至少防止您的客户端区域中的项目出现向一个方向跳转然后返回另一个方向。
理想情况下,如果可能的话,还可以避免添加背景像素B的需要
第1c3节涉及扩大窗口。如果我们看看缩小窗口,我们会看到有一组完全相似的案例。
缩小窗口时可以使用的一种技术如下:窗口总是可以使窗口边界立即跟随鼠标移动,而窗口可以简单地切掉(裁剪)您现在较小的工作区中的一些像素。
例如,将右边框向左拖动时(缩小宽度):
(Figure 1c4-1) LLL-----------RRR LLL-----------RR LLL-----------R LLL--------RRR (app catches up)
使用这种技术,L保持原样,但右边发生了一件奇怪的事情:无论窗口大小如何,R都应该保持齐平,它的右边缘似乎被客户端区域的右边缘逐渐切掉,直到R消失,然后当应用程序赶上时,R突然出现在正确的位置。这很奇怪,但请记住,R在任何时候都不会向右移动。R的左边缘似乎保持静止,直到最后一刻,所有R都向左跳回3列。因此,如图1c3-1所示,R仅在正确方向上移动。
现在考虑一下当我们将左边框向右拖动(缩小宽度)时会发生什么。
合乎逻辑的做法是将像素从工作区的左侧剃掉:
(Figure 1c4-2) LLL-----------RRR LL-----------RRR L-----------RRR LLL--------RRR (app catches up)
这将具有与图 1c4-1 相同的奇怪属性,只是左和右的角色颠倒了。L似乎从L的左边缘逐渐剃掉,但L的右边缘将保持静止,直到最后一刻L似乎向右跳跃。所以L只朝着正确的方向移动,尽管是突然的。
但是——是的,再次做好全面震惊的准备——在你必须处理的几个重要案例中,Windows没有做合乎逻辑的事情。
相反,即使您拖动左侧窗口边框,Windows有时也会从右侧切掉像素:
(Figure 1c4-3) LLL-----------RRR LLL-----------RR LLL-----------R LLL--------RRR (app catches up)
考虑一下这对用户来说是什么样子的:
>
L似乎在一个方向上以恒定的速度非常平稳地移动,所以这实际上是好的,但是
看看R在做什么:
RRR RR R RRR (app catches up)
正如您在阅读第1c3节后现在应该意识到的那样,这种来回运动看起来绝对可怕,并且比图1c4-1和图1c4-2公认的怪异行为要糟糕得多。
到目前为止,我们已经提出了当用户开始拖动窗口边框但应用尚未重绘时该怎么办的单独想法。
这些方法实际上可以结合起来。
暂时,试着从微软的角度来思考这个问题。当用户开始拖动鼠标调整窗口大小时,微软无法提前知道你的应用程序需要多长时间来绘制。因此,微软必须找到一个平衡点:
> < li>
如果你的应用程序要快速响应,那么微软对屏幕所做的任何更改都会使你的应用程序看起来比微软只让你绘制真实内容更糟糕(记住,以上所有技巧在不同程度上都很奇怪,会使你的内容看起来很奇怪,所以不使用这些技巧肯定会更好)。
但是如果微软等待你画图的时间太长,你的应用程序(以及Windows的扩展)将会看起来笨拙而没有反应,正如我们在1c1部分解释的那样。这让微软很没面子即使是你的错。
因此,另一种选择是首先推迟任何屏幕更改,并给应用程序一定数量的时间来绘制,如果应用程序未能满足截止日期,则采用上述方法之一暂时“填补空白”。
你觉得这听起来很可怕吗?猜猜看。这就是Windows所做的,至少以两种不同的方式同时使用两种不同的截止日期时间。第2部分将深入研究这些情况...
注意:您想先阅读第1部分,以使此答案有意义。
此答案不会解决所有调整大小的问题。
它组织了其他帖子中仍然可用的想法,并添加了一些新颖的想法。
微软的MSDN中根本没有记录这些行为,下面的内容是我自己的实验和查看其他StackOverflow文章的结果。
以下问题发生在所有版本的Windows上。它们可以追溯到Windows平台(Windows XP)上实时滚动的第一天,并且仍然存在于Windows 10上。在较新的Windows版本上,其他调整大小的问题可能会在此问题之上,正如我们在下面解释的那样。
以下是与单击窗口边框并拖动该边框的典型会话相关的Windows事件。缩进表示嵌套的wndproc
(嵌套是因为已发送(未发布)消息或因为可怕的Windows模态事件循环在上述问题中的“不在本问题的范围内”中提到):
msg=0xa1 (WM_NCLBUTTONDOWN) [click mouse button on border]
msg=0x112 (WM_SYSCOMMAND) [window resize command: modal event loop]
msg=0x24 (WM_GETMINMAXINFO)
msg=0x24 (WM_GETMINMAXINFO) done
msg=0x231 (WM_ENTERSIZEMOVE) [starting to size/move window]
msg=0x231 (WM_ENTERSIZEMOVE) done
msg=0x2a2 (WM_NCMOUSELEAVE)
msg=0x2a2 (WM_NCMOUSELEAVE) done
loop:
msg=0x214 (WM_SIZING) [mouse dragged]
msg=0x214 (WM_SIZING) done
msg=0x46 (WM_WINDOWPOSCHANGING)
msg=0x24 (WM_GETMINMAXINFO)
msg=0x24 (WM_GETMINMAXINFO) done
msg=0x46 (WM_WINDOWPOSCHANGING) done
msg=0x83 (WM_NCCALCSIZE)
msg=0x83 (WM_NCCALCSIZE) done
msg=0x85 (WM_NCPAINT)
msg=0x85 (WM_NCPAINT) done
msg=0x14 (WM_ERASEBKGND)
msg=0x14 (WM_ERASEBKGND) done
msg=0x47 (WM_WINDOWPOSCHANGED)
msg=0x3 (WM_MOVE)
msg=0x3 (WM_MOVE) done
msg=0x5 (WM_SIZE)
msg=0x5 (WM_SIZE) done
msg=0x47 (WM_WINDOWPOSCHANGED) done
msg=0xf (WM_PAINT) [may or may not come: see below]
msg=0xf (WM_PAINT) done
goto loop;
msg=0x215 (WM_CAPTURECHANGED) [mouse released]
msg=0x215 (WM_CAPTURECHANGED) done
msg=0x46 (WM_WINDOWPOSCHANGING)
msg=0x24 (WM_GETMINMAXINFO)
msg=0x24 (WM_GETMINMAXINFO) done
msg=0x46 (WM_WINDOWPOSCHANGING) done
msg=0x232 (WM_EXITSIZEMOVE)
msg=0x232 (WM_EXITSIZEMOVE) done [finished size/moving window]
msg=0x112 (WM_SYSCOMMAND) done
msg=0xa1 (WM_NCLBUTTONDOWN) done
每次拖动鼠标时,Windows都会为您提供上面循环中显示的一系列消息。最有趣的是,您会收到WM_SIZING
然后是WM_NCCALCSIZE
然后是WM_MOVE/WM_SIZE
,然后您可能会收到WM_PAINT
。
请记住,我们假设您提供了一个返回1的WM_ERASEBKGND
处理程序(请参阅上面问题中的“Not IN SCOPE OF this QUESTION”),因此该消息什么也不做,我们可以忽略它。
在处理这些消息期间(在WM_WINDOWPOSCHANGING
返回后不久),Windows 会对 SetWindowPos()
进行内部调用以实际调整窗口大小。SetWindowPos()
调用首先调整非工作区(例如标题栏和窗口边框)的大小,然后将其注意力转向工作区(您负责的窗口的主要部分)。
在每次拖动消息序列期间,Microsoft会给您一定的时间自行更新客户端区域。
该截止日期的时钟显然在<code>WM_NCCALCSIZE<code>返回后开始滴答作响。在OpenGL窗口中,当您调用<code>SwapBuffers()时),显然满足了截止日期。我不使用GDI或DirectX,因此我不知道对<code>SwapBuffers()来验证,以查看以下行为何时触发。
你有多少时间来完成最后期限?在我的实验中,这个数字似乎在40-60毫秒左右,但考虑到微软惯常的恶作剧,如果这个数字取决于你的硬件配置,甚至是你的应用程序之前的行为,我不会感到惊讶。
如果您确实在截止日期前更新了您的客户端区域,那么Microsoft将使您的客户端区域完好无损。您的用户只会看到您绘制的像素,并且您将拥有最平滑的调整大小。
如果您没有在截止日期前更新您的客户端区域,那么Microsoft将介入并“帮助”您,首先根据“填充一些背景颜色”技术(第1部分的第1c3节)和“切断一些像素”技术(第1部分的第1c4节)。微软向您的用户显示的像素是复杂的:
>
如果您的窗口有一个WNDCLASS.style
,其中包含CS_HREDRAW|CS_VREDRAW
位(您将WNDCLASS结构传递给RealsterClassEx
):
> < li>
令人惊讶的合理的事情发生了。您将得到第1部分的图1c3-1、1c3-2、1c4-1和1c4-2中所示的逻辑行为。当放大客户区时,Windows将在您拖动的窗口的同一侧用“背景色”(见下文)填充新暴露的像素。如果需要的话(左边界和上边界的情况),微软做一个< code>BitBlt来完成这个。当缩小客户区时,微软将在你拖动的窗口的同一侧砍掉像素。这意味着您避免了真正令人讨厌的伪像,它使您的客户区中的对象看起来向一个方向移动,然后又向另一个方向移动。
这可能足以给你一个可以通过的调整大小行为,除非你真的想推动它,看看你是否可以在你有机会画画之前完全阻止Windows骚扰你的客户区域(见下文)。
在这种情况下,不要实现自己的WM_NCCALCSIZE
处理程序,以避免下面描述的错误Windows行为。
如果您的窗口具有<code>WNDCLASS。样式不包括<code>CS_HREDRAW |CS_VREDRAW):
>
Windows试图通过执行<code>BitBlt</code>来“帮助”您,该操作从旧客户端区域复制某个像素矩形,并将该矩形写入新客户端区域的某个位置。此位BLT
为1:1(不缩放像素)。
然后,Windows用“背景色”填充新客户端区域的其他部分(在<code>BitBlt<code>操作期间Windows没有覆盖的部分)
BitBlt
操作通常是调整大小看起来如此糟糕的关键原因。这是因为Windows对应用程序在调整大小后如何重新绘制客户端区域做出了错误的猜测。Windows将内容放置在错误的位置。最终的结果是,当用户第一次看到<code>BitBlt
因此,修复大小调整问题的大多数解决方案都涉及禁用BitBlt
。
如果您实现了一个WM_NCCALCSIZE
处理程序,并且该处理程序在wParam
为1时返回WVR_VALIDRECTS
,您实际上可以控制Windows从旧客户端区域复制哪些像素(BitBlts
)以及Windows将这些像素放置在新客户端区域中的位置。WM_NCCALCSIZE
几乎没有文档记录,但请参阅WVR_VALIDRECTS
和NCCALCSIZE_PARAMS.rgrc[1]和[2]
的MSDN页面中的提示WM_NCCALCSIZE和NCCALCSIZE_PARAMS
。您甚至可以提供NCCALCSIZE_PARAMS.rgrc[1]和[2]
返回值,以完全防止Windows将旧客户端区域的任何像素BitBlting
添加到新客户端区域,或导致Windows从同一位置BitBlt
一个像素,这实际上是同样的事情,因为屏幕上的像素不会被修改。只需将NCCALCSIZE_PARAMS.rgrc[1]和[2]
设置为相同的1像素矩形。结合消除“背景颜色”(见下文),这为您提供了一种方法来防止Windows在您有时间绘制之前骚扰您窗口的像素。
如果你实现了一个WM_NCCALCSIZE
处理程序,并且当wParam
为1时,它返回WVR_VALIDRECTS
以外的任何内容,那么你会得到一个行为(至少在Windows 10上)与MSDN所说的完全不同。Windows似乎忽略了您返回的任何左/右/上/下对齐标志。我建议你不要这样做。特别是流行的StackOverflow文章,当用户调整我的对话框大小时,如何强制窗口不重绘我的对话框中的任何内容?返回WVR_ALIGNLEFT|WVR_ALIGNTOP
,至少在我的Windows 10测试系统上,这似乎已经完全打破了。如果将文章中的代码更改为返回WVR_VALIDRECTS
,则该代码可能会起作用。
如果您没有自己的自定义WM_NCCALCSIZE
处理程序,则可能会得到一个非常无用的行为,最好避免:
>
如果您缩小客户端区域,什么都不会发生(您的应用程序根本没有WM_PAINT
)!如果您使用顶部或左侧边框,您的客户端区域内容将与客户端区域的左上角一起移动。为了在缩小窗口时获得任何实时调整大小,您必须从wndproc
消息中手动绘制,例如WM_SIZE
,或调用InvalidateWindow()
以触发稍后的WM_PAINT
。
如果你扩大客户区
> < li>
如果您拖动窗口的底部或右侧边框,Microsoft会用“背景色”填充新像素(参见下文)
如果拖动窗口的顶部或左侧边框,Microsoft会将现有像素复制到扩展窗口的左上角,并在新打开的空间中留下旧像素的旧垃圾副本
正如你从这个肮脏的故事中看到的,似乎有两种有用的组合:
>
2a1。WNDCLASS.style
withCS_HREDRAW|CS_VREDRAW
为您提供PART 1的图1c3-1、1c3-2、1c4-1和1c4-2中的行为,这不是完美的,但至少您的客户端区域内容不会移动一个方向,然后猛然返回另一个方向
2a2. 没有CS_HREDRAW|CS_VREDRAW
的 WNDCLASS.style
加上一个WM_NCCALCSIZE
处理程序返回 bitBlm
为 1 WVR_VALIDRECTS
,BitBls
什么都没有,再加上禁用“背景色”(见下文),可能会完全禁用 Windows 对客户端区域的骚扰。
显然还有另一种方法可以实现组合2a2的效果。您可以拦截WM_WINDOWPOSCHANGING
(首先将其传递到DefWindowProc
)并设置WINDOWPOS.flags|=SWP_NOCOPYBITS
,这会禁用Windows在窗口调整期间对SetWindowPos()
的内部调用中的BitBlt
。我自己没有尝试过这个技巧,但许多SO用户报告说它有效。
在上面的几点,我们提到了“背景颜色”。该颜色由<code>WNDCLASS决定。hbrBackground字段,您传递给RegisterClassEx
。此字段包含一个<code>HBRUSH<code>对象。大多数人使用以下样板代码进行设置:
wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
COLOR_WINDOW1
咒语为您提供白色背景颜色。有关1的解释,请参阅WNDCLASS的MSDN dox,并注意StackOverflow和MS论坛上有很多关于1的错误信息。
你可以像这样选择自己的颜色:
wndclass.hbrBackground = CreateSolidBrush(RGB(255,200,122));
您还可以使用以下方法禁用背景填充:
wndclass.hbrBackground = NULL;
这是上述组合2a2的另一个关键成分。但请注意,在你的应用赶上并绘制新的工作区像素之前,新发现的像素将呈现一些本质上随机的颜色或图案(无论图形帧缓冲器中有什么垃圾),因此实际上最好使用组合 2a1 并选择与应用一起使用的背景色。
在Aero开发过程中的某个时刻,微软在上述全Windows版本问题的基础上添加了另一个实时调整大小抖动问题。
阅读之前的StackOverflow文章,实际上很难判断这个问题是何时引入的,但我们可以说:
问题围绕着微软在Windows Vista中引入的架构重大变化,称为DWM桌面组合。应用程序不再直接绘制到图形帧缓冲区。相反,所有应用程序实际上都在绘制到一个屏幕外的帧缓冲区中,然后通过新的、邪恶的Windows桌面窗口管理器(DWM)进程将其与其他应用程序的输出组合在一起。
所以,因为在显示你的像素时涉及到另一个过程,所以有另一个机会来弄乱你的像素。
微软永远不会错过这样的机会。
以下是DWM Compostion显然会发生的事情:
>
用户在窗口边框上单击鼠标并开始拖动鼠标
每次用户拖动鼠标时,都会触发我们在上面第2a节中描述的应用程序中的wndproc
事件序列。
但是,与此同时,DWM(记住是一个单独的进程,它异步运行到您的应用程序)启动自己的截止日期计时器。
与上面的2a部分类似,计时器显然在< code>WM_NCCALCSIZE返回后开始计时,并在您的应用程序绘制和调用< code>SwapBuffers()时得到满足。
如果您在截止日期前更新了您的客户区,那么DWM将使您的客户区不受干扰。您的客户区仍有可能受到第2a节中的问题的骚扰,因此请务必阅读第2a节。
如果您没有在截止日期前更新您的客户端区域,那么Microsoft将做一些非常可怕和令人难以置信的糟糕的事情(Microsoft没有吸取教训吗?)
--------------AAA----------------- | | B C B C B C | | --------------DDD-----------------
--------------AAA----------------------------------------------- | | | B C | B C | B CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC | |CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC --------------DDD-----------------CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC | DDDDDDDDD | | DDDDDDDDD | | DDDDDDDDD | | DDDDDDDDD | | DDDDDDDDD | ------------------------------DDDDDDDDD-------------------------
WNDCLASS.hbrBackground
中)会更好,但我怀疑DWM可能无法访问该信息,因为DWM处于不同的进程中,因此遭到黑客攻击。叹息。但我们甚至还没有达到最糟糕的部分:
这就是为什么,如果您在Windows 10上启动Windows资源管理器并拖动左侧边框,您很可能会看到右侧滚动条不稳定地抖动/闪烁/跳跃,就好像Windows是由四年级学生编写的一样。
我不敢相信微软已经发布了这样的代码,并认为它“完成了”。也有可能负责的代码在图形驱动程序中(例如Nvidia,Intel,...),但是一些StackOverflow帖子使我相信这种行为是跨设备的。
在使用左侧或顶部窗口边框调整大小时,您无法防止这层无能产生可怕的抖动/闪烁/跳跃。这是因为对您的客户区的粗鲁,未经同意的修改正在另一个过程中发生。
我真的希望一些StackOverflow用户能在Windows 10中提出一些神奇的DWM设置或标志,我们可以使其延长截止日期或完全禁用可怕的行为。
但与此同时,我确实想出了一个方法,在窗口调整期间,它在一定程度上减少了可怕的来回工件的频率。
黑客,灵感来自https://stackoverflow.com/a/25364123/1046167,是尽最大努力使应用程序进程与驱动DWM活动的垂直回溯同步。实际上,在Windows中实现这一点并不简单。此破解的代码应该是WM_NCCALCSIZE
处理程序中的最后一个代码:
LARGE_INTEGER freq, now0, now1, now2;
QueryPerformanceFrequency(&freq); // hz
// this absurd code makes Sleep() more accurate
// - without it, Sleep() is not even +-10ms accurate
// - with it, Sleep is around +-1.5 ms accurate
TIMECAPS tc;
MMRESULT mmerr;
MMC(timeGetDevCaps(&tc, sizeof(tc)), {});
int ms_granularity = tc.wPeriodMin;
timeBeginPeriod(ms_granularity); // begin accurate Sleep() !
QueryPerformanceCounter(&now0);
// ask DWM where the vertical blank falls
DWM_TIMING_INFO dti;
memset(&dti, 0, sizeof(dti));
dti.cbSize = sizeof(dti);
HRESULT hrerr;
HRC(DwmGetCompositionTimingInfo(NULL, &dti), {});
QueryPerformanceCounter(&now1);
// - DWM told us about SOME vertical blank
// - past or future, possibly many frames away
// - convert that into the NEXT vertical blank
__int64 period = (__int64)dti.qpcRefreshPeriod;
__int64 dt = (__int64)dti.qpcVBlank - (__int64)now1.QuadPart;
__int64 w, m;
if (dt >= 0)
{
w = dt / period;
}
else // dt < 0
{
// reach back to previous period
// - so m represents consistent position within phase
w = -1 + dt / period;
}
// uncomment this to see worst-case behavior
// dt += (sint_64_t)(0.5 * period);
m = dt - (period * w);
assert(m >= 0);
assert(m < period);
double m_ms = 1000.0 * m / (double)freq.QuadPart;
Sleep((int)round(m_ms));
timeEndPeriod(ms_granularity);
您可以通过尝试将绘图安排在帧中间而不是垂直同步来取消注释显示“最坏情况”行为的行,并注意您还有多少工件来说服自己。您还可以尝试慢慢改变该行中的偏移量,您将看到工件在大约90%的时间段内突然消失(但不是完全消失),并在大约5-10%的时间段内再次出现。
由于Windows不是一个实时操作系统,您的应用程序可能会在此代码中的任何位置被抢占,从而导致now1
和dti.qpcVBlank
。这个小代码段中的抢占是罕见的,但也是可能的。如果需要,可以比较now0
和now1
,如果边界不够紧,则再次循环。抢占还可以中断Sleep()
或Sleep()之前或之后的代码的计时。对此您无能为力,但这部分代码中的计时错误被DWM的不确定性行为所淹没;即使您的时间非常完美,您仍然会得到一些窗口调整工件。这只是一个启发。
还有第二个技巧,这是一个非常有创意的技巧:正如StackOverflow文章中所解释的,拖动窗口的左边框时无法摆脱抖动,你实际上可以在你的应用程序中创建两个主窗口,每次windows执行<code>SetWindowPos</code>时,你都会接受这一点,而是隐藏一个窗口,显示另一个窗口!我还没有尝试过,但OP报告说它绕过了上面描述的疯狂像素DWM像素复制。
还有第三个技巧,根据您的应用程序(特别是与上面的定时黑客结合使用),它可能会起作用。在实时调整大小期间(您可以通过拦截
WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE
来检测),您可以修改绘图代码,以最初绘制一些更简单的内容,这些内容更有可能在问题2a和2b规定的截止日期内完成,并调用SwapBuffers()
来领取您的奖品:这将足以防止Windows执行第2a和2b节中描述的不良blit/fill。然后,在部分绘制之后,立即执行另一个完全更新窗口内容的绘制,并再次调用SwapBuffers()。
这可能看起来仍然有些奇怪,因为用户将分两部分看到您的窗口更新,但它可能比Windows中可怕的来回运动工件看起来好得多。
还有一点很诱人:Windows 10中的一些应用程序,包括控制台(start
cmd.exe
),即使拖动左边框,也没有DWM合成工件。所以有一些方法可以绕过这个问题。让我们找到它!
当您试图解决特定的调整大小问题时,您可能会想知道您看到的是第2a节和第2b节中的哪些重叠效果。
将它们分开的一种方法是在Windows 7上进行调试(禁用Aero,只是为了安全起见)。
另一种快速识别第2b节中是否存在问题的方法是修改应用程序以显示第2b节所述的测试模式,如以下示例(请注意四条边缘上的每一条1像素细彩色线):
然后抓取任何窗口边框并开始快速调整边框大小。如果您看到间歇性的巨大彩色条(在本测试模式中为蓝色或绿色条,因为底部边缘为蓝色,右侧边缘为绿色),则您知道您在第2b节中看到了问题。
您可以通过设置<code>WNDCLASS来测试是否在第2a节中看到问题。hbrBackground
设置为不同的背景颜色,如红色。当您调整窗口大小时,新暴露的部分将显示为该颜色。但请通读第2a节,以确保消息处理程序不会导致Windows对整个客户端区域进行BitBlt
处理,这会导致Windows不绘制任何背景颜色。
请记住,第2a节和第2b节中的问题只有在您的应用程序未能在某个截止日期之前绘制时才会出现,并且每个问题都有不同的截止日期。
因此,在没有修改的情况下,您的应用程序可能只显示2b部分的问题,但如果您修改您的应用程序以绘制得更慢(例如,在< code>SwapBuffers()之前插入< code > Sleep() in < code > WM _ PAINT ),您可能会错过2a部分和2b部分的最后期限,并开始同时看到这两个问题。
当您在较慢的< code >调试版本和< code >发布版本之间更改应用程序时,也可能会发生这种情况,这可能会使处理这些调整大小问题变得非常令人沮丧。了解引擎盖下发生的事情可以帮助你处理令人困惑的结果。
我是Flink的新手,需要方法的帮助。我有时间颗粒度为5分钟的事件流。我想通过调用rest API来获取事件的元数据,其中包含过去1小时数据点的历史事件,即过去12点(5分钟时间颗粒度)。 e、 g事件的时间戳为10:00、10:05、10:10、10:15等,因此如果我想获取时间戳为11:00的事件元数据,我将调用send发送所有时间戳为10:00、10:05、10:10、10:15的事件。。1
作业并行性(4,8,16):[自动生成源]-->[Map1]-->[滚动窗口(10s)]-->[Map2]-->[接收器] Flink窗口性能eps 4p、8p、16p 作业以上的性能最高达到了每秒50k+-左右,不管我如何将集群缩放成4-16的并行度。 闪烁性能无窗口4p、8p 我已经删除了窗口的逻辑,以消除瓶颈性能的应用程序逻辑,但似乎窗口仍然导致我的整个流性能下降,即使该窗口只是一个通过函数
我有一个wxWidgets应用程序,它有许多子opengl窗口。我使用我自己的GL画布类,而不是wx类。窗口共享它们的OpenGL上下文。我不认为它是wxwidgets的事实在这里真的很重要。 opengl窗口是一个窗口的子窗口,它们是彼此的兄弟姐妹,包含在一个选项卡控件中。有点像MDI风格的界面,但它不是MDI窗口...每个都可以单独调整大小。除非启用Aero并且DWM处于活动状态,否则所有工作
我目前正在编写一个具有LineChart的程序,在这个问题的帮助下,我有条件地对其背景进行了着色。当我调整我的JavaFX程序所在的窗口大小时,颜色会扭曲整个地方。 如你所见,颜色从来没有被“清理”过。下面是我绘制多边形和linechart的代码: (代码被清除了一点,我排除了一些我认为不相关的内容)
在tkinter中调整窗口大小时,有没有办法动态调整小部件的大小? 这是代码: 当我调整窗口大小时,窗口小部件的宽度和高度保持不变。 我想要的是根据窗口的大小动态调整窗口小部件的大小。 有没有办法在tkinter中实现这一点? 如果有人能帮我,那就太好了。
我正在处理一个应用程序,它以透明的锚窗格开始(没有标题栏和圆角)。我想要能够拖动和移动窗口周围。我已经让它工作了,但是当我点击它时,窗口会向上弹到你从中心拖动的地方,而不是你点击的地方。 CSS: main.java: 控制器: 我有一种感觉,我需要说明光标在哪里,但我不确定如何。我是正确的还是我遗漏了一些更容易的东西?