【问题标题】:Flicker-free expansion (resize) of a window to the left窗口向左无闪烁扩展(调整大小)
【发布时间】:2018-07-27 06:55:55
【问题描述】:

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

已折叠:

扩展:

在 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 键几秒钟。)您可能会注意到显示器上带有按钮的区域不会显示完美的静止图像,而是会闪烁.此外,您实际上可能会在实际按钮列的左侧看到“幽灵”按钮。

我无法使用屏幕捕捉来捕捉这种毫秒级的闪烁,所以我使用数码相机记录我的屏幕:

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

在此视频剪辑中,按钮列显然不是屏幕上的静态图像;相反,每次调整表单大小时,在几毫秒内,这个区域就不是应该的了。同样明显的是左侧有一列“幽灵”按钮。

我的问题是,是否有任何相当简单的方法可以消除这些视觉伪影(至少对我来说,即使您一次展开/折叠表单也非常明显)。

在我工作的 Windows 10/Delphi 10.1 计算机上,当我使用鼠标拖动其最左边缘时,表单的大小以完美的方式调整:未受影响的表单客户区在显示器上完全静止。但是,在我家中的 Windows 7/Delphi 2009 PC 上,我确实看到在执行此操作时正在进行很多重新定位。

【问题讨论】:

  • 你试过DisableAlign/EnableAlign吗?
  • @nil:是的,但它不会消除闪烁。
  • 我明白了,这并没有改变。在我的测试中,调整边缘的表格大小也确实闪烁。你会提供一个小样本来重现这个问题吗?编辑,似乎不同之处在于您抓取和调整大小的位置。左:闪烁。对:无。
  • 这里有一些组件 - stackoverflow.com/questions/7265416/…。我还使用了 Jedi 的 TJvRollOut - wiki.delphi-jedi.org/wiki/JVCL_Help:TJvRollOut,它表现不错。
  • @nil:只需创建一个新的 VCL 应用程序,将一些控件放入其中,将所有控件上的 Align 设置为 [alTop, alRight] 并使用我提交的代码。例如,您可以使用TForm.OnClick,并在每次调用时切换FCollapsed。至少在启用了视觉主题的 Windows 10 上,WM_COMMAND 样式的大小调整是无闪烁的。我可以想象它在没有视觉主题的 Windows 7 上不会无闪烁(但我还没有测试过)。

标签: windows forms delphi resize flicker


【解决方案1】:

我可以提供一些关于为什么您会看到另一半 UI 的幻影图像以及可能阻止它的方法的一些见解。重影图像表明在您有机会使用正确的像素重新绘制它们之前,有人正在复制您的客户区像素(并将它们复制到错误的位置,始终在您的窗口中左对齐)。

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

第一层适用于所有 Windows 操作系统,来自BitBlt 内的SetWindowPos。您可以通过多种方式摆脱 BitBlt。您可以创建自己的 WM_NCCALCSIZE 自定义实现来告诉 Windows 不进行任何 blit(或在自身顶部 blit 一个像素),或者您可以拦截 WM_WINDOWPOSCHANGING(首先将其传递给 DefWindowProc)并设置 @987654328 @,它会禁用 Windows 在调整窗口大小期间对 SetWindowPos() 的内部调用中的 BitBlt。这与跳过BitBlt 具有相同的最终效果。

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

将突破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 类型的操作发生在整个顶层窗口上(无论下面有多少窗口,也不管CLIPCHILDREN,它们都将窗口视为一组像素)。但是您需要让窗口自动移动,以便在下一次重绘时它们都正确定位。您可能会发现 BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos 对此很有用(但只有在上述技巧不起作用时才去那里)。

【讨论】:

    猜你喜欢
    • 2022-10-13
    • 2014-01-24
    • 1970-01-01
    • 2015-08-29
    • 2014-12-29
    • 2016-03-19
    • 1970-01-01
    • 2019-01-09
    • 2012-11-17
    相关资源
    最近更新 更多