左侧窗口的无闪烁扩展(调整大小)

时间:2018-02-16 11:11:33

标签: windows forms delphi resize flicker

假设您有一个表单,您可以向左展开以显示其他控件:

已折叠:

Collapsed form

扩展

Expanded form

在Delphi中实现这一目标的最简单方法是使用alRight作为所有控件(而不是alLeft)的主要锚点,然后只需调整表单的宽度和X坐标。您可以单独设置WidthLeft属性,也可以使用同时设置它们的函数,例如

if FCollapsed then
  SetWindowPos(Handle, 0, Left - Width, Top, 2 * Width, Height, 0)
else
  SetWindowPos(Handle, 0, Left + Width div 2, Top, Width div 2, Height, 0)

问题是在展开或折叠时表单中始终可见的部分(在此示例中为按钮)中存在明显的闪烁。亲自试试吧!

操作系统可以将表单调整到左侧而不会出现任何闪烁 - 只需使用鼠标抓住表单的左边缘并向左或向右拖动鼠标 - 但我无法在Windows API中查找公开此类调整大小的任何函数。

我尝试使用几种不同的Windows API函数来调整表单大小和重新定位,尝试了各种参数(例如SWP_*标志),尝试了LockWindowUpdateWM_SETREDRAWTForm.DoubleBuffered等无济于事。我还研究了使用WM_SYSCOMMAND SC_SIZE方法的可能性。

我还不确定问题出在操作系统级别还是VCL级别。

有什么建议吗?

编辑:我很惊讶地看到这个Q获得了接近的选票。让我试着澄清一下:

  1. 创建一个新的VCL表单应用程序。

  2. 在主窗体的右侧添加几个按钮,在左侧添加备忘录。在所有控件上将Anchors设置为[alTop, alRight]。在按钮的OnClick处理程序上,添加以下代码:

    if FCollapsed then
      SetWindowPos(Handle, 0, Left - Width, Top, 2 * Width, Height, 0)
    else
      SetWindowPos(Handle, 0, Left + Width div 2, Top, Width div 2, Height, 0);
    
    FCollapsed := not FCollapsed;
    

    其中FCollapsed是表单的私有布尔字段(初始化为false)。

  3. 现在,重复单击按钮。 (或者给他们中的一个键盘对焦并按住 Enter 键几秒钟。)您可能会注意到显示器上带按钮的区域不会显示完美的静止图像,但会闪烁。另外,你可能真的看到了鬼魂'实际按钮列左侧的按钮。

  4. 我无法使用屏幕截图捕捉这个毫秒闪烁,所以我使用数码相机来录制我的屏幕:

    https://privat.rejbrand.se/VCLFormExpandFlicker.mp4

    在这个视频片段中,很明显按钮列不是屏幕上的静态图像;相反,每次调整表单大小几毫秒时,这个区域应该是应有的。同样显而易见的是,有一个“幽灵”。左边的按钮列。

    我的问题是,是否有任何相当简单的方法来摆脱这些视觉文物(即使你一次扩展/折叠表格,至少对我来说是非常明显的。)

    在我的Windows 10 / Delphi 10.1计算机上工作时,当我使用鼠标拖动其最左边时,窗体会以完美的方式调整大小:窗体的未受影响的客户区在监视器上是完全静态的。但是,在家里的Windows 7 / Delphi 2009 PC上,我确实发现当我这样做时会有很多重新定位。

1 个答案:

答案 0 :(得分:0)

我可以提供一些有关为什么您会看到用户界面另一半的幻像的信息,并可能提供一种阻止它的方法。重影表示有人在您有机会用正确的像素重画像素之前(正在将其复制到客户区域像素(并将它们复制到错误的位置,始终在窗口中向左平移))。

这些重影像素可能有两个不同的重叠来源。

第一层适用于所有Windows操作系统,并且来自BitBlt内部的SetWindowPos。您可以通过多种方式摆脱该BitBlt。您可以创建自己的WM_NCCALCSIZE的自定义实现,以告诉Windows不进行盲处理(或对自身顶部进行盲处理),或者可以拦截WM_WINDOWPOSCHANGING(首先将其传递到{{1} })并设置DefWindowProc,这将禁用Windows在调整窗口大小期间对WINDOWPOS.flags |= SWP_NOCOPYBITS进行的内部调用中的BitBlt。最终具有跳过SetWindowPos()的相同效果。

但是,Windows 8/10 Aero增加了另一个更麻烦的层。现在,应用程序会绘制到屏幕外缓冲区,然后由新的邪恶DWM.exe窗口管理器进行合成。事实证明,DWM.exe有时会在旧版XP / Vista / 7代码已经完成的操作之上执行自己的BitBlt类型的操作。阻止DWM变得更加困难。到目前为止,我还没有看到完整的解决方案。

有关将突破XP / Vista / 7层并至少改善8/10层性能的示例代码,请参阅:

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

由于您有多个子窗口,因此情况更加复杂。我上面提到的BitBlt类型的操作会在整个顶层窗口中整体发生(无论下面有多少窗口,也不管BitBlt,它们都将窗口视为一组像素)。但是您需要使窗口原子移动,以便在下一次重绘时将它们都正确定位。您可能会发现CLIPCHILDREN对此有用(但只有在上述技巧无效的情况下,才去那里)。