简体   繁体   English

如何在调整窗口大小时平滑丑陋的抖动/闪烁/跳跃,尤其是拖动左/上边框(Win 7-10;bg、bitblt 和 DWM)?

[英]How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)?

THE PROBLEM: When I grab the resize border of my Windows app, especially the top or left borders, and resize the window, the contents of the window do resize "live" as I drag, but they resize in a hideous manner that looks like a blatant bug to even the most novice user: the contents at the opposite edge of the window from the edge I am dragging jitter/flicker/jump back and forth wildly.问题:当我抓住我的 Windows 应用程序的调整大小边框,尤其是顶部或左边框,并调整窗口大小时,窗口的内容会在我拖动时“实时”调整大小,但它们以一种可怕的方式调整大小,看起来像即使是最新手的用户也有一个明显的错误:窗口对面边缘的内容我正在疯狂地前后拖动抖动/闪烁/跳跃。 Depending on the situation, the phenomenon may look like:根据情况,该现象可能如下所示:

  • contents that seem to walk off the edge of the window and snap back when we slow down or stop dragging当我们放慢或停止拖动时,内容似乎会离开窗口边缘并弹回
  • contents that seem to pull into the window, intermittently displaced by a border of varying colors, often black or white似乎被拉进窗口的内容,被不同颜色的边框间歇性地取代,通常是黑色或白色
  • a seriously ugly "double image" with two overlapping copies of the content displaced by a distance proportional to how much/how fast we are dragging一个非常丑陋的“双重图像”,内容的两个重叠副本被移动的距离与我们拖动的速度/速度成正比

The ugly phenomenon stops as soon as I stop dragging, but during the dragging it makes the app look amateurish and unprofessional.一旦我停止拖动,丑陋的现象就会停止,但在拖动过程中它使应用程序看起来很业余和不专业。

It is not an understatement to say this Windows problem has driven thousands of app developers crazy .毫不夸张地说,这个 Windows 问题已经让数以千计的应用程序开发人员抓狂了

Here are two example pictures of the phenomenon, kindly prepared for a related question by Roman Starkov :以下是该现象的两张示例图片,为Roman Starkov提出的相关问题精心准备

Jitter:抖动:
示例 1:抖动

Border:边界:
示例 2:边框

Another example showing the evil "double image" phenomenon (note the quick flash) from Kenny Liu :另一个例子显示了Kenny Liu的邪恶“双重图像”现象(注意快速闪光):

示例 2:双重图像

Another example video of the phenomenon with Task Manager is here .任务管理器现象的另一个示例视频在这里

THE QUESTION: Any developer who has experienced this problem quickly finds that there are at least 30 Stack Overflow questions, some recent and some dating from 2008, full of promising-sounding answers that rarely work.问题:任何遇到此问题的开发人员都会很快发现,至少有 30 个 Stack Overflow 问题,有些是最近的,有些是 2008 年的,其中充满了听起来很有希望但很少奏效的答案。 The reality is that this one problem has many causes , and the existing Stack Overflow questions/answers never make the wider context clear.现实情况是,这个问题有很多原因,现有的 Stack Overflow 问题/答案从未使更广泛的上下文变得清晰。 This question seeks to answer:这个问题试图回答:

  • what are the most likely causes of this kind of ugly jitter/flicker/jumping?这种丑陋的抖动/闪烁/跳跃的最可能原因是什么?
  • how do I tell which cause I am seeing?我怎么知道我看到的是哪个原因?
  • is this cause specific to particular graphics drivers or general for Windows?这是特定于特定图形驱动程序的原因还是 Windows 的一般原因?
  • how do I fix each cause?我如何解决每个原因? can an app fix it?一个应用程序可以修复它吗?

(This is meant as a canonical Q&A to explain all the different causes of window resize jitter so that users can identify which of the causes is causing their problem and solve it. As the answers explain, all the permutations above (native/managed, window/dialog, XP-10) boil down to only two root causes, but identifying which you have is the tricky part.) (这是一个规范的问答,用于解释窗口调整大小抖动的所有不同原因,以便用户可以确定是哪个原因导致了他们的问题并解决了它。正如答案所解释的,上面的所有排列(本机/托管,窗口/dialog, XP-10) 归结为只有两个根本原因,但确定您拥有的是哪个是棘手的部分。)

SCOPE OF THIS QUESTION: For the scope of this question, the phenomenon happens with:这个问题的范围对于这个问题的范围,这种现象发生在:

  • both native Win32 and managed .NET/WPF/Windows Forms apps本机 Win32 和托管 .NET/WPF/Windows 窗体应用程序
  • both normal Win32 windows and Win32 Dialog windows普通的 Win32 窗口和 Win32 对话框窗口
  • Windows versions including XP, Vista, 7, 8, and 10 (but see below for the dark truth of multiple causes) Windows 版本,包括 XP、Vista、7、8 和 10(但请参阅下文了解多种原因的黑暗真相)

NOT IN SCOPE OF THIS QUESTION:不在这个问题的范围内:

  • If your app has one or more child windows (child HWNDs), the info in this question is useful to you (since the jerk-causing BitBlts we will describe are applied to your child windows along with the parent window), but during window resize you have an additional problem to handle that is beyond the scope of this question: you need to make all your child windows move atomically and in sync with the parent window.如果您的应用程序有一个或多个子窗口(子 HWND),则此问题中的信息对您很有用(因为我们将描述的引起混蛋的BitBlts与父窗口一起应用于您的子窗口),但在窗口调整大小期间您还有一个额外的问题需要处理,这超出了本问题的范围:您需要让所有子窗口自动移动并与父窗口同步。 For this task, you will probably want BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos and you can find out about them here and here .对于此任务,您可能需要BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos ,您可以在此处此处找到有关它们的信息

  • This question assumes that if your app draws to a window using GDI, DirectX, or OpenGL, then you have already implemented a WM_ERASEBKGND handler in your wndproc that simply returns 1. WM_ERASEBKGND is an arcane Windows remnant from Windows 3.1 that comes before WM_PAINT to give your app a chance to "erase the background" of your window before you draw your window...uh huh.这个问题假设如果你的应用程序使用 GDI、DirectX 或 OpenGL 绘制到一个窗口,那么你已经在wndproc中实现了一个WM_ERASEBKGND处理程序, wndproc返回WM_ERASEBKGND是 Windows 3.1 的神秘 Windows 残余,在WM_PAINT之前给出在您绘制窗口之前,您的应用程序有机会“擦除窗口的背景”......嗯。 If you let the WM_ERASEBKGND message go into DefWindowProc() , that will cause your entire window to get painted a solid color, usually white, on each redraw, including redraws that happen during live window resizing.如果您让WM_ERASEBKGND消息进入DefWindowProc() ,这将导致您的整个窗口在每次重绘时都被绘制为纯色,通常为白色,包括在实时窗口调整大小期间发生的重绘。 The result is an ugly full-window flicker that is gross, but not the type of jitter/flicker/jumping we are talking about in this question.结果是丑陋的全窗口闪烁,但不是我们在这个问题中谈论的抖动/闪烁/跳跃类型。 Intercepting WM_ERASEBKGND fixes this problem immediately.拦截WM_ERASEBKGND立即修复了这个问题。

  • This question is primarily about live-resize by dragging window borders with the mouse.这个问题主要是关于通过用鼠标拖动窗口边框来实时调整大小。 However, much of what is written here also applies to ugly artifacts you can see when an app manually does a one-time window resize using SetWindowPos() .然而,这里写的大部分内容也适用于当应用程序使用SetWindowPos()手动调整一次性窗口大小时您可以看到的丑陋工件。 These are less visible though because they only flick on the screen for one instant, rather than over a long period of dragging.但它们不太明显,因为它们只在屏幕上轻弹一瞬间,而不是长时间拖动。

  • This question is not about how to make your app-specific drawing code go faster, even though doing so may be a solution to the ugly resizing problem in many cases.这个问题不是关于如何让你的特定于应用程序的绘图代码运行得更快,尽管这样做在许多情况下可能是解决丑陋的调整大小问题的方法。 If your app really does take huge amounts of time to redisplay its contents during live window resize, consider optimizing your drawing code in general or at least switching to a faster, lower-quality drawing mode during resize by intercepting the WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE messages to detect resize.如果您的应用程序在实时窗口调整大小期间确实需要大量时间来重新显示其内容,请考虑优化您的绘图代码,或者至少在调整大小期间通过拦截WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE消息来切换到更快、质量较低的绘图模式以进行检测调整大小。

  • If your app fails to resize at all during app resizing (eg it "hangs" during resizing, especially if it is OpenGL using GLFW or other library), see these other questions which explain about Microsoft's hideous nested/modal event loop inside WM_SYSCOMMAND during dragging: here especially this good answer , here , here , here , and here .如果您的应用程序在调整大小期间根本无法调整大小(例如,在调整大小期间“挂起”,特别是如果它是使用 GLFW 或其他库的 OpenGL),请参阅这些其他问题,这些问题解释了在拖动期间 Microsoft 在WM_SYSCOMMAND内的可怕的嵌套/模态事件循环: 这里特别是这个很好的答案这里这里这里这里

PART 2: Identifying and Fixing Windows Resize Problems第 2 部分:识别和修复 Windows 调整大小问题

Note: you want to read PART 1 first for this answer to make sense.注意:您想先阅读第 1 部分,以使此答案有意义。

This answer will not solve all your resizing problems.这个答案不会解决您所有的调整大小问题。

It organizes the still-usable ideas from other posts and adds a few novel ideas.它组织了来自其他帖子的仍然可用的想法,并添加了一些新颖的想法。

None of this behavior is at all documented on Microsoft's MSDN, and what follows below is the result of my own experimentation and looking at other StackOverflow posts.微软的 MSDN 上根本没有记录这种行为,下面是我自己的实验和查看其他 StackOverflow 帖子的结果。

2a. 2a. Resize Problems from SetWindowPos() BitBlt and Background Fill来自SetWindowPos() BitBlt和背景填充的调整大小问题

The following problems happen on all versions of Windows .以下问题发生在所有版本的 Windows 上 They date back to the very first days of live-scrolling on the Windows platform (Windows XP) and are still present on Windows 10. On more recent Windows versions, other resize problems may layer on top of this problem, as we explain below.它们可以追溯到 Windows 平台 (Windows XP) 上实时滚动的最初几天,并且仍然存在于 Windows 10 上。在较新的 Windows 版本中,其他调整大小问题可能会叠加在此问题之上,如下文所述。

Here are the Windows events associated with a typical session of clicking a window border and dragging that border.以下是与单击窗口边框并拖动该边框的典型会话相关联的 Windows 事件。 Indentation indicates nested wndproc (nested because of sent (not posted) messages or because of the hideous Windows modal event loop mentioned in "NOT IN SCOPE OF THIS QUESTION" in the question above):缩进表示嵌套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

Each time you drag the mouse, Windows gives you the series of messages shown in the loop above.每次拖动鼠标时,Windows 都会为您提供上面循环中显示的一系列消息。 Most interestingly, you get WM_SIZING then WM_NCCALCSIZE then WM_MOVE/WM_SIZE , then you may (more on that below) receive WM_PAINT .最有趣的是,你得到WM_SIZING然后WM_NCCALCSIZE然后WM_MOVE/WM_SIZE ,那么你可能(低于更多)收到WM_PAINT

Remember we assume you have provided a WM_ERASEBKGND handler that returns 1 (see "NOT IN SCOPE OF THIS QUESTION" in the question above) so that message does nothing and we can ignore it.请记住,我们假设您提供了一个返回 1 的WM_ERASEBKGND处理程序(请参阅上面问题中的“不在此问题范围内”),因此该消息不执行任何操作,我们可以忽略它。

During the processing of those messages (shortly after WM_WINDOWPOSCHANGING returns), Windows makes an internal call to SetWindowPos() to actually resize the window.在处理这些消息期间(在WM_WINDOWPOSCHANGING返回后不久),Windows 对SetWindowPos()进行内部调用以实际调整窗口大小。 That SetWindowPos() call first resizes the non-client area (eg the title bars and window border) then turns its attention to the client area (the main part of the window that you are responsible for).SetWindowPos()调用首先调整非客户区(例如标题栏和窗口边框)的大小,然后将注意力转向客户区(您负责的窗口的主要部分)。

During each sequence of messages from one drag, Microsoft gives you a certain amount of time to update the client area by yourself.在一次拖动的每个消息序列中,Microsoft 都会给您一定的时间来自行更新客户区。

The clock for this deadline apparently starts ticking after WM_NCCALCSIZE returns.WM_NCCALCSIZE返回后,这个截止日期的时钟显然开始滴答作响。 In the case of OpenGL windows, the deadline is apparently satisfied when you call SwapBuffers() to present a new buffer (not when your WM_PAINT is entered or returns).在 OpenGL 窗口的情况下,当您调用SwapBuffers()以呈现新缓冲区时(而不是在输入或返回WM_PAINTSwapBuffers() ,截止日期显然已满足。 I do not use GDI or DirectX, so I don't know what the equavalent call to SwapBuffers() is, but you can probably make a good guess and you can verify by inserting Sleep(1000) at various points in your code to see when the behaviors below get triggered.我不使用 GDI 或 DirectX,所以我不知道对SwapBuffers()的等效调用是什么,但您可能会做出很好的猜测,您可以通过在代码中的各个点插入Sleep(1000)来验证当以下行为被触发时。

How much time do you have to meet your deadline?你有多少时间来满足你的截止日期? The number seems to be around 40-60 milliseconds by my experiments, but given the kinds of shenanigans Microsoft routinely pulls, I wouldn't be surprised if the number depends on your hardware config or even your app's previous behavior.根据我的实验,这个数字似乎在 40-60 毫秒左右,但考虑到 Microsoft 经常使用的各种恶作剧,如果这个数字取决于您的硬件配置甚至您的应用程序以前的行为,我不会感到惊讶。

If you do update your client area by the deadline, then Microsoft will leave your client area beautifully unmolested.如果您确实在截止日期前更新了您的客户区,那么 Microsoft 将使您的客户区保持完好无损。 Your user will only see the pixels that you draw, and you will have the smoothest possible resizing.您的用户只会看到您绘制的像素,并且您将获得尽可能平滑的调整大小。

If you do not update your client area by the deadline, then Microsoft will step in and "help" you by first showing some other pixels to your user, based on a combination of the "Fill in Some Background Color" technique (Section 1c3 of PART 1 ) and the "Cut off some Pixels" technique (Section 1c4 of PART 1 ).如果您没有在截止日期前更新您的客户区,那么 Microsoft 将介入并“帮助”您,首先向您的用户显示一些其他像素,基于“填充某些背景颜色”技术的组合(第 1c3 节)第 1 部分)和“切掉一些像素”技术(第 1部分的第 1c4 节)。 Exactly what pixels Microsoft shows your user is, well, complicated: Microsoft 向您的用户显示的确切像素是复杂的:

  • If your window has a WNDCLASS.style that includes the CS_HREDRAW|CS_VREDRAW bits (you pass the WNDCLASS structure to RegisterClassEx ):如果您的窗口的WNDCLASS.style包含CS_HREDRAW|CS_VREDRAW位(您将WNDCLASS结构传递给RegisterClassEx ):

    • Something surprisingly reasonable happens.一些令人惊讶的合理的事情发生了。 You get the logical behavior shown in Figures 1c3-1, 1c3-2, 1c4-1, and 1c4-2 of PART 1 .您将获得第 1 部分的图 1c3-1、1c3-2、1c4-1 和 1c4-2 中所示的逻辑行为。 When enlarging the client area, Windows will fill in newly exposed pixels with the "background color" (see below) on the same side of the window you are dragging.扩大客户区时,Windows 将在您拖动的窗口的同一侧用“背景颜色”(见下文)填充新暴露的像素。 If needed (left and top border cases), Microsoft does a BitBlt to accomplish this.如果需要(左边框和上边框),Microsoft 会使用BitBlt来完成此操作。 When shrinking the client area, Microsoft will chop off pixels on the same side of the window you are dragging.缩小客户区时,Microsoft 将在您拖动的窗口的同一侧切掉像素。 This means you avoid the truly heinous artifact that makes objects in your client area appear to move in one direction then move back in the other direction.这意味着您避免了真正令人发指的工件,它使您的客户区中的对象看起来朝一个方向移动,然后又朝另一个方向移动。

    • This may be good enough to give you passable resize behavior, unless you really want to push it and see if you can totally prevent Windows from molesting your client area before you have a chance to draw (see below).这可能足以为您提供可通过的调整大小行为,除非您真的想推动它并查看是否可以在您有机会绘制之前完全防止 Windows 骚扰您的客户区(见下文)。

    • Do not implement your own WM_NCCALCSIZE handler in this case, to avoid buggy Windows behavior described below.在这种情况下,不要实现您自己的WM_NCCALCSIZE处理程序,以避免下面描述的错误的 Windows 行为。

  • If your window has a WNDCLASS.style that does not include the CS_HREDRAW|CS_VREDRAW bits (including Dialogs, where Windows does not let you set WNDCLASS.style ):如果您的窗口的WNDCLASS.style不包括CS_HREDRAW|CS_VREDRAW位(包括对话框,Windows 不允许您设置WNDCLASS.style ):

    • Windows tries to "help" you by doing a BitBlt that makes a copy of a certain rectangle of pixels from your old client area and writes that rectangle to a certain place in your new client area. Windows 尝试通过执行BitBlt来“帮助”您,该BitBlt从旧客户区复制某个像素矩形并将该矩形写入新客户区中的某个位置。 This BitBlt is 1:1 (it does not scale or zoom your pixels).这个BitBlt是 1:1(它不会缩放或缩放你的像素)。

    • Then, Windows fills in the other parts of the new client area (the parts that Windows did not overwrite during the BitBlt operation) with the "background color."然后,Windows 用“背景色”填充新客户区的其他部分(Windows 在BitBlt操作期间没有覆盖的部分)。

    • The BitBlt operation is often the key reason why resize looks so bad. BitBlt操作通常是调整大小看起来如此糟糕的关键原因。 This is because Windows makes a bad guess about how your app is going to redraw the client area after the resize.这是因为 Windows 对您的应用程序在调整大小后将如何重绘客户区做出了错误的猜测。 Windows places your content in the wrong location. Windows 将您的内容放在错误的位置。 The net result is that when the user first sees the BitBlt pixels and then sees the real pixels drawn by your code, your content appears to first move in one direction, then jerk back in the other direction.最终结果是,当用户首先看到BitBlt像素,然后看到由您的代码绘制的真实像素时,您的内容似乎首先向一个方向移动,然后向另一个方向猛拉。 As we explained in PART 1 , this creates the most hideous type of resize artifact.正如我们在第 1 部分中解释的那样,这会创建最可怕的调整大小工件类型。

    • So, most solutions for fixing resize problems involve disabling the BitBlt .因此,修复调整大小问题的大多数解决方案都涉及禁用BitBlt

    • If you implement a WM_NCCALCSIZE handler and that handler returns WVR_VALIDRECTS when wParam is 1, you can actually control which pixels Windows copies ( BitBlts ) from the old client area and where Windows places those pixels in the new client area.如果您实现WM_NCCALCSIZE处理程序并且该处理程序在wParam为 1 时返回WVR_VALIDRECTS ,则您实际上可以控制 Windows 从旧客户区复制哪些像素 ( BitBlts ) 以及 Windows 在新客户区中放置这些像素的位置。 WM_NCCALCSIZE is just barely documented, but see the hints about WVR_VALIDRECTS and NCCALCSIZE_PARAMS.rgrc[1] and [2] in the MSDN pages for WM_NCCALCSIZE and NCCALCSIZE_PARAMS . WM_NCCALCSIZE只是勉强记载,但看到有关提示WVR_VALIDRECTSNCCALCSIZE_PARAMS.rgrc[1] and [2]在MSDN页WM_NCCALCSIZENCCALCSIZE_PARAMS You can even provide NCCALCSIZE_PARAMS.rgrc[1] and [2] return values that completely prevent Windows from BitBlting any of the pixels of the old client area to the new client area, or cause Windows to BitBlt one pixel from and to the same location, which is effectively the same thing since no on-screen pixels would get modified.您甚至可以提供NCCALCSIZE_PARAMS.rgrc[1] and [2]返回值,以完全阻止 Windows 将旧客户区的任何像素BitBlting到新客户区,或导致 Windows 从和到同一位置BitBlt一个像素,这实际上是同一件事,因为屏幕上的像素不会被修改。 Just set both NCCALCSIZE_PARAMS.rgrc[1] and [2] to the same 1-pixel rectangle.只需将NCCALCSIZE_PARAMS.rgrc[1] and [2]设置为相同的 1 像素矩形。 In combination with eliminating the "background color" (see below), this gives you a way to prevent Windows from molesting your window's pixels before you have time to draw them.与消除“背景颜色”(见下文)相结合,这为您提供了一种方法,可以在您有时间绘制窗口像素之前防止 Windows 骚扰您的窗口像素。

    • If you implement a WM_NCCALCSIZE handler and it returns anything other than WVR_VALIDRECTS when wParam is 1, then you get a behavior which (at least on Windows 10) does not at all resemble what MSDN says.如果您实现了WM_NCCALCSIZE处理程序,并且当wParam为 1 时它返回WVR_VALIDRECTS以外的任何内容,那么您会得到一种(至少在 Windows 10 上)与 MSDN 所说的完全不同的行为。 Windows seems to ignore whatever left/right/top/bottom alignment flags you return. Windows 似乎忽略了您返回的任何左/右/上/下对齐标志。 I advise you do not do this.我建议你不要这样做。 In particular the popular StackOverflow article How do I force windows NOT to redraw anything in my dialog when the user is resizing my dialog?特别是流行的 StackOverflow 文章如何在用户调整对话框大小时强制窗口不重绘对话框中的任何内容? returns WVR_ALIGNLEFT|WVR_ALIGNTOP and this appears to be completely broken now at least on my Windows 10 test system.返回WVR_ALIGNLEFT|WVR_ALIGNTOP并且至少在我的 Windows 10 测试系统上现在这似乎完全被破坏了。 The code in that article might work if it is changed to return WVR_VALIDRECTS instead.如果更改为返回WVR_VALIDRECTS ,那篇文章中的代码可能会起作用。

    • If you do not have your own custom WM_NCCALCSIZE handler, you get a pretty useless behavior that is probably best avoided:如果您没有自己的自定义WM_NCCALCSIZE处理程序,您会得到一个可能最好避免的非常无用的行为:

      • If you shrink the client area, nothing happens (your app gets no WM_PAINT at all)!如果您缩小客户区,则什么也不会发生(您的应用根本没有WM_PAINT )! If you're using the top or left border, your client area contents will move along with the top left of the client area.如果您使用顶部或左边框,您的客户区内容将与客户区的左上角一起移动。 In order to get any live resizing when shrinking the window, you have to manually draw from a wndproc message like WM_SIZE , or call InvalidateWindow() to trigger a later WM_PAINT .为了在缩小窗口时实时调整大小,您必须手动从wndproc消息(如WM_SIZE绘制,或调用InvalidateWindow()以触​​发稍后的WM_PAINT

      • If you enlarge the client area如果您扩大客户区

        • If you drag the bottom or right window border, Microsoft fills in the new pixels with the "background color" (see below)如果拖动底部或右侧窗口边框,Microsoft 会用“背景色”填充新像素(见下文)

        • If you drag the top or left window border, Microsoft copies the existing pixels to the top left corner of the expanded window and leaves an old junk copy of old pixels in the newly opened space如果拖动顶部或左侧窗口边框,Microsoft 会将现有像素复制到展开窗口的左上角,并在新打开的空间中留下旧像素的旧垃圾副本

So as you can see from this sordid tale, there appear to be two useful combinations:因此,从这个肮脏的故事中可以看出,似乎有两种有用的组合:

  • 2a1. 2a1. WNDCLASS.style with CS_HREDRAW|CS_VREDRAW gives you the behavior in Figures 1c3-1, 1c3-2, 1c4-1, and 1c4-2 of PART 1 , which is not perfect but at least your client area content will not move one direction then jerk back in the other direction带有CS_HREDRAW|CS_VREDRAW WNDCLASS.style为您提供第 1 部分的图 1c3-1、1c3-2、1c4-1 和 1c4-2 中的行为,这并不完美,但至少您的客户区内容不会向一个方向移动向另一个方向猛拉

  • 2a2. 2a2. WNDCLASS.style without CS_HREDRAW|CS_VREDRAW plus a WM_NCCALCSIZE handler returning WVR_VALIDRECTS (when wParam is 1) that BitBlts nothing, plus disabling the "background color" (see below) may completely disable Windows' molestation of your client area. WNDCLASS.style没有CS_HREDRAW|CS_VREDRAW加上WM_NCCALCSIZE处理程序返回WVR_VALIDRECTS (当wParam为 1 时) BitBlts什么都没有,加上禁用“背景颜色”(见下文)可能会完全禁用 Windows 对您的客户区的骚扰。

There is apparently another way to achieve the effect of combination 2a2.显然还有另外一种方式可以达到组合2a2的效果。 Instead of implementing your own WM_NCCALCSIZE , you can intercept WM_WINDOWPOSCHANGING (first passing it onto DefWindowProc ) and set WINDOWPOS.flags |= SWP_NOCOPYBITS , which disables the BitBlt inside the internal call to SetWindowPos() that Windows makes during window resizing.您可以拦截WM_WINDOWPOSCHANGING (首先将其传递给DefWindowProc )并设置WINDOWPOS.flags |= SWP_NOCOPYBITS ,而不是实现您自己的WM_NCCALCSIZE ,这将禁用 Windows 在窗口调整大小期间对SetWindowPos()的内部调用中的BitBlt I have not tried this trick myself but many SO users reported it worked.我自己没有尝试过这个技巧,但许多 SO 用户报告说它有效。

At several points above, we mentioned the "background color."在上面的几个点中,我们提到了“背景颜色”。 This color is determined by the WNDCLASS.hbrBackground field that you passed to RegisterClassEx .此颜色由您传递给RegisterClassExWNDCLASS.hbrBackground字段决定。 This field contains an HBRUSH object.该字段包含一个HBRUSH对象。 Most people set it using the following boilerplate code:大多数人使用以下样板代码设置它:

wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

The COLOR_WINDOW+1 incantation gives you a white background color. COLOR_WINDOW+1咒语为您提供白色背景色。 See MSDN dox for WNDCLASS for the +1 explanation and note there is a lot of wrong info about the +1 on StackOverflow and MS forums.有关 +1 的解释,请参阅 MSDN dox for WNDCLASS ,并注意 StackOverflow 和 MS 论坛上有很多关于 +1 的错误信息。

You can choose your own color like this:您可以像这样选择自己的颜色:

wndclass.hbrBackground = CreateSolidBrush(RGB(255,200,122));

You can also disable the background fill-in using:您还可以使用以下方法禁用背景填充:

wndclass.hbrBackground = NULL;

which is another key ingredient of combination 2a2 above.这是上述组合 2a2 的另一个关键成分。 But be aware that newly uncovered pixels will take on some essentially random color or pattern (whatever garbage happens to be in your graphics framebuffer) until your app catches up and draws new client area pixels, so it might actually be better to use combination 2a1 and choose a background color that goes with your app.但请注意,新发现的像素将呈现一些本质上随机的颜色或图案(无论图形帧缓冲区中出现什么垃圾),直到您的应用程序赶上并绘制新的客户区像素,因此实际上使用组合 2a1 和选择适合您的应用的背景颜色。

2b. 2b. Resize Problems from DWM Composition Fill从 DWM 组合填充调整大小问题

At a certain point during the development of Aero, Microsoft added another live resize jitter problem on top of the all-Windows-version problem described above.在 Aero 开发过程中的某个时刻,微软在上述全 Windows 版本问题之上添加了另一个实时调整大小抖动问题。

Reading earlier StackOverflow posts, it is actually hard to tell when this problem was introduced, but we can say that:阅读早期的 StackOverflow 帖子,实际上很难说这个问题是什么时候引入的,但我们可以说:

  • this problem definitely occurs in Windows 10这个问题肯定会出现在 Windows 10 中
  • this problem almost certainly occurs in Windows 8这个问题几乎肯定会在 Windows 8 中发生
  • this problem may have also occurred in Windows Vista with Aero enabled (many posts with resize problems under Vista do not say if they have Aero enabled or not).这个问题可能也发生在启用 Aero 的 Windows Vista 中(许多在 Vista 下有调整大小问题的帖子都没有说明是否启用了 Aero)。
  • this problem probably did not occur under Windows 7, even with Aero enabled.即使启用了 Aero,Windows 7 下也可能不会出现此问题。

The problem revolves around a major change of architecture that Microsoft introduced in Windows Vista called DWM Desktop Composition .问题围绕着 Microsoft 在 Windows Vista 中引入的称为DWM 桌面组合的体系结构的重大变化。 Applications no longer draw directly to the graphics framebuffer.应用程序不再直接绘制到图形帧缓冲区。 Instead, all applications are actually drawing into an off-screen framebuffer which is then composited with the output of other apps by the new, evil Desktop Window Manager (DWM) process of Windows.相反,所有应用程序实际上都在绘制一个离屏帧缓冲区,然后由 Windows 的新的、邪恶的桌面窗口管理器 (DWM) 进程与其他应用程序的输出合成。

So, because there is another process involved in displaying your pixels, there is another opportunity to mess up your pixels.因此,因为显示像素涉及另一个过程,所以还有另一个机会弄乱您的像素。

And Microsoft would never miss such an opportunity.微软绝不会错过这样的机会。

Here is what apparently happens with DWM Compostion:以下是 DWM Compostion 显然发生的情况:

  • The user clicks the mouse on a window border and begins to drag the mouse用户在窗口边框上单击鼠标并开始拖动鼠标

  • Each time the user drags the mouse, this triggers the sequence of wndproc events in your application that we described in section 2a above.每次用户拖动鼠标时,都会触发我们在上面第 2a 节中描述的应用程序中的wndproc事件序列。

  • But, at the same time, DWM (which remember is a separate process that is runnning asynchronously to your app) starts its own deadline timer.但是,与此同时,DWM(记住它是一个与您的应用程序异步运行的单独进程)启动自己的截止时间计时器。

  • Similarly to section 2a above, the timer apparently starts ticking after WM_NCCALCSIZE returns and is satisfied when your app draws and calls SwapBuffers() .与上面的第 2a 节类似,计时器显然在WM_NCCALCSIZE返回后开始计时,并且在您的应用程序绘制和调用SwapBuffers()

  • If you do update your client area by the deadline, then DWM will leave your client area beautifully unmolested.如果更新的截止时间你的客户区域,然后DWM会离开你的客户区精美不受干扰。 There is still a definite chance that your client area could still get molested by the problem in section 2a, so be sure to read section 2a as well.您的客户区仍有可能受到第 2a 节中的问题的骚扰,因此请务必阅读第 2a 节。

  • If you do not update your client area by the deadline, then Microsoft will do something truly hideous and unbelievably bad (didn't Microsoft learn their lesson?):如果你没有在截止日期前更新你的客户区,那么微软将做一些真正可怕且令人难以置信的糟糕事情(微软没有吸取教训吗?):

    • Suppose this is your client area before the resize, where A, B, C, and D represent pixel colors at the middle of your client area top, left, right, and bottom edges:假设这是调整大小之前的客户区,其中 A、B、C 和 D 代表客户区顶部、左侧、右侧和底部边缘中间的像素颜色:
    \n  --------------AAA----------------- ---------------AAA-----------------\n  | | | |\n  BC公元前\n  BC公元前\n  BC公元前\n  | | | |\n  --------------DDD----------------- --------------DDD-----------------\n  
    • Suppose you are using the mouse to enlarge your client area in both dimensions.假设您正在使用鼠标在两个维度上放大您的客户区。 Genius Windows DWM (or perhaps Nvidia: more on that later) will always copy the pixels of your client area to the upper-left corner of the new client area (regardless of which window border you are dragging) and then do the most absurd thing imaginable to the rest of the client area. Genius Windows DWM(或者可能是 Nvidia:稍后会详细介绍)将始终将您的客户区的像素复制到新客户区的左上角(无论您拖动哪个窗口边框),然后做最荒谬的事情可以想象到客户区的其余部分。 Windows will take whatever pixel values used to be along the bottom edge of your client area, stretch them out to the new client area width (a terrible idea we explored in Section 1c2 of PART 1 , and replicate those pixels to fill in all the newly opened space at the bottom (see what happens to D). Then Windows will take whatever pixel values used to be along the right edge of your client area, stretch them out to the new client area height, and replicate them to fill in the newly opened space at the top-right: Windows 将沿客户区底部边缘使用任何像素值,将它们拉伸到新的客户区宽度(我们在第 1部分第 1c2 节中探讨的一个糟糕的想法,并复制这些像素以填充所有新的客户区宽度)在底部打开空间(看看 D 会发生什么)。然后 Windows 将采用沿客户区右边缘的任何像素值,将它们拉伸到新的客户区高度,并复制它们以填充新的右上角的开放空间:
    \n  --------------AAA----------------------------------------------- --------------AAA----------------------------------- ------------\n  | | | | | |\n  BC |卑诗省 |\n  BC |卑诗省 |\n  B CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC B CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n  | | |CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC |CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n  --------------DDD-----------------CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC --------------DDD-----------------CCCCCCCCCCCCCCCCCCCCCCCCCCCC\n  | | DDDDDDDDD |滴滴滴滴滴|\n  | | DDDDDDDDD |滴滴滴滴滴|\n  | | DDDDDDDDD |滴滴滴滴滴|\n  | | DDDDDDDDD |滴滴滴滴滴|\n  | | DDDDDDDDD |滴滴滴滴滴|\n  ------------------------------DDDDDDDDD------------------------- ------------------------------DDDDDDDDD------------------- ------\n  
    • I cannot even imagine what they were smoking.我什至无法想象他们在抽什么烟。 This behavior produces the worst possible result in many cases.在许多情况下,这种行为会产生最坏的结果。 First, it's almost guaranteed to generate the horrific back-and-forth motion we showed in Figure 1c3-3 and Figure 1c4-3 of PART 1 when dragging the left and top window borders, since the rectangle copied is always at the upper-left regardless of which window border you are dragging.首先,当拖动左侧和顶部窗口边框时,几乎可以保证产生我们在第 1 部分的图 1c3-3 和图 1c4-3 中展示的可怕的来回运动,因为复制的矩形总是在左上角无论您拖动的是哪个窗口边框。 Second, the even more ridulous thing that's happening with the edge pixels being replicated is going to produce ugly bars if you happen to have any pixels set there other than the background color.其次,如果您碰巧在那里设置了除背景颜色以外的任何像素,那么复制边缘像素时发生的更荒谬的事情将产生丑陋的条。 Notice how the bars of C and D created do not even line up with the original C and D from the copied old pixels.请注意创建的 C 和 D 条甚至与复制的旧像素中的原始 C 和 D 不对齐。 I can understand why they are replicating the edge, hoping to find background pixels there to "automate" the process of background color detection, but it seems the likelihood of this actually working is heavily outweighed by the hack factor and chance of failure.我可以理解他们为什么要复制边缘,希望在那里找到背景像素以“自动化”背景颜色检测过程,但似乎这种实际工作的可能性被黑客因素和失败的可能性大大超过了。 It would be better if DWM used the app's chosen "background color" (in WNDCLASS.hbrBackground ), but I suspect DWM might not have access to that info since DWM is in a different process, hence the hack.如果 DWM 使用应用程序选择的“背景颜色”(在WNDCLASS.hbrBackground )会更好,但我怀疑 DWM 可能无法访问该信息,因为 DWM 处于不同的进程中,因此是 hack。 Sigh.叹。

But we haven't even gotten to the worst part yet:但我们甚至还没有到最糟糕的部分:

  • What actually is the deadline that DWM gives you to draw your own client area before DWM corrupts it with this clumsy hack of a guess?在 DWM 通过这种笨拙的猜测破坏它之前,DWM 给您绘制自己的客户区的最后期限实际上是多少? Apparently (from my experiments) the deadline is on the order of 10-15 milliseconds !显然(根据我的实验)截止日期约为 10-15 毫秒 Given that 15 milliseconds is close to 1/60, I would guess that the deadline is actually the end of the current frame.鉴于 15 毫秒接近 1/60,我猜测截止日期实际上是当前帧的结束。 And the vast majority of apps are unable to meet this deadline most of the time.绝大多数应用程序在大多数情况下都无法满足这个期限。

That is why, if you launch Windows Explorer on Windows 10 and drag the left border, you will most likely see the scroll bar on the right jitter/flicker/jump around erratically as if Windows were written by a fourth grader.这就是为什么,如果您在 Windows 10 上启动 Windows 资源管理器并拖动左边框,您很可能会看到右侧的滚动条不规则地抖动/闪烁/跳跃,就好像 Windows 是由四年级学生编写的一样。

I cannot believe that Microsoft has released code like this and considers it "done."我不敢相信微软已经发布了这样的代码并认为它​​“完成了”。 It is also possible that the responsible code is in the graphics driver (eg Nvidia, Intel, ...) but some StackOverflow posts led me to believe that this behavior is cross-device.也有可能相关代码在图形驱动程序中(例如 Nvidia、Intel 等),但一些 StackOverflow 帖子让我相信这种行为是跨设备的。

There is very little you can do to prevent this layer of incompetence from generating hideous jitter/flicker/jump when resizing using the left or top window border.在使用左侧或顶部窗口边框调整大小时,您几乎无法防止这一层无能产生可怕的抖动/闪烁/跳跃。 That is because the rude, non-consentual modification of your client area is happening in another process.那是因为对您的客户区的粗鲁、未经同意的修改发生在另一个进程中。

I am really hoping that some StackOverflow user will come up with some magic DWM setting or flag in Windows 10 that we can make to either extend the deadline or disable the horrible behavior completely.我真的希望某些 StackOverflow 用户能够在 Windows 10 中提出一些神奇的 DWM 设置或标志,我们可以使用它们来延长截止日期或完全禁用可怕的行为。

But in the meantime, I did come up with one hack that somewhat reduces the frequency of the hideous back-and-forth artifacts during window resize.但与此同时,我确实想出了一个技巧,可以在一定程度上降低窗口调整大小期间可怕的来回工件的频率。

The hack, inspired by a comment in https://stackoverflow.com/a/25364123/1046167 , is to do a best-effort at synchronizing the app process with the vertical retrace that drives DWM's activity.受到https://stackoverflow.com/a/25364123/1046167 中的评论启发的 hack 是尽最大努力将应用程序进程与驱动 DWM 活动的垂直回溯同步。 Actually making this work in Windows is not trivial.实际上,在 Windows 中进行这项工作并非易事。 The code for this hack should be the very last thing in your WM_NCCALCSIZE handler:这个 hack 的代码应该是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);

You can convince yourself that this hack is working by uncommenting the line that shows "worst-case" behavior by trying to schedule the drawing right in the middle of a frame rather than at vertical sync, and noticing how many more artifacts you have.您可以通过取消注释显示“最坏情况”行为的行来说服自己这个 hack 正在起作用,方法是尝试在帧的中间而不是垂直同步安排绘图,并注意您还有多少工件。 You can also try varying the offset in that line slowly and you will see that artifacts abruptly disappear (but not completely) at about 90% of the period and come back again at about 5-10% of the period.您还可以尝试缓慢改变该行中的偏移量,您会看到伪像在大约 90% 的时间段突然消失(但不是完全),并在大约 5-10% 的时间段再次出现。

Since Windows is not a real-time OS, it is possible for your app to be preempted anywhere in this code, leading to inaccuracy in the pairing of now1 and dti.qpcVBlank .由于Windows不是一个实时操作系统,可以为您的应用程序将在此代码的任何地方抢占,导致不准确的配对now1dti.qpcVBlank Preemption in this small code section is rare, but possible.这个小代码段中的抢占很少见,但有可能。 If you want, you can compare now0 and now1 and loop around again if the bound is not tight enough.如果你愿意,你可以比较now0now1如果结合不够紧密再次运转并循环。 It is also possible for preemption to disrupt the timing of Sleep() or the code before or after Sleep() .另外,也可以用于抢占破坏的定时Sleep()之前或之后或代码Sleep() There's not much you can do about this, but it turns out timing errors in this part of the code are swamped by the uncertian behavior of DWM;对此您无能为力,但事实证明这部分代码中的计时错误被 DWM 的不确定行为所淹没; you are still going to get some window resize artifacts even if your timing is perfect.即使您的时机是完美的,您仍然会得到一些调整窗口大小的伪像。 It's just a heuristic.这只是一个启发式。

There is a second hack, and it is an incredibly creative one: as explained in the StackOverflow post Can't get rid of jitter while dragging the left border of a window , you can actually create two main windows in your app, and every time Windows would do SetWindowPos , you intecept that and instead hide one window and show the other!还有第二个技巧,这是一个非常有创意的技巧:如 StackOverflow 帖子中所述,拖动窗口的左边框时无法消除抖动,您实际上可以在您的应用程序中创建两个主窗口,并且每次Windows 会执行SetWindowPos ,您接受这一点,而是隐藏一个窗口并显示另一个! I haven't tried this yet but the OP reports that it bypasses the insane pixel DWM pixel copy described above.我还没有尝试过这个,但 OP 报告说它绕过了上面描述的疯狂像素 DWM 像素副本。

There is a third hack, which might work depending on your application (especially in combination with the timing hack above).还有第三个技巧,它可能会根据您的应用程序起作用(尤其是与上面的计时技巧结合使用)。 During live resizing (which you can detect by intercepting WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE ), you could modify your drawing code to initially draw something much simpler that is much more likely to complete within the deadline imposed by problem 2a and 2b, and call SwapBuffers() to claim your prize: that will be enough to prevent Windows from doing the bad blit/fill described in section 2a and 2b.在实时调整大小期间(您可以通过拦截WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE来检测),您可以修改您的绘图代码以最初绘制更简单的东西,这些东西更有可能在问题 2a 和 2b 规定的期限内完成,然后调用SwapBuffers()领取您的奖品:这足以防止 Windows 执行第 2a 和 2b 节中描述的错误 blit/fill。 Then, immediately after the partial draw, do another draw that fully updates the window contents and call SwapBuffers() again.然后,在部分绘制之后,立即进行另一次绘制以完全更新窗口内容并再次调用SwapBuffers() That might still look somewhat odd, since the user will see your window update in two parts, but it's likely to look much better than the hideous back-and-forth motion artifact from Windows.这可能看起来仍然有些奇怪,因为用户将看到您的窗口更新分为两部分,但它看起来可能比 Windows 可怕的来回运动伪影要好得多。

One more tantalizing point: some apps in Windows 10, including the console (start cmd.exe ), are rock-solid free of DWM Composition artifacts even when dragging the left border.还有一点很吸引人:Windows 10 中的一些应用程序,包括控制台(启动cmd.exe ),即使在拖动左边框时也没有 DWM 合成伪影。 So there is some way of bypassing the problem.所以有一些方法可以绕过这个问题。 Let's find it!让我们找到它!

2c. 2c。 How to Diagnose Your Problem如何诊断您的问题

As you try to solve your particular resize problem, you may wonder which of the overlapping effects from Section 2a and Section 2b you are seeing.当您尝试解决特定的调整大小问题时,您可能想知道您看到的是第 2a 节和第 2b 节中的哪些重叠效果。

One way to separate them is to debug on Windows 7 (with Aero disabled, just to be safe) for a bit.将它们分开的一种方法是在 Windows 7 上调试一下(为了安全起见,禁用 Aero 的情况下)。

Another way to quickly identify if you are seeing the problem in Section 2b is to modify your app to display the test pattern described in Section 2b, like this example (note the 1-pixel-thin colored lines on each of the four edges):快速确定您是否在第 2b 节中看到问题的另一种方法是修改您的应用程序以显示第 2b 节中描述的测试模式,如下例所示(注意四个边缘中每条边上的 1 像素细彩色线):

测试模式

Then grab any window border and start resizing that border rapidly.然后抓住任何窗口边框并开始快速调整该边框的大小。 If you see intermittent giant colored bars (blue or green bars in the case of this test pattern, since there is blue on the bottom edge and green on the right edge) then you know you are seeing the problem in Section 2b.如果您看到间歇性的巨大彩色条(在此测试模式中为蓝色或绿色条,因为底部边缘为蓝色,右侧边缘为绿色),那么您就知道您在第 2b 节中看到了问题。

You can test if you are seeing the problem in Section 2a by setting WNDCLASS.hbrBackground to a distinct background color, like red.您可以通过将WNDCLASS.hbrBackground设置为不同的背景颜色(如红色)来测试您是否在第 2a 节中看到了问题。 As you resize the window, newly exposed parts will show up with that color.当您调整窗口大小时,新暴露的部分将显示为该颜色。 But read through Section 2a to make sure your message handlers are not causing Windows to BitBlt the entire client area, which would cause Windows not to draw any background color.但是通读第 2a 节以确保您的消息处理程序不会导致 Windows 对整个客户区进行BitBlt ,这会导致 Windows 不绘制任何背景颜色。

Remember that the problems in Section 2a and 2b only show up if your app fails to draw by a certain deadline, and each problem has a different deadline.请记住,第 2a 节和第 2b 节中的问题仅在您的应用程序未能在特定截止日期前绘制时出现,并且每个问题都有不同的截止日期。

So, without modification, your app might show the Section 2b problem only, but if you modify your app to draw more slowly (insert Sleep() in WM_PAINT before SwapBuffers() for example), you may miss the deadline for both Section 2a and Section 2b and start to see both problems simultaneously.因此,如果不进行修改,您的应用程序可能只会显示第 2b 节的问题,但是如果您修改应用程序以使其绘制更慢(例如,在SwapBuffers()之前的WM_PAINT插入Sleep() ),您可能会错过第 2a 节和第 2b 节的最后期限第 2b 节并开始同时看到这两个问题。

This may also happen when you change your app between a slower DEBUG build and a RELEASE build, which can make chasing these resize problems very frustrating.当您在较慢的DEBUG构建和RELEASE构建之间更改应用程序时,也可能会发生这种情况,这会使追逐这些调整大小问题变得非常令人沮丧。 Knowing what's going on under the hood can help you deal with the confusing results.了解幕后发生的事情可以帮助您处理令人困惑的结果。

PART 1: What Makes Resize Look Good or Bad?第 1 部分:是什么让调整大小看起来好或坏?

There is so much ambiguity and unclarity in StackOverflow questions about smooth resize that we need to establish a common vocabulary to help people make their answers more clear. StackOverflow 上关于平滑调整大小的问题有太多的歧义和不明确,我们需要建立一个通用词汇来帮助人们更清晰地回答。

That is what we will do in this section.这就是我们在本节中要做的。

To keep things simple, we going to explain the problems of smooth resizing in the horizontal dimension only, but everything here applies to vertical resizing just the same.为简单起见,我们将仅在水平维度上解释平滑调整大小的问题,但这里的所有内容都同样适用于垂直调整大小。

Below we will refer to a window's下面我们将参考一个窗口的

  • "non-client area:" the part of the window that Windows manages, including the title bar at the top and window borders around all edges, and “非客户区”:Windows 管理的窗口部分,包括顶部的标题栏和所有边缘的窗口边框,以及

  • "client area:" the main part of the window that you are responsible for “客户区:”您负责的窗口的主要部分

Suppose you have an app with:假设您有一个应用程序:

  • a button or label L that is supposed to remain flush-left应该保持左齐平的按钮或标签 L
  • a button or label R that is supposed to remain flush-right应该保持齐平的按钮或标签 R

no matter how the window gets resized.无论窗口如何调整大小。

Your app might draw L/R itself (eg using GDI/OpenGL/DirectX inside the one window) or L/R might be some Microsoft control (which would have its own HWND separate from your main window HWND);您的应用程序可能会自己绘制 L/R(例如在一个窗口内使用 GDI/OpenGL/DirectX),或者 L/R 可能是一些 Microsoft 控件(它有自己的 HWND 与您的主窗口 HWND 分开); doesn't matter.没关系。

Here is a simplified representation of the client area of your app window.这是应用程序窗口客户区的简化表示。 As you can see, we have three-column-wide LLL at the far left of the client area, and three-column-wide RRR at the far right of the client area, with various other client area content represented by "-" in between (please ignore the grey background that StackOverflow insists on adding; L and R are at the far left and right edges of your client area):如您所见,我们在客户区最左侧有三列宽的 LLL,在客户区最右侧有三列宽的 RRR,其他各种客户区内容以“-”表示之间(请忽略 StackOverflow 坚持添加的灰色背景;L 和 R 位于客户区的最左侧和最右侧):

LLL-----------RRR

Now imagine that you grab the left or right border of this window and drag it to make the window bigger or smaller.现在想象你抓住这个窗口的左边界或右边界并拖动它来使窗口变大或变小。

1a. 1a. Easy Case: Drawing On Time简单案例:按时绘图

Imagine that your app is very fast at drawing so that it can always respond to the user's dragging action in 1 millisecond, and the OS lets your app draw that quickly without trying to draw anything else on the screen to "help" you.想象一下,您的应用程序绘制速度非常快,因此它始终可以在 1 毫秒内响应用户的拖动操作,而操作系统让您的应用程序可以快速绘制,而无需尝试在屏幕上绘制任何其他内容来“帮助”您。

As you drag the app border, the user sees the following on-screen (with each line of these figures representing one instant of time):当您拖动应用程序边框时,用户会在屏幕上看到以下内容(这些数字的每一行代表一个时刻):

Dragging right border to the right (enlarging width):向右拖动右边框(扩大宽度):

(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)

Dragging right border to the left (shrinking width):向左拖动右边框(缩小宽度):

(Figure 1a-2)
LLL-----------RRR
LLL----------RRR
LLL---------RRR
LLL--------RRR

Dragging left border to the left (enlarging width):向左拖动左边框(扩大宽度):

(Figure 1a-3)
   LLL-----------RRR
  LLL------------RRR
 LLL-------------RRR
LLL--------------RRR

Dragging left border to the right (shrinking width):将左边框向右拖动(缩小宽度):

(Figure 1a-4)
LLL-----------RRR
 LLL----------RRR
  LLL---------RRR
   LLL--------RRR

These all look good and smooth:这些看起来都很好而且很流畅:

  • When adjusting the right border, R appears to move at a constant speed in one direction and L stays still as it should.调整右边框时,R 似乎在一个方向上以恒定速度移动,而 L 保持原样不动。
  • When adjusting the left border, L appears to move at a constant speed in one direction and R stays still as it should.调整左边框时,L 似乎在一个方向上以恒定速度移动,而 R 保持原样不动。

So far so good.到现在为止还挺好。

1b. 1b. Hard Case: Drawing Falls Behind硬案例:绘图落后

Now, imagine that your app is so slow at drawing that the app cannot keep up with you as you drag with the mouse.现在,想象一下您的应用程序绘制速度如此之慢,以至于当您用鼠标拖动时,该应用程序无法跟上您的步伐。 Yes, eventually, your drawing will catch up, but we are talking about what happens during the time that you are dragging the mouse with your hand.是的,最终,您的绘图会赶上来,但我们正在讨论在您用手拖动鼠标期间会发生什么。 Obviously the computer cannot reach out and grab your hand to slow your mouse movement down, so the key questions are:显然计算机无法伸手抓住你的手来减慢你的鼠标移动速度,所以关键问题是:

  • what should show on the screen during this period , and在此期间屏幕上应该显示什么,以及
  • who decides what should show?谁来决定应该展示什么?

For example, when dragging the right border to the right (enlarging width):例如,向右拖动右边框时(扩大宽度):

(Figure 1b-1)
LLL-----------RRR
??????????????????    (what should show here?)
???????????????????   (what should show here?)
LLL--------------RRR  (app catches up)

As another example, when dragging the left border to the left (shrinking width):再举一个例子,当向左拖动左边框时(缩小宽度):

(Figure 1b-2)
LLL-----------RRR
 ????????????????  (what should show here?)
  ???????????????  (what should show here?)
   LLL--------RRR  (app catches up)

These turn out to be the key questions that determine whether the motion looks smooth or not, and they are the key questions around which this whole StackOverflow question revolves.这些是决定运动是否流畅的关键问题,也是整个 StackOverflow 问题所围绕的关键问题。

Different versions of Windows provide different answers to these questions in different contexts, meaning that the solution to getting smoother resize depends on which situation you are in.不同版本的 Windows 在不同的上下文中对这些问题提供了不同的答案,这意味着更平滑地调整大小的解决方案取决于您所处的情况。

1c. 1c。 Temporary Solutions While Waiting for App to Draw等待应用程序绘制时的临时解决方案

There are several choices of what to do in the period after the user has begun to drag the mouse to resize the window, but before your app has caught up by drawing the window at the new size.在用户开始拖动鼠标以调整窗口大小之后,但在您的应用程序通过以新大小绘制窗口来赶上之前的时间段内,有多种选择。

1c1. 1c1。 Do Nothing没做什么

The screen could remain exactly as it is until the app catches up (neither your client pixels nor even the window border in the non-client area changes):屏幕可以保持原样,直到应用程序赶上(您的客户端像素甚至非客户端区域中的窗口边框都不会发生变化):

Example when dragging the right border to the right (enlarging width):向右拖动右边框(扩大宽度)时的示例:

(Figure 1c1-1)
LLL-----------RRR
LLL-----------RRR
LLL-----------RRR
LLL--------------RRR  (app catches up)

Example when dragging the left border to the left (shrinking width):向左拖动左边框时的示例(缩小宽度):

(Figure 1c1-2)
LLL-----------RRR
LLL-----------RRR
LLL-----------RRR
   LLL--------RRR  (app catches up)

The obvious disadvantage of this method is that during the period in question, the app appears to have "hung" and appears to be unresponsive to your mouse movements, because neither the R nor the '-' nor the L nor the the window border is moving.这种方法的明显缺点是,在相关期间,应用程序似乎“挂起”并且似乎对您的鼠标移动没有响应,因为 R 和“-”、L 和窗口边框都没有移动。

Microsoft is often picked on for Windows being an unresponsive OS (and it's sometimes their fault and sometimes the fault of the app developer), so ever since Microsoft introduced live-resize (Windows XP?), Microsoft never uses the "do nothing" method by itself.微软经常被指责为 Windows 是一个没有响应的操作系统(有时是他们的错,有时是应用程序开发人员的错),所以自从微软推出实时调整大小(Windows XP?)以来,微软从未使用过“什么都不做”的方法通过它自己。

The "do nothing" method is annoying for the user and looks unprofessional, but it turns out (very non-obviously) that it's not always the worst choice. “什么都不做”的方法对用户来说很烦人,而且看起来不专业,但事实证明(非常不明显)它并不总是最糟糕的选择。 Read on...继续阅读...

1c2. 1c2。 Scale Content缩放内容

Another possibility is that Windows could always make the window border follow your mouse movements instantly (because Windows itself has enough processing power to at least draw the non-client area in a timely manner), and while it is waiting for your app, Windows could take the old pixels of the client area and scale those pixels up or down just like when you zoom/blow up an image so that they "fit" in the smaller or bigger space.另一种可能性是,Windows 总是可以让窗口边框立即跟随您的鼠标移动(因为 Windows 本身有足够的处理能力,至少可以及时绘制非客户区),并且在等待您的应用程序时,Windows 可以获取客户区域的旧像素并放大或缩小这些像素,就像放大/放大图像一样,以便它们“适合”更小或更大的空间。

This technique is generally worse than any other technique because it will result in a blurry image of your original content that is likely to be out of proportion.这种技术通常比任何其他技术都差,因为它会导致原始内容的模糊图像很可能不成比例。 So nobody should ever do this in any case.所以在任何情况下都没有人应该这样做。 Except, as we will see in PART 2 , sometimes Microsoft does.除了,正如我们将在第 2 部分中看到的,有时微软会这样做。

1c3. 1c3。 When Enlarging, Fill in Some Background Color放大时填一些背景色

Another technique that could work when enlarging a window is the following: Windows could always make the window border follow your mouse movements instantly, and Windows could fill in new pixels of the now-larger client area with some temporary background color B:放大窗口时可以使用的另一种技术如下:Windows 始终可以使窗口边框立即跟随您的鼠标移动,并且 Windows 可以用一些临时背景色 B 填充现在更大的客户区的新像素:

For example, when dragging the right border to the right (enlarging width):例如,向右拖动右边框时(扩大宽度):

(Figure 1c3-1)
LLL-----------RRR
LLL-----------RRRB
LLL-----------RRRBB
LLL--------------RRR  (app catches up)

This method has the advantage that during the period in question, at least your window border is moving, so the app feels responsive.这种方法的优点是在所讨论的时间段内,至少您的窗口边框移动的,因此应用程序感觉响应。

Another nice feature is that during the drag, L stays still, just like it should.另一个不错的功能是在拖动过程中,L 保持静止,就像它应该的那样。

It's a little weird that the new space you are creating as you drag gets filled in with some random color, and even more weird that R doesn't actually move until later (notice that R jerks rightward by 3 columns at the last instant), but at least R only moves in the correct direction.有点奇怪的是,你在拖动时创建的新空间被一些随机颜色填充,更奇怪的是 R 直到稍后才真正移动(注意 R 在最后一刻向右猛拉 3 列),但至少 R 只在正确的方向上移动。 It's a partial improvement.这是部分改进。

A huge and important question is: what color should the newly filled-in background color B be?一个巨大而重要的问题是:新填充的背景色 B 应该是什么颜色? If B happens to be black and your app happens to have a mostly white background, or vice versa, it's going to be much uglier than if B matches your existing content's background color.如果 B 恰好是黑色,而您的应用程序恰好具有大部分白色背景,反之亦然,则比 B 与您现有内容的背景颜色匹配时要丑得多。 As we will see in PART 2 , Windows has deployed several different strategies to improve the choice of B.正如我们将在第 2 部分中看到的,Windows 已经部署了几种不同的策略来改进 B 的选择。

Now consider the same idea, but instead apply it to the case where we are dragging the left border to the left (enlarging width).现在考虑相同的想法,但将其应用于我们将左边框向左拖动(扩大宽度)的情况。

The logical thing would be to fill in the new background color on the left side of the window:合乎逻辑的事情是在窗口左侧填充新的背景颜色:

(Figure 1c3-2)
   LLL-----------RRR
  BLLL-----------RRR
 BBLLL-----------RRR
LLL--------------RRR  (app catches up)

This would be logical because R would stay put, just like it should.这是合乎逻辑的,因为 R 会保持原状,就像它应该的那样。 L would have the same weirdness we described along with Figure 1c3-1 above (L would hold still and then jerk 3 columns leftward all of a sudden at the last instant), but at least L would only move in the correct direction. L 将具有与我们在上面的图 1c3-1 中描述的相同的怪异(L 会保持静止,然后在最后一刻突然向左猛拉 3 列),但至少 L 只会向正确的方向移动。

However---and this is going to really come as a shock---in several important cases that you have to deal with, Windows does not do the logical thing.然而——这真的会让人感到震惊——在你必须处理的几个重要情况下,Windows 并没有做合乎逻辑的事情。

Instead, Windows sometimes fills in background pixels B on the right even if you are dragging the left window border:相反,即使您拖动左侧窗口边框,Windows 有时也会填充右侧的背景像素 B:

(Figure 1c3-3)
   LLL-----------RRR
  LLL-----------RRRB
 LLL-----------RRRBB
LLL--------------RRR  (app catches up)

Yes, this is insane.是的,这太疯狂了。

Consider how this looks to the user:考虑一下这对用户的看法:

  • L appears to move very smoothly at a constant speed in one direction, so that is actually good, but L 看起来在一个方向上以恒定速度非常平稳地移动,所以这实际上很好,但是

  • Just look at what R is doing:看看R在做什么:

    \n  RRR存款准备金率\n RRR存款准备金率\nRRR存款准备金率  \n  RRR (app catches up) RRR(应用赶上)\n
    • R first moves to the left by two columns, which it should not do: R is supposed to stay flush-right at all times R 首先向左移动两列,它不应该这样做:R 应该始终保持齐平
    • R then snaps back to the right again. R 然后再次向右 Holy crap!碉堡了!

This looks horrible, terrible, abysmal, disgusting, ... there are not even words to describe how bad this looks.这看起来太可怕了,可怕的,糟糕的,恶心的,......甚至没有词来形容这看起来有多糟糕。

The human eye is extremely sensitive to motion, even motion that happens over just a few frames of time.人眼对运动极其敏感,即使是发生在几帧时间内的运动。 Our eye instantly picks up on this bizarre back-and-forth motion of R and we immediately know something is seriously wrong.我们的眼睛立即注意到 R 的这种奇怪的来回运动,我们立即知道有些事情严重错误。

So here you can begin to get a sense of why some of these ugly resize problems only happen when you drag the left (or top) border and not the right (or bottom) border.因此,在这里您可以开始了解为什么某些丑陋的调整大小问题仅在您拖动左侧(或顶部)边框而不是右侧(或底部)边框时发生。

In reality, both cases (Figure 1c3-2 vs. Figure 1c3-3) do something weird.实际上,这两种情况(图 1c3-2 与图 1c3-3)都做了一些奇怪的事情。 In Figure 1c3-2 we temporarily add some background pixels B that do not belong there.在图 1c3-2 中,我们临时添加了一些不属于那里的背景像素 B。 But this weird behavior is much less noticeable than the back-and-forth motion of Figure 1c3-3.但是这种奇怪的行为远不如图 1c3-3 的来回运动那么明显。

This back-and-forth motion is the jitter/flicker/jumping that so many StackOverflow questions are about.这种来回运动许多 StackOverflow 问题所涉及的抖动/闪烁/跳跃。

So any solution to the problem of smooth resize has to:因此,平滑调整大小问题的任何解决方案都必须:

  • at least prevent items in your client area from appearing to jump in one direction then back the other direction.至少防止您客户区中的项目出现向一个方向跳跃然后返回另一个方向的情况。

  • ideally also avoid the need to add background pixels B, if possible如果可能的话,最好也避免添加背景像素 B

1c4. 1c4。 When Shrinking, Cut Off Some Pixels收缩时,切掉一些像素

Section 1c3 dealt with expanding the window.第 1c3 节涉及扩展窗口。 If we look at shrinking the window, we will see there is an exactly analogous set of cases.如果我们查看缩小窗口,我们将看到一组完全类似的情况。

A technique that could work when shrinking a window is the following: Windows could always make the window border follow your mouse movements instantly, and Windows could simply chop off (crop) some pixels of your now-smaller client area.缩小窗口时可以使用的技术如下:Windows 始终可以使窗口边框立即跟随您的鼠标移动,并且 Windows 可以简单地切掉(裁剪)您现在更小的客户区的一些像素。

For example, when dragging the right border to the left (shrinking width):例如,当向左拖动右边框时(缩小宽度):

(Figure 1c4-1)
LLL-----------RRR
LLL-----------RR
LLL-----------R
LLL--------RRR     (app catches up)

With this technique, L stays put as it should, but a weird thing happens on the right: R, which is supposed to stay flush-right no matter what the window size, appears to get its right edge incrementally sliced off by the right edge of the client area until R disappears, and then all of a sudden R reappears at its correct position when the app catches up.使用这种技术,L 保持原样,但右侧发生了奇怪的事情:R,无论窗口大小如何,都应该保持齐平,但它的右边缘似乎被右边缘逐渐切掉客户区,直到 R 消失,然后当应用程序赶上时,突然 R 重新出现在正确的位置。 This is very weird, but keep in mind that at no point does R appear to be moving to the right.这很奇怪,但请记住,R 似乎在任何时候都不会向右移动。 R's left edge appears to stay still, until the last moment when all of R jumps back 3 columns leftward. R 的左边缘似乎保持静止,直到所有 R 向左跳回 3 列的最后时刻。 So, like we saw in Figure 1c3-1, R only moves in the correct direction.所以,就像我们在图 1c3-1 中看到的那样,R 只会在正确的方向上移动。

Now consider what happens when we drag the left border to the right (shrinking width).现在考虑当我们向右拖动左边框(缩小宽度)时会发生什么。

The logical thing to do would be to shave pixels off the left of the client area:合乎逻辑的做法是将客户区左侧的像素刮掉:

(Figure 1c4-2)
LLL-----------RRR
 LL-----------RRR
  L-----------RRR
   LLL--------RRR  (app catches up)

This would have the same weird properties as Figure 1c4-1, just with the roles of left and right reversed.这将具有与图 1c4-1 相同的奇怪属性,只是左右角色颠倒了。 L would appear to get incrementally shaved off from L's left edge but L's right edge would remain still until at the last instant L appears to jump to the right. L 似乎从 L 的左边缘逐渐被剃光,但 L 的右边缘将保持静止,直到最后一刻 L 似乎向右跳。 So L only moves in the correct direction, albeit abruptly.所以 L 只会朝着正确的方向移动,尽管是突然的。

But---yes, get ready for total shock again---in several important cases that you have to deal with, Windows does not do the logical thing.但是——是的,再次为完全震惊做好准备——在你必须处理的几个重要情况下,Windows 并没有做合乎逻辑的事情。

Instead, Windows sometimes chops pixels off of the right even if you are dragging the left window border:相反,即使您拖动左侧窗口边框,Windows 有时也会从右侧切掉像素:

(Figure 1c4-3)
LLL-----------RRR
 LLL-----------RR
  LLL-----------R
   LLL--------RRR  (app catches up)

Consider how this looks to the user:考虑一下这对用户的看法:

  • L appears to move very smoothly at a constant speed in one direction, so that is actually good, but L 看起来在一个方向上以恒定速度非常平稳地移动,所以这实际上很好,但是

  • Just look at what R is doing:看看R在做什么:

    \nRRR存款准备金率\n RR RR\n  R电阻\nRRR (app catches up) RRR(应用赶上)\n
    • R first slides over to the right by two columns. R 首先向右滑动两列。 R's left edge appears to move rightward along with the rest of R. R 的左边缘似乎与 R 的其余部分一起向右移动。
    • R then snaps back to the left again. R 然后再次向左

As you should now be aware of after reading section 1c3, this back-and-forth motion looks absolutely horrible and is much worse than the admittedly weird behavior of Figure 1c4-1 and Figure 1c4-2.正如您在阅读 1c3 部分后现在应该意识到的那样,这种来回运动看起来绝对可怕,并且比图 1c4-1 和图 1c4-2 公认的怪异行为要糟糕得多。

1c5. 1c5。 Wait a Bit, Then Try One of Above稍等,然后尝试以上之一

So far we have presented separate ideas for what to do when the user has begun to drag the window borders but the app hasn't redrawn yet.到目前为止,当用户开始拖动窗口边框但应用程序尚未重绘时,我们已经提出了不同的想法。

These methods can actually be combined.这些方法实际上可以组合使用。

For a moment, try to think of this problem from Microsoft's point of view.暂时,试着从微软的角度思考这个问题。 At the moment that user starts dragging the mouse to resize your window, Microsoft has no way of knowing ahead of time how long it will take your app to draw.在用户开始拖动鼠标来调整窗口大小的那一刻,微软无法提前知道你的应用绘制需要多长时间。 So Microsoft has to strike a balance:所以微软必须取得一个平衡:

  • if your app is going to respond quickly, then any changes Microsoft makes to the screen are going to make your app look worse than if Microsoft just lets you draw the real content (remember, all the tricks above are weird to varying degrees and will make your content appear strangely, so not using any of those tricks is definitely better).如果您的应用程序要快速响应,那么微软对屏幕所做的任何更改都会使您的应用程序看起来比微软只是让您绘制真实内容更糟(请记住,上述所有技巧在不同程度上都很奇怪,并且会您的内容看起来很奇怪,所以不使用任何这些技巧肯定会更好)。

  • but if Microsoft waits for you to draw for too long, your app (and Windows by extension) will look hangy and unresponsive as we explained in Section 1c1.但是,如果 Microsoft 等待您绘制的时间过长,您的应用程序(以及扩展的 Windows)将看起来像我们在第 1c1 节中所解释的那样卡顿和无响应。 This makes Microsoft lose face even if it's your fault.这让微软失去面子,即使是你的错。

So, another option is to first hold off on any screen changes and give the app a certain amount of time to draw, and if the app fails to meet the deadline, then employ one of the methods above to temporarily "fill in the gap."所以,另一种选择是先推迟任何屏幕变化,给应用程序一定的时间来绘制,如果应用程序未能按时完成,则使用上述方法之一暂时“填补空白”。 ”

Does this sound horrible and hacky to you?这对你来说听起来很可怕吗? Guess what?你猜怎么着? That's what Windows does, in at least 2 different ways simultaneously with 2 different deadline times.这就是 Windows 所做的,至少以 2 种不同的方式同时具有 2 个不同的截止时间。 PART 2 will dive into these cases...第 2 部分将深入探讨这些案例......

PART 3: Gallery of Sorrow: Annotated List of Related Links第 3 部分:悲伤画廊:相关链接的注释列表

You might be able to glean ideas I missed by looking over the source material:您可能可以通过查看源材料来收集我遗漏的想法:

2014 with 2017 updates: Can't get rid of jitter while dragging the left border of a window : probably the most up-to-date question but still lacks context; 2014 与 2017 更新: 拖动窗口左边框时无法消除抖动:可能是最新的问题,但仍然缺乏上下文; suggests a creative but rather crazy hack of having two windows and alternately unhiding them during live resize!建议有两个窗口并在实时调整大小期间交替取消隐藏它们的创造性但相当疯狂的技巧! Also the only question I have found with an answer mentioning a race condition in DWM and a partial timing fix with DwmGetCompositionTimingInfo() .也是我在答案中发现的唯一问题,其中提到了 DWM 中的竞争条件和DwmGetCompositionTimingInfo()的部分时间修复。

2014 Why is there a black lag every time a WPF window is resized? 2014 为什么每次调整 WPF 窗口大小时都会出现黑色延迟? : yes WPF does it too. : 是的 WPF 也这样做。 No useful answers没有有用的答案

2009 How to fix the WPF form resize - controls lagging behind and black background? 2009 如何修复 WPF 表单调整大小 - 控件滞后和黑色背景? : controls lagging behind and black background?" multi-HWND example. mentions WM_ERASEBKGND and background brush tricks, but no modern answer. : 控制滞后和黑色背景?” 多 HWND 示例。提到WM_ERASEBKGND和背景刷技巧,但没有现代答案。

2018 Is there a way to reduce or prevent form flickering when using WPF? 2018 使用WPF时有没有办法减少或防止表单闪烁? : yes, still not fixed as of 2018. : 是的,截至 2018 年仍未修复。

2018 Reduce flickering when using SetWindowPos to change the left edge of a window : unanswered question that got many obsolete recommendations like WM_NCCALCSIZE 2018 使用 SetWindowPos 更改窗口左边缘时减少闪烁:未回答的问题有许多过时的建议,如WM_NCCALCSIZE

2012 OpenGL flickering/damaged with window resize and DWM active : good statement of problem, answerers completely misunderstood the context and provided inapplicable answers. 2012 OpenGL 闪烁/损坏,窗口调整大小和 DWM 处于活动状态:很好的问题陈述,回答者完全误解了上下文并提供了不适用的答案。

2012 How to avoid transient updates in a GUI resize? 2012 如何避免 GUI 调整大小中的瞬时更新? : mentions the trick of intercepting WM_WINDOWPOSCHANGING and setting WINDOWPOS.flags |= SWP_NOCOPYBITS . : 提到了拦截WM_WINDOWPOSCHANGING并设置WINDOWPOS.flags |= SWP_NOCOPYBITS

2016 Unity bug report : "Window resizing is very choppy and stutters (border does not smoothly follow the mouse)" typical bug report found in hundreds of apps that is partially due to the problem in this bug report, and partially due to certain apps having slow drawing. 2016 Unity 错误报告:“窗口大小调整非常断断续续(边框不能顺滑地跟随鼠标)”在数百个应用程序中发现的典型错误报告部分是由于此错误报告中的问题,部分是由于某些应用程序具有画得慢。 The only doc I EVER found which actually says that Windows 10 DWM clamps and extends the outer pixel of the old window, which I can confirm.我发现的唯一一个文档实际上说 Windows 10 DWM 会限制并扩展旧窗口的外部像素,我可以确认这一点。

2014 Flickering on window when resizing from left side with pre-Windows-8 answer including CS_HREDRAW/CS_VREDRAW and WM_NCCALCSIZE . 2014 使用 Windows-8 之前的答案(包括CS_HREDRAW/CS_VREDRAWWM_NCCALCSIZE 从左侧调整大小时在窗口上闪烁

2013 Resizing Window causes smearing near the right border with old-school Win-7-only solution to disable Aero. 2013 Resizing Window 导致右边框附近出现拖尾现象,老式的 Win-7-only 解决方案禁用 Aero。

2018 Flicker-free expansion (resize) of a window to the left an example of a multi-window (multi-HWND) case, no real answer. 2018 Flicker-free expand (resize) of a window to left一个多窗口(multi-HWND)案例的例子,没有真正的答案。

2013 WinAPI C++: Reprogramming Window Resize : too ambiguously asked to tell whether it is about client-area flickering (like this question) or non-client-area flickering. 2013 WinAPI C++: Reprogramming Window Resize : 太含糊不清地询问是关于客户区闪烁(如这个问题)还是非客户区闪烁。

2018 GLFW bug "Resizing windows on Windows 10 shows jumpy behaviour" one of MANY such bugs which never explain the context, like many StackOverflow posts 2018 GLFW 错误“在 Windows 10 上调整窗口大小会显示跳跃行为”是许多此类错误之一,它们从未解释上下文,就像许多 StackOverflow 帖子一样

2008 "Flicker Free Main Frame Resizing" CodeProject that actually does a StretchBlt but won't work in a Windows 8+ world, where app does not have control when incorrect pixels are shown on screen. 2008 年“Flicker Free Main Frame Resizing” CodeProject实际上执行 StretchBlt 但在 Windows 8+ 世界中不起作用,当屏幕上显示不正确的像素时,应用程序无法控制。

2014 Smooth window resizing in Windows (using Direct2D 1.1)? 2014 在 Windows 中平滑调整窗口大小(使用 Direct2D 1.1)? : Well-stated but unanswered issue with Windows 8+ DWM copy :Windows 8+ DWM 副本的明确但未解决的问题

2010 How do I force windows NOT to redraw anything in my dialog when the user is resizing my dialog? 2010 当用户调整我的对话框大小时,我如何强制窗口不在我的对话框中重绘任何内容? : WM_NCCALCSIZE fix to disable bitblt that no longer works in Windows 8+ since DWM corrupts the screen before app has chance to display. :WM_NCCALCSIZE 修复了禁用在 Windows 8+ 中不再有效的 bitblt,因为 DWM 在应用程序有机会显示之前破坏了屏幕。

2014 Flicker when moving/resizing window : roundup of previous fixes that do not work in Windows 8+. 2014 移动/调整窗口大小时闪烁:以前在 Windows 8+ 中不起作用的修复的综述。

2007 WinXP-era "reducing flicker" CodeProject recommending WM_ERASEBKGND+SWP_NOCOPYBITS 2007 WinXP时代“减少闪烁”CodeProject推荐WM_ERASEBKGND+SWP_NOCOPYBITS

2008 early Google Bug report of new Vista DWM problems 2008 年早期的Google Bug报告新的 Vista DWM 问题

Table of Contents目录

Because this is a complex, multi-faceted issue, I recommend reading the answers in this order:因为这是一个复杂的、多方面的问题,我建议按以下顺序阅读答案:

as well as a list of source material which may help others glean insights:以及可以帮助其他人收集见解的源材料列表:

Please feel free to contribute more answers with creative ways of avoiding the problems described in 2a and especially 2b!请随时以创造性的方式提供更多答案,以避免 2a 中描述的问题,尤其是 2b!

See the blog post The smooth resize test which has some analysis and pointers to solutions.请参阅博客文章平滑调整大小测试,其中有一些分析和解决方案的指针。 Basically there is a winning strategy, which is to render to the redirection surface during live resizing, and use the swapchain at other times.基本上有一个获胜策略,即在实时调整大小时渲染到重定向表面,并在其他时间使用交换链。 I'm not sure if this fixes your specific problem, as you need enough low-level control of the way presentation works to be able to implement that.我不确定这是否能解决您的具体问题,因为您需要对演示文稿的工作方式进行足够的低级控制才能实现它。 This approach also makes the assumption that you're drawing using Direct2D (as I'm currently doing) or DirectX.这种方法还假设您正在使用 Direct2D(正如我目前正在做的那样)或 DirectX 进行绘图。

If you're using DXGI, you can use DirectComposition + WS_EX_NOREDIRECTIONBITMAP to bypass the redirection surface entirely and render/present the client area with the new size before even returning from WM_NCCALCSIZE (ie before any deadline timers even start).如果您使用的是 DXGI,您可以使用 DirectComposition + WS_EX_NOREDIRECTIONBITMAP 完全绕过重定向表面并在从 WM_NCCALCSIZE 返回之前(即在任何截止时间计时器开始之前)渲染/呈现具有新大小的客户区。 Here's a minimal example using D3D11:这是使用 D3D11 的最小示例:

#include <Windows.h>

#include <d3d11.h>
#include <dcomp.h>
#include <dxgi1_2.h>

ID3D11Device* d3d;
ID3D11DeviceContext* ctx;
IDXGISwapChain1* sc;

/// <summary>
/// Crash if hr != S_OK.
/// </summary>
void hr_check(HRESULT hr)
{
    if (hr == S_OK) return;
    while (true) __debugbreak();
}

/// <summary>
/// Passthrough (t) if truthy. Crash otherwise.
/// </summary>
template<class T> T win32_check(T t)
{
    if (t) return t;

    // Debuggers are better at displaying HRESULTs than the raw DWORD returned by GetLastError().
    HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
    while (true) __debugbreak();
}

/// <summary>
/// Win32 message handler.
/// </summary>
LRESULT window_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
    switch (message)
    {
    case WM_CLOSE:
        ExitProcess(0);
        return 0;

    case WM_NCCALCSIZE:
        // Use the result of DefWindowProc's WM_NCCALCSIZE handler to get the upcoming client rect.
        // Technically, when wparam is TRUE, lparam points to NCCALCSIZE_PARAMS, but its first
        // member is a RECT with the same meaning as the one lparam points to when wparam is FALSE.
        DefWindowProc(hwnd, message, wparam, lparam);
        if (RECT* rect = (RECT*)lparam; rect->right > rect->left && rect->bottom > rect->top)
        {
            // A real app might want to compare these dimensions with the current swap chain
            // dimensions and skip all this if they're unchanged.
            UINT width = rect->right - rect->left;
            UINT height = rect->bottom - rect->top;
            hr_check(sc->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0));

            // Do some minimal rendering to prove this works.
            ID3D11Resource* buffer;
            ID3D11RenderTargetView* rtv;
            FLOAT color[] = { 0.0f, 0.2f, 0.4f, 1.0f };
            hr_check(sc->GetBuffer(0, IID_PPV_ARGS(&buffer)));
            hr_check(d3d->CreateRenderTargetView(buffer, NULL, &rtv));
            ctx->ClearRenderTargetView(rtv, color);
            buffer->Release();
            rtv->Release();

            // Discard outstanding queued presents and queue a frame with the new size ASAP.
            hr_check(sc->Present(0, DXGI_PRESENT_RESTART));

            // Wait for a vblank to really make sure our frame with the new size is ready before
            // the window finishes resizing.
            // TODO: Determine why this is necessary at all. Why isn't one Present() enough?
            // TODO: Determine if there's a way to wait for vblank without calling Present().
            // TODO: Determine if DO_NOT_SEQUENCE is safe to use with SWAP_EFFECT_FLIP_DISCARD.
            hr_check(sc->Present(1, DXGI_PRESENT_DO_NOT_SEQUENCE));
        }
        // We're never preserving the client area so we always return 0.
        return 0;

    default:
        return DefWindowProc(hwnd, message, wparam, lparam);
    }
}

/// <summary>
/// The app entry point.
/// </summary>
int WinMain(HINSTANCE hinstance, HINSTANCE, LPSTR, int)
{
    // Create the DXGI factory.
    IDXGIFactory2* dxgi;
    hr_check(CreateDXGIFactory1(IID_PPV_ARGS(&dxgi)));

    // Create the D3D device.
    hr_check(D3D11CreateDevice(
        NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
        NULL, 0, D3D11_SDK_VERSION, &d3d, NULL, &ctx));

    // Create the swap chain.
    DXGI_SWAP_CHAIN_DESC1 scd = {};
    // Just use a minimal size for now. WM_NCCALCSIZE will resize when necessary.
    scd.Width = 1;
    scd.Height = 1;
    scd.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    scd.SampleDesc.Count = 1;
    scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    scd.BufferCount = 2;
    // TODO: Determine if PRESENT_DO_NOT_SEQUENCE is safe to use with SWAP_EFFECT_FLIP_DISCARD.
    scd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
    scd.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
    hr_check(dxgi->CreateSwapChainForComposition(d3d, &scd, NULL, &sc));

    // Register the window class.
    WNDCLASS wc = {};
    wc.lpfnWndProc = window_proc;
    wc.hInstance = hinstance;
    wc.hCursor = win32_check(LoadCursor(NULL, IDC_ARROW));
    wc.lpszClassName = TEXT("D3DWindow");
    win32_check(RegisterClass(&wc));

    // Create the window. We can use WS_EX_NOREDIRECTIONBITMAP
    // since all our presentation is happening through DirectComposition.
    HWND hwnd = win32_check(CreateWindowEx(
        WS_EX_NOREDIRECTIONBITMAP, wc.lpszClassName, TEXT("D3D Window"),
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hinstance, NULL));

    // Bind our swap chain to the window.
    // TODO: Determine what DCompositionCreateDevice(NULL, ...) actually does.
    // I assume it creates a minimal IDCompositionDevice for use with D3D that can't actually
    // do any adapter-specific resource allocations itself, but I'm yet to verify this.
    IDCompositionDevice* dcomp;
    IDCompositionTarget* target;
    IDCompositionVisual* visual;
    hr_check(DCompositionCreateDevice(NULL, IID_PPV_ARGS(&dcomp)));
    hr_check(dcomp->CreateTargetForHwnd(hwnd, FALSE, &target));
    hr_check(dcomp->CreateVisual(&visual));
    hr_check(target->SetRoot(visual));
    hr_check(visual->SetContent(sc));
    hr_check(dcomp->Commit());

    // Show the window and enter the message loop.
    ShowWindow(hwnd, SW_SHOWNORMAL);
    while (true)
    {
        MSG msg;
        win32_check(GetMessage(&msg, NULL, 0, 0) > 0);
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM