← 全部文章 WinForm

WinForm 窗体闪烁怎么办

2022 年 6 月 9 日 · 1 分钟读完

目录
  1. 关闭 ShowDialog 的窗体时主窗体闪烁
  2. 延伸:其他几种常见闪烁
  3. 自己绘图时闪烁
  4. 控件很多,窗体打开或缩放时闪烁
  5. 批量增删控件时闪烁
  6. 背景图

工作中遇到的窗体闪烁问题,记在这里,遇到新的再补充。

关闭 ShowDialog 的窗体时主窗体闪烁

关闭一个用 ShowDialog() 弹出的窗体后,主窗体快速闪了一下(动图里一闪而过的黑色是后面的 VS 调试界面)。这个弹窗隐藏了 WinForm 自带的边框,关闭按钮是自己写的:

private void btnClose_Click(object sender, EventArgs e)
{
    this.Close();
    this.Dispose(); // 闪烁的原因在这里
}

把 Dispose 注释掉就正常了。

原因:对话框关闭时,Windows 要把激活状态交还给主窗体。在 Close 之后立刻 Dispose,窗口句柄在对话框还没退出完的时候就被销毁,焦点先跳到了别的窗口(VS),再回到主窗体,于是闪了一下。

那不 Dispose 会不会内存泄漏?会有一点:用 ShowDialog() 显示的窗体关闭时不会自动释放(用 Show() 显示的会),因为调用方可能还要读它上面的值。正确做法是由调用方在用完后释放:

using (var frm = new FrmBinAdd(binBox))
{
    if (frm.ShowDialog() == DialogResult.OK)
    {
        // 读取 frm 上用户填的值
    }
} // 这里自动 Dispose

弹窗里的关闭按钮只需要设置 DialogResult 或者 Close(),不要自己 Dispose 自己。

延伸:其他几种常见闪烁

自己绘图时闪烁

在 Paint 里画很多东西(图表、示意图、实时曲线)时,每次刷新都会先擦背景再画,看起来一闪一闪。打开双缓冲:先画在内存里,画完一次性贴到屏幕上。

public FrmChart()
{
    InitializeComponent();
    this.DoubleBuffered = true;
    // 自定义控件里的等价写法
    // SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
}

控件很多,窗体打开或缩放时闪烁

DoubleBuffered 只对窗体自己的绘制有效,管不到子控件。控件多、带背景图的窗体,可以在窗体里重写 CreateParams,让 Windows 合成整个窗口后再显示:

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

副作用是某些控件(比如嵌入的相机预览、Halcon 窗口)刷新会变慢或不刷新,有这类控件时慎用。

批量增删控件时闪烁

动态加很多控件、或者改大量布局时,先暂停布局,改完再统一计算一次:

panel1.SuspendLayout();
for (int i = 0; i < 50; i++) panel1.Controls.Add(new Button { Text = i.ToString() });
panel1.ResumeLayout();

DataGridView 大量更新数据时同理,先解绑或者把 DoubleBuffered 通过反射打开(它的这个属性是 protected 的)。

背景图

BackgroundImage 用大图并且 BackgroundImageLayout = Stretch 时,每次重绘都要缩放,很慢。把图片提前缩放成窗体大小,布局设为 None。

# WinForm # 界面

原文首发于 blog.csdn.net