C#使用ListView更新数据出现闪烁解决办法

C#使用ListView更新数据出现闪烁解决办法

在使用vs自动控件ListView控件时候,更新里面的部分代码时候出现闪烁的情况

如图:

 

解决以后:

 

解决办法使用双缓冲:添加新类继承ListView 对其重写

复制代码
 1 public class DoubleBufferListView : ListView
 2     {
 3         public DoubleBufferListView()
 4         {
 5             SetStyle(ControlStyles.DoubleBuffer |
 6               ControlStyles.OptimizedDoubleBuffer |
 7               ControlStyles.AllPaintingInWmPaint, true);
 8             UpdateStyles();
 9         }
10     }
复制代码

新建一个DemoTest测试

 

1.添加一个DoubleBufferListView的实例

复制代码
       DoubleBufferListView doubleBufferListView1= new DoubleBufferListView();
            // 
            // doubleBufferListView1
            // 
            this.doubleBufferListView1.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
            this.doubleBufferListView1.FullRowSelect = true;
            this.doubleBufferListView1.HideSelection = false;
            this.doubleBufferListView1.Location = new System.Drawing.Point(50, 37);
            this.doubleBufferListView1.Name = "doubleBufferListView1";
            this.doubleBufferListView1.Size = new System.Drawing.Size(400, 191);
            this.doubleBufferListView1.TabIndex = 2;
            this.doubleBufferListView1.UseCompatibleStateImageBehavior = false;
            this.doubleBufferListView1.View = System.Windows.Forms.View.Details;
复制代码

2.将其添加到form窗体里面

 this.Controls.Add(this.doubleBufferListView1);

3.给添加列

        doubleBufferListView1.Clear();
            doubleBufferListView1.Columns.Add("Action", 80, System.Windows.Forms.HorizontalAlignment.Left);
            doubleBufferListView1.Columns.Add("value", 80, System.Windows.Forms.HorizontalAlignment.Right);
            doubleBufferListView1.Columns.Add("Action", 80, System.Windows.Forms.HorizontalAlignment.Left);
            doubleBufferListView1.Columns.Add("value", 80, System.Windows.Forms.HorizontalAlignment.Left);

4.随便添加点内容

复制代码
         string[] listViewData = new string[4];
            listViewData[0] = "Action";
            listViewData[1] = "1";
            listViewData[2] = "Action";
            listViewData[3] = "1";
            ListViewItem lvItem = new ListViewItem(listViewData, 0);
            doubleBufferView1.Items.Add(lvItem);    
复制代码

 

5.点击按钮开始运行

复制代码
 private void button1_Click(object sender, EventArgs e)
        {
            Thread th = new Thread(PlayGame);
            if (state == false)
            {
                state = true;
                button1.Text = "停止";
                th.IsBackground = true;
                th.Name = "新线程";
                th.Start();
            }
            else
            {
                state = false;
                button1.Text = "开始";

            }
        }
        private void PlayGame()
        {
            Random r = new Random();
            while (state)
            {
                string temp = r.Next(0, 10).ToString();
                label1.Text = temp;
                this.doubleBufferListView1.Items[0].SubItems[1].Text = temp;
            }
        }
复制代码

6.运行对比图:

左侧是解决闪屏后,右侧是自带的ListView效果

 

原文链接:https://www.cnblogs.com/JiYF/p/6233313.html

 

 

C#中Invoke的用法

 

在用.NET Framework框架的WinForm构建GUI程序界面时,如果要在控件的事件响应函数中改变控件的状态,例如:某个按钮上的文本原先叫“打开”,单击之后按钮上的文本显示“关闭”,初学者往往会想当然地这么写:

void ButtonOnClick(object sender,EventArgs e)

{

    button.Text="关闭";

}

这样的写法运行程序之后,可能会触发异常,异常信息大致是“不能从不是创建该控件的线程调用它”。注意这里是“可能”,并不一定会触发该种异常。造成这种异常的原因在于,控件是在主线程中创建的(比如this.Controls.Add(...);),进入控件的事件响应函数时,是在控件所在的线程,并不是主线程。在控件的事件响应函数中改变控件的状态,可能与主线程发生线程冲突。如果主线程正在重绘控件外观,此时在别的线程改变控件外观,就会造成画面混乱。不过这样的情况并不总会发生,如果主线程此时在重绘别的控件,就可能逃过一劫,这样的写法可以正常通过,没有触发异常。

正确的写法是在控件响应函数中调用控件的Invoke方法(其实如果大家以前用过C++ Builder的话,也会找到类似Invoke那样的激活到主线程的函数)。Invoke方法会顺着控件树向上搜索,直到找到创建控件的那个线程(通常是主线程),然后进入那个线程改变控件的外观,确保不发生线程冲突。正确写法的示例如下:

void ButtonOnClick(object sender,EventArgs e)

{

    button.Invoke(new EventHandler(delegate

    {

        button.Text="关闭";

    }));

}

Invoke方法需要创建一个委托。你可以事先写好函数和与之对应的委托。不过,若想直观地在Invoke方法调用的时候就看到具体的函数,而不是到别处搜寻的话,上面的示例代码是不错的选择。

这样的写法有一个烦人的地方:对不同的控件写法不同。对于TextBox,要TextBoxObject.Invoke,对于Label,又要LabelObject.Invoke。有没有统一一点的写法呢?

主窗口类本身也有Invoke方法。如果你不想对不同的控件写法不一样,可以全部用this.Invoke:

void ButtonOnClick(object sender,EventArgs e)

{

    this.Invoke(new EventHandler(delegate

    {

        button.Text="关闭";

    }));

}

在C# 3.0及以后的版本中有了Lamda表达式,像上面这种匿名委托有了更简洁的写法。.NET Framework 3.5及以后版本更能用Action封装方法。例如以下写法可以看上去非常简洁:

void ButtonOnClick(object sender,EventArgs e)

{

    this.Invoke(new Action(()=>

    {

        button.Text="关闭";

    }));

}

以上写法往往充斥着WinForm构建的程序。

在微软新一代的界面开发技术WPF中,由于界面呈现和业务逻辑原生态地分开在两个线程中,所以控件的事件响应函数就不必Invoke了。但是,如果手动开辟一个新线程,那么在这个新线程中改变控件的外观,则还是要Invoke的。

 

当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它,此时它将会在内部调用new MethodInvoker(LoadGlobalImage)来完成下面的步骤,这个做法保证了控件的安全,你可以这样理解,有人想找你借钱,他可以直接在你的钱包中拿,这样太不安全,因此必须让别人先要告诉你,你再从自己的钱包把钱拿出来借给别人,这样就安全了

 

 

another:

在设计中为了让界面与逻辑分离,我的做法是使用事件,界面只要响应事件来处理界面的显示就行了。而事件在逻辑处理中可能由不同的线程引发,这些事件的响应方法在修改界面中的控件内容时便会引发一个异常。

这时就用到了Control.InvokeRequired 属性 与Invoke方法。

MSDN中说:
获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方位于创建控件所在的线程以外的线程中。 
如果控件的 Handle 是在与调用线程不同的线程上创建的(说明您必须通过 Invoke 方法对控件进行调用),则为 true;否则为 false。
Windows 窗体中的控件被绑定到特定的线程,不具备线程安全性 。因此,如果从另一个线程调用控件的方法,那么必须使用控件的一个 Invoke 方法来将调用封送到适当的线程。该属性可用于确定是否必须调用 Invoke 方法,当不知道什么线程拥有控件时这很有用。

下面来说下这个的用法(我的一般做法):
首先定义一个委托,与这个事件处理函数的签名一样委托,当然直接使用该事件的委托也是可以的,如:

 private delegate void InvokeCallback( string msg);

然后就是判断这个属性的值来决定是否要调用Invoke函数:

 void m_comm_MessageEvent( string msg)
 {
 if (txtMessage.InvokeRequired)
 {
 InvokeCallbackmsgCallback = new InvokeCallback(m_comm_MessageEvent);
 txtMessage.Invoke(msgCallback, new object [] { msg } );
 } 
 else 
 {
 txtMessage.Text = msg;
 } 
 }

说明:这个函数就是事件处理函数,txtMessage是一个文本框。
这样就做到了窗体中控件的线程安全性。

 

------------------

InvokeRequired 当前线程不是创建控件的线程时为true
比如你可以自己开一个Thread,或使用Timer的事件来访问窗体上的控件的时候,在线程中窗体的这个属性就是True的。

简单的说,如果有两个线程,Thread A和Thread B,并且有一个Control c,是在Thread A里面new的。
那么在Thread A里面运行的任何方法调用c.InvokeRequired都会返回false。
相反,如果在Thread B里面运行的任何方法调用c.InvokeRequired都会返回true。
是否是UI线程与结果无关。(通常Control所在的线程是UI线程,但是可以有例外)

也可以认为,在new Control()的时候,control用一个变量记录下了当前线程,在调用InvokeRequired时,返回当前线程是否不等于new的时候记录下来的那个线程。

--------------------

我理解:如果InvokeRequired==true表示其它线程需要访问控件,那么调用invoke来转给控件owner处理。

原文链接:https://www.cnblogs.com/vaevvaev/p/6909042.html
posted @ 2018-04-02 09:44  龙骑科技  阅读(1481)  评论(0编辑  收藏  举报