繁体   English   中英

如何在调整大小 (C#) 时防止无边框 Windows 窗体闪烁?

[英]How to prevent a borderless Windows Form from flickering when resizing (C#)?

[C# .NET 4.0]

我正在学习 C# 并且我正在尝试使用 C# 构建一个 Windows 窗体,它具有FormBorderStyle = FormBorderStyle.None并且可以使用 Windows API 移动/调整大小。 例如,我使用用于 Google Chrome 和 Norton 360 的圆角或自定义(可移动/可调整大小)边框设计作为表单的基础。

到目前为止,我已经取得了很大的进步,并且一切正常,除了当我调整表单大小时,当您快速调整表单大小时,沿着右边框和下边框的长度会出现黑色/白色闪烁

我试过在构造函数中添加this.DoubleBuffer = true并且也试过this.SetStyles(ControlStyles.AllPaintInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true); .

因为我是图形方面的傻瓜,我喜欢控制表单的完整设计,所以我可以看到这是永远困扰我的东西......所以如果有人可以帮助我解决这个问题,那么闪烁不再发生,这对我的学习过程非常有用。

我还应该提到我使用的是 Windows XP,所以我不确定这篇文章是否会对我有所帮助,因为它似乎专注于 Vista/7(使用 DWM)......并不是说我还不够先进了解该帖子中的所有内容。

与 API 一起使用的代码的两部分如下。 我有一个 Windows API 的 WM_NCHITTEST 的公共枚举......你可以在这个链接中看到这些值。

OnPaint 覆盖方法

protected override void OnPaint(PaintEventArgs e)
{
    System.IntPtr ptrBorder = CreateRoundRectRgn(0, 0,
        this.ClientSize.Width, this.ClientSize.Height, 15, 15);

    SetWindowRgn(this.Handle, ptrBorder, true);

    Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip,
        this.ClientSize.Height - cGrip, cGrip, cGrip);
    ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
    rc = new Rectangle(0, 0, this.ClientSize.Width, 32);
    e.Graphics.FillRectangle(Brushes.SlateGray, rc);
}

WndProc 覆盖方法

protected override void WndProc(ref Message m)
{
    if (m.Msg == (int)HitTest.WM_NCHITTEST)
    {
        // Trap WM_NCHITTEST
        Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
        pos = this.PointToClient(pos);

        if (pos.Y < cCaption)
        {
            m.Result = (IntPtr)HitTest.HTCAPTION;
            return;
        }

        if (pos.X <= cGrip && pos.Y >= this.ClientSize.Height - cGrip)
        {
            m.Result = (IntPtr)HitTest.HTBOTTOMLEFT;
            return;
        }

        if (pos.X >= this.ClientSize.Width - cGrip &&
            pos.Y >= this.ClientSize.Height - cGrip)
        {
            m.Result = (IntPtr)HitTest.HTBOTTOMRIGHT;
            return;
        }

        if (pos.X >= this.ClientSize.Width - cBorder)
        {
            m.Result = (IntPtr)HitTest.HTRIGHT;
            return;
        }

        if (pos.Y >= this.ClientSize.Height - cBorder)
        {
            m.Result = (IntPtr)HitTest.HTBOTTOM;
            return;
        }

        if (pos.X <= cBorder)
        {
            m.Result = (IntPtr)HitTest.HTLEFT;
            return;
        }
    }

    base.WndProc(ref m);
}

这是完整的代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace PracticeForm
{
    public partial class Form2 : Form
    {
        [DllImport("user32.dll")]
        private static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw);

        [DllImport("gdi32.dll")]
        private static extern IntPtr CreateRoundRectRgn(int x1, int y1, int x2, int y2, int cx, int cy);

        [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
        private static extern bool DeleteObject(System.IntPtr hObject);

        private const int cGrip = 20;
        private const int cCaption = 35;
        private const int cBorder = 7;
        private Point mouseOffset;

        public Form2()
        {
            InitializeComponent();
            this.FormBorderStyle = FormBorderStyle.None;
            this.MaximumSize = new Size(670, 440);
            this.DoubleBuffered = true;
            this.SetStyle(ControlStyles.ResizeRedraw |
                          ControlStyles.OptimizedDoubleBuffer |
                          ControlStyles.AllPaintingInWmPaint |
                          ControlStyles.UserPaint, true);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            System.IntPtr ptrBorder = CreateRoundRectRgn(0, 0,
                this.ClientSize.Width, this.ClientSize.Height, 15, 15);

            SetWindowRgn(this.Handle, ptrBorder, true);

            Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip,
                this.ClientSize.Height - cGrip, cGrip, cGrip);
            ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == (int)HitTest.WM_NCHITTEST)
            {
                // Trap WM_NCHITTEST
                Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
                pos = this.PointToClient(pos);

                if (pos.Y < cCaption)
                {
                    m.Result = (IntPtr)HitTest.HTCAPTION;
                    return;
                }

                if (pos.X <= cGrip && pos.Y >= this.ClientSize.Height - cGrip)
                {
                    m.Result = (IntPtr)HitTest.HTBOTTOMLEFT;
                    return;
                }

                if (pos.X >= this.ClientSize.Width - cGrip &&
                    pos.Y >= this.ClientSize.Height - cGrip)
                {
                    m.Result = (IntPtr)HitTest.HTBOTTOMRIGHT;
                    return;
                }

                if (pos.X >= this.ClientSize.Width - cBorder)
                {
                    m.Result = (IntPtr)HitTest.HTRIGHT;
                    return;
                }

                if (pos.Y >= this.ClientSize.Height - cBorder)
                {
                    m.Result = (IntPtr)HitTest.HTBOTTOM;
                    return;
                }

                if (pos.X <= cBorder)
                {
                    m.Result = (IntPtr)HitTest.HTLEFT;
                    return;
                }
            }

            base.WndProc(ref m);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void button2_MouseClick(object sender, MouseEventArgs e)
        {
            this.WindowState = FormWindowState.Minimized;
        }

        private void panel1_MouseDown(object sender, MouseEventArgs e)
        {
            mouseOffset = new Point(-e.X, -e.Y);
        }

        private void panel1_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Point p = Control.MousePosition;
                p.Offset(mouseOffset.X, mouseOffset.Y);
                Location = p;
            }
        }

        private void label1_MouseDown(object sender, MouseEventArgs e)
        {
            mouseOffset = new Point(-e.X, -e.Y);
        }

        private void label1_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Point p = Control.MousePosition;
                p.Offset(mouseOffset.X, mouseOffset.Y);
                Location = p;
            }
        }
    }
}

谢谢您的帮助。

闪烁的发生是因为您的显示器区域正在迅速改变颜色,而这又是因为您过度绘制 - 在同一像素上绘制了不止一件东西。

发生这种情况是因为:

  • 如果您的重绘速度很慢,那么您正在绘制的屏幕上的内容(例如窗口边框)将在一段时间内可见。 例如,用户可能会看到滚动条的两个副本,直到您用表单的内容擦除旧的滚动条。
  • windows 会自动为您擦除窗口的背景,通常为白色。 因此,在您用正确的图像过度绘制之前,您绘图的任何非白色区域都会闪烁白色。
  • 如果你在同一个地方绘制多个东西,当你不断改变屏幕那个区域的颜色时,你会看到闪烁

要解决这些问题,您需要综合考虑(越多越好)

  • 禁用擦除背景,或将擦除颜色设置为图像中的主色
  • 优化您的重绘代码以使其更快,因此闪烁不那么突出
  • 优化您的重绘代码以消除过度绘制。 例如,要在矩形页面周围放置边框,您可以绘制背景颜色并随页面过度绘制,但这会闪烁。 相反,将顶部边框绘制为矩形,然后绘制左下方和右下方,然后在中间绘制页面。 由于 nopixels 被绘制多次,它不会闪烁
  • 在您的控件上启用 DoubleBuffered 模式。 这样,您的所有绘图实际上都发生在内存中的位图图像中,然后将最终图像复制到屏幕上,这样每个像素只显示一次并且没有闪烁。

虽然这是一个相当老的线程,并且 OP 可能已经找到了解决他的问题的方法并继续前进,但我想添加一些额外的要点,以防它们被证明对正在解决类似问题的 .NET 开发人员有益.

首先,我要感谢您尝试在 Windows XP 上解决这个问题。 我去过那里,在那里度过了很多小时,并因此吸取了所有艰难的教训。 不幸的是,由于 Windows XP 缺少我们大多数人已经习惯的 DWM,因此没有简单的解决方案。

正确设置 ControlStyles 绝对重要——我还将包括:

SetStyle(ControlStyles.Opaque, True)

对您打算绘制的控件进行双缓冲很重要,因为闪烁主要是由在监视器垂直回扫中间重绘的控件引起的。 仅仅因为您调用了 Invalidate(),并不一定意味着该控件会在您想要的时候重新绘制——您受 Windows 的支配,操作系统会在它准备好时执行它。 您可以在 Windows XP 上解决这个问题(就像我所做的那样),方法是利用 DirecDraw 7 中的 WaitForVerticalBlank 之类的功能(Windows XP 上对该 API 有很多支持),还可以使用 GetVerticalBlankStatus 和 GetScanLine 相应地为渲染和演示计时。

仅在 Form 实际更改SIZE时才设置 Region ,而不是每次都在 Paint() 事件中:

    protected override void OnSizeChanged(EventArgs e)
    {
        base.OnSizeChanged(e);

        System.IntPtr ptrBorder = CreateRoundRectRgn(0, 0,
            this.ClientSize.Width, this.ClientSize.Height, 15, 15);

        SetWindowRgn(this.Handle, ptrBorder, true);
    }

    protected override void OnPaint(PaintEventArgs e)
    {

        Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip,
            this.ClientSize.Height - cGrip, cGrip, cGrip);
        ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
    }

试试这个:

如何停止闪烁的 C# winforms

我的无边框表单上有面板作为标题栏,标题栏面板上出现闪烁问题,并在表单加载中添加了此代码,闪烁消失了。

var prop = TitleBar_panel.GetType().GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic);
            prop.SetValue(TitleBar_panel, true, null);

TitleBar_panel是闪烁的控件。

编辑:现在只有当我从表单的左侧调整它的大小时它才会闪烁。 所以它不是 100% 由这段代码解决的

暂无
暂无

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

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