【问题标题】:WinForms Layered Controls with Background images cause tearing while scrolling带有背景图像的 WinForms 分层控件导致滚动时撕裂
【发布时间】:2015-08-27 09:51:31
【问题描述】:

我有一个 Form 具有以下属性:

  • 背景图片
  • 带有透明背景的可滚动PanelDock = DockStyle.Fill
  • PictureBox 带有大号 WidthHeight,显示滚动条

现在所有控件都设置为 DoubleBuffered,包括表单本身。一切都按预期工作,除了滚动 PictureBox 的面板时,表单背景图像滚动并重复显示垂直和水平撕裂,尽管它的静态图像适合表单大小,并且当您停止滚动时,它会正确显示。这仅在拖动滚动条时发生,如果我单击滚动条中的任何点来移动它,它会正确显示。

根据我的理解,双缓冲应该消除这种情况,但即使使用双缓冲也是一样的,可能会好一点,但滚动时仍然是一个大问题。

我尝试将所有控件放在另一个面板中,而不是使用表单背景图像并将这个面板放在表单上,​​但没有任何区别。

【问题讨论】:

    标签: c# .net winforms


    【解决方案1】:

    您正在使用名为“拖动时显示窗口内容”的 Windows 系统选项进行战斗。对于所有现代版本的 Windows,它都已打开。关闭它不是一个现实的目标,因为它是一个系统选项,它会影响所有应用程序的所有窗口。没有后门可以选择性地绕过此选项。

    启用后,操作系统会优化窗口的滚动。它执行快速 bitblt 以移动视频帧缓冲区中的像素,并仅为滚动显示的窗口部分生成绘制消息。就像向下滚动时底部的几行像素一样。底层 winapi 调用是ScrollWindowEx()。目的是为应用提供响应更快的 UI,实现滚动的工作量要少得多。

    您可能会看到它的标题,ScrollWindowEx() 还会移动由窗体的 BackgroundImage 绘制的像素。你可以看到。接下来你看到的是优化的油漆的副作用,它只重绘了显示的窗口部分。所以移动的背景图像像素不会被重绘。看起来像“涂抹”效果。

    有一个简单的解决方法,只需为面板的 Scroll 事件实现一个事件处理程序并调用 Invalidate()。所以整个面板再次被重绘:

        private void panel1_Scroll(object sender, ScrollEventArgs e) {
            panel1.Invalidate();
        }
    

    但现在您会注意到绘制的副作用不再被优化。您仍然可以看到像素被移动,然后被透支。这在很大程度上取决于绘制 BackgroundImage 的成本。通常从不便宜,因为它没有最佳像素格式 (32bppPArgb) 并且没有正确的大小,因此需要重新调整以适应窗口。视觉效果类似于“pogo”,面板边缘快速抖动。

    您不太可能会发现可以接受或想做优化 BackgroundImage 的工作。阻止 ScrollWindowEx() 完成它的工作需要一个相当大的武器,你可以调用 LockWindowUpdate()。像这样:

     using System.Runtime.InteropServices;
     ...
        private void panel1_Scroll(object sender, ScrollEventArgs e) {
            if (e.Type == ScrollEventType.First) {
                LockWindowUpdate(this.Handle);
            }
            else {
                LockWindowUpdate(IntPtr.Zero);
                panel1.Update();
                if (e.Type != ScrollEventType.Last) LockWindowUpdate(this.Handle);
            }
        }
    
        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool LockWindowUpdate(IntPtr hWnd);
    

    效果很好,背景图像像素现在很稳定。任何其他像素,好吧,不是那么多。另一种视觉效果,我们称之为“皱纹”。可以通过将窗口置于合成模式来摆脱该伪影。它对整个窗口表面进行双缓冲,包括子控件:

        protected override CreateParams CreateParams {
            get {
                const int WS_EX_COMPOSITED = 0x02000000;
                var cp = base.CreateParams;
                cp.ExStyle |= WS_EX_COMPOSITED;
                return cp;
            }
        }
    

    唯一剩下的工件是这个不是很便宜的代码的副作用。滚动时它可能看起来不那么流畅。否则就会告诉你为什么 28 年前窗户被设计成不透明的。

    【讨论】:

    • 完美运行,感谢详细解释和解决方案:)
    【解决方案2】:

    这并不容易,但它是可行的,虽然我花了 2 个小时才发现,但以下内容对我来说效果很好:

    首先,在将列添加到网格之前,您需要确保该列获得空值而没有默认的“空”图标:

        DataGridViewImageColumn imagecol = new DataGridViewImageColumn { ImageLayout = DataGridViewImageCellLayout.Stretch };
        imagecol.DefaultCellStyle.NullValue = null;
        grid.Columns.Add(imagecol);
    

    然后,您需要在调整或移动该特定列的行的任何事件中删除所有行的此列值(此处的示例是滚动事件):

    private void DataGridViewScrollEventHandler(object sender, ScrollEventArgs e)
    {
        if (e.ScrollOrientation == ScrollOrientation.VerticalScroll)
        {
            DataGridView grid = (DataGridView)sender;
            foreach (DataGridViewRow row in grid.Rows)
            {
                row.Cells[1].Value = null;
            }
        }
    }
    

    最后,您需要为您在绘制事件中删除它的所有行再次填充图像值:

    private void DataGridViewPaintEventHandler(object sender, PaintEventArgs e)
    {
        DataGridView grid = (DataGridView)sender;
        foreach (DataGridViewRow row in grid.Rows)
        {
            row.Cells[1].Value = myImage;
        }
    }
    

    如果您有很多行,您只需要对可见行执行此操作以提高性能。有一个属性,所以它是可行的。

    【讨论】:

      【解决方案3】:

      最好的解决办法是在控件滚动事件上再次设置表单的背景图片

      private void panel1_Scroll(object sender, ScrollEventArgs e) {
          /*
               Your Code if any exists
          */
      
          //reset the form's background image again in the scroll event
          this.BackgroundImage = Properties.Resources.your_background_image;
      }
      

      【讨论】:

        猜你喜欢
        • 2021-02-25
        • 1970-01-01
        • 2012-09-08
        • 1970-01-01
        • 2012-12-09
        • 2013-03-04
        • 1970-01-01
        • 2013-07-15
        • 1970-01-01
        相关资源
        最近更新 更多