Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 541|回复: 0

C# winfrom界面跳转闪烁问题解决方法

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-6-28 00:07:27 | 显示全部楼层 |阅读模式

    在窗体的构造函数中添加代码:

     SetStyle(ControlStyles.UserPaint, true);
                SetStyle(ControlStyles.AllPaintingInWmPaint, true); // 禁止擦除背景.
                SetStyle(ControlStyles.DoubleBuffer, true); // 双缓冲
    View Code

    然后在构造函数下面,写以下方法:

     #region 解决闪烁问题
            protected override void WndProc(ref Message m)
            {
                if (m.Msg == 0x0014) // 禁掉清除背景消息
                    return;
                base.WndProc(ref m);
            }
    
            protected override CreateParams CreateParams
            {
                get
                {
                    CreateParams cp = base.CreateParams;
                    cp.ExStyle |= 0x02000000;
                    return cp;
                }
            }
            #endregion
    View Code

    这种方法的确可以解决闪屏问题,在电脑上切换没有问题,但是在触控机上切换会有黑色的一条条,暂时还未解决,就是第一次进来的时候有,希望看到此贴的人,如果有什么好的方法,欢迎下方评论,谢谢

    最近对代码作了一些优化,试验后效果还可以,但是发现界面会闪烁,具体是TreeView控件会闪烁,语言为C#,IDE为VS2005。在查阅一些资料,使用了一些基本技术后(如开启双缓冲),发现没什么效果。

            于是使用Profiler工具,查找出瓶颈在于每次更新完界面的EndUpdate操作(使用这个是为了减少界面更新次数,但这里不理想是因为控件中中的元素很多),猜想大概每次更新,.Net底层都会更新重绘每个图元,所以速度会慢,造成闪烁。但是如果这样,使用双缓冲应该会有较好效果。再看代码,发现可能是更新动作太过频繁,于是降低速度,有所好转,但还是不行。

           继续在网上查阅,最终找到一个方案比较合适。原来底层重绘每次会清除画布,然后再全部重新绘制,这才是导致闪烁最主要的原因。于是重载消息发送函数操作,禁掉这条消息。代码如下:

            protected override void WndProc(ref Message m)

            {

                if (m.Msg == 0x0014) // 禁掉清除背景消息

                    return;

                base.WndProc(ref m);

            }

            成功!

    注:双缓冲还是有用的,在更新不是很频繁且控件内含元素不是特别多的时候。一旦元素过多,每次更新时间都比较长,即便使用了双缓冲,仍解决不了闪烁问题。个人认为最终比较理想的方法还是禁掉清除背景消息。

    附:一些尝试过但失败的记录

    1)使用setStyle

          网上有说使用setStyle函数去设置该控件的参数,具体为:

          SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);

          这三个选项参数后者是依赖前者的,必须并存,否则无效。并且这个函数本身是protected的,所以首先需要继承某控件再使用。

          这个目标是跟前面正确解决方案一致,也是禁止清除背景并开启双缓冲,但需要使用用户绘制选项,而且是全部交由用户绘制。这需要自己实现控件的全部绘制,比较麻烦。所以这个方法不是完全不可行,但是需要额外工作量,不推荐。我也没有使用。

    2)使用BeginUpdate和EndUpdate

          这一对操作对于需要批量操作更新控件的情景有比较好的效果,比如初始化时批量添加了大量节点。坏处就在于不能即时更新。所以,对于频繁的更新节点并希望立即反映到界面的情况不适用。如果使用并且没有禁掉清除界面消息的话,则控件看起来就会不停的闪烁,而且以白底为主,内容几乎不可见(这个视频繁程度而定)。因为界面更新都在EndUpdate处完成,操作太多导致EndUpdate阻塞时间过长,且清空在先,更新在后,导致界面看起来长时间处于空白状态。

    3)使用ControlStyles.EnableNotifyMessage选项

          这个选项的作用和正确解决方案也是一致的。使用方法是:

          SetStyle(ControlStyles.EnableNotifyMessage, true);

          protected override void onNotifyMessage(Message m)

          {

                   // 此处书写过滤消息代码

          }

          但是实际实验显示无效果,不知是什么原因,没有细究。

     

     

    我的操作系统是Win7,使用的VS版本是VS2012,文中的代码都是C#代码。

    这几天遇到一个问题,即我用一个嵌入图片的Panel作为Winform应用程序的背景,如下图所示:

    这是一个Winform窗体,里面放置了一个Panel,Dock属性为Fill,BackgroundImage使用了《少年电世界》2003年第02期的封面图片,BackgroundImageLayout使用了Stretch。

    这个界面现在有两个问题:

    1、在窗体第一次被打开时,背景图片会出现明显的闪烁

    2、在拉动窗体的边界以调整窗体大小时,背景图片非出现明显的闪烁

    为了处理这一问题,我查了一些资料,也都逐个试过了,下面先说下其中的两个有代表性方法:

    方法1:直接使用双缓冲

    1.  
      SetStyle( ControlStyles.UserPaint, true);
    2.  
      SetStyle( ControlStyles.AllPaintingInWmPaint, true)// 禁止擦除背景.
    3.  
      SetStyle( ControlStyles.DoubleBuffer, true)// 双缓冲

    我尝试着将这段代码加到窗体的构造函数中,并不能解决问题,闪烁依然非常明显

    在MSDN上还有一篇文章《如何通过对窗体和控件使用双缓冲来减少图形闪烁》

    地址:https://msdn.microsoft.com/zh-cn/library/3t7htc9c%28v=vs.80%29.aspx

    这篇文章中也介绍了一个方法使用双缓冲:

    SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

    这个方法依然不能解决问题。

    方法2:重写CreateParams方法

    方法2需要将以下这段代码放在Form类的代码内:

    1.  
      protected override CreateParams CreateParams
    2.  
      {
    3.  
      get
    4.  
      {
    5.  
      CreateParams paras =  base.CreateParams;
    6.  
      paras.ExStyle |=  0x02000000;////用双缓冲绘制窗口的所有子控件
    7.  
      return paras;
    8.  
      }
    9.  
      }

    这个方法我一开始尝试的时候一度认为是有效的,但使用了一段时间后还是发现了问题:

    1、这个方法可以解决问题1,但不能解决问题2

    2、这个方法会影响一些其他控件、组件的重绘(这点才是致命的)

    因此,这个方法也不能解决问题。

    上面两个方法都不能解决问题,于是我继续求助度娘,终于在下面这个页面找到了解决方法:

    方法3:封装Panel类

    http://blog.chinaunix.net/uid-14414741-id-2814313.html

    这个方法,需要新建一个PanelEnhanced类继承Panel类,代码如下:

    1.  
      /// <summary>
    2.  
      /// 加强版 Panel
    3.  
      /// </summary>
    4.  
      class PanelEnhanced : Panel
    5.  
      {
    6.  
      /// <summary>
    7.  
      /// OnPaintBackground 事件
    8.  
      /// </summary>
    9.  
      /// <param name="e"></param>
    10.  
      protected override void OnPaintBackground(PaintEventArgs e)
    11.  
      {
    12.  
      // 重载基类的背景擦除函数,
    13.  
      // 解决窗口刷新,放大,图像闪烁
    14.  
      return;
    15.  
      }
    16.  
       
    17.  
      /// <summary>
    18.  
      /// OnPaint 事件
    19.  
      /// </summary>
    20.  
      /// <param name="e"></param>
    21.  
      protected override void OnPaint(PaintEventArgs e)
    22.  
      {
    23.  
      // 使用双缓冲
    24.  
      this.DoubleBuffered = true;
    25.  
      // 背景重绘移动到此
    26.  
      if (this.BackgroundImage != null)
    27.  
      {
    28.  
      e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
    29.  
      e.Graphics.DrawImage(
    30.  
      this.BackgroundImage,
    31.  
      new System.Drawing.Rectangle(0, 0, this.Width, this.Height),
    32.  
      0,
    33.  
      0,
    34.  
      this.BackgroundImage.Width,
    35.  
      this.BackgroundImage.Height,
    36.  
      System.Drawing.GraphicsUnit.Pixel);
    37.  
      }
    38.  
      base.OnPaint(e);
    39.  
      }
    40.  
      }

    将之前我们建立窗体中的Panel容器换为我们新封装的PanelEnhanced容器,将程序的背景图片放到里面,再运行程序,程序背景闪烁的问题就完美解决了!

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-21 05:32 , Processed in 0.068185 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表