频繁刷新DataGridView的DataSource会报错

来源:互联网 发布:十大杀人狂魔电影 知乎 编辑:程序博客网 时间:2024/04/28 00:32

在winform开发中,我们常常会用DataGridView来展现datatable的数据,单次或者不频繁的异步设置其DataSource属性不会有任何问题,但是如果实时的异步刷新其数据,不管你是通过异步的调用方式还是采用直接修改数据源,都会导致DataGridView报莫名其妙的错误。

比如下面的应用场景,我们需要一个客户端来显示当前CTI服务器的电话排队情况,这种数据是很实时的。针对这种应用场景,我们有一下几种实现方法,但均会不定期报错

其一:

直接设置DataGridView的DataSource属性,然后直接在监听线程里面修改数据源,模拟程序如下

public Form1()        {            InitializeComponent();            source = new DataTable();            source.Columns.Add("CA");            source.Columns.Add("CB");            source.Columns.Add("CC");        }        DataTable source = null;        Thread thmain = null;        bool run = true;        private void Form1_Load(object sender, EventArgs e)        {            dataGridView1.DataSource = source;        }        private void button1_Click(object sender, EventArgs e)        {            thmain = new Thread(new ThreadStart(main));            thmain.Start();        }        private void main()        {            while (run)            {                Thread.Sleep(500);                if (source.Rows.Count >= 10)                {                    source.Rows.Clear();                }                DataRow dr = source.NewRow();                dr["CA"] = "A-" + DateTime.Now.ToFileTime();                dr["CB"] = "B-" + DateTime.Now.ToFileTime();                dr["CC"] = "C-" + DateTime.Now.ToFileTime();                source.Rows.Add(dr);            }        }        private void button2_Click(object sender, EventArgs e)        {            run = false;        }
此方法会导致GV报如下的错误:


其二:

仍然直接设置datasource属性,但是是通过异步invoke的方式实现

this.Invoke(new UpdateUI(delegate(object o){    gvPhoneList.DataSource = phoneList;}), "");
通过在线程中使用invoke的方式来设置datasource属性,但仍然会报如下的错误:

System.NullReferenceException: 未将对象引用设置到对象的实例。
   在 System.Windows.Forms.DataGridViewLinkCell.PaintPrivate(Graphics g, Rectangle clipBounds, Rectangle cellBounds, Int32 rowIndex, DataGridViewElementStates cellState, Object formattedValue, String errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts, Boolean computeContentBounds, Boolean computeErrorIconBounds, Boolean paint)
   在 System.Windows.Forms.DataGridViewLinkCell.GetContentBounds(Graphics graphics, DataGridViewCellStyle cellStyle, Int32 rowIndex)
   在 System.Windows.Forms.DataGridViewCell.GetContentBounds(Int32 rowIndex)
   在 System.Windows.Forms.DataGridViewLinkCell.MouseMoveUnsharesRow(DataGridViewCellMouseEventArgs e)
   在 System.Windows.Forms.DataGridView.OnCellMouseMove(DataGridViewCellMouseEventArgs e)
   在 System.Windows.Forms.DataGridView.UpdateMouseEnteredCell(HitTestInfo hti, MouseEventArgs e)
   在 System.Windows.Forms.DataGridView.OnMouseMove(MouseEventArgs e)
   在 System.Windows.Forms.Control.WmMouseMove(Message& m)
   在 System.Windows.Forms.Control.WndProc(Message& m)
   在 System.Windows.Forms.DataGridView.WndProc(Message& m)
   在 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   在 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   在 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
后来在CSDN上找到了另外的一种方法,但是仍然会有错,只是错误的消息不一样了而已:

System.NullReferenceException: 未将对象引用设置到对象的实例。
   在 System.Data.DataView.GetRecord(Int32 recordIndex)
   在 System.Data.DataView.GetRow(Int32 index)
   在 System.Data.DataView.System.Collections.IList.get_Item(Int32 recordIndex)
   在 System.Windows.Forms.CurrencyManager.get_Item(Int32 index)
   在 System.Windows.Forms.DataGridView.DataGridViewDataConnection.GetError(Int32 boundColumnIndex, Int32 columnIndex, Int32 rowIndex)
   在 System.Windows.Forms.DataGridViewCell.GetErrorText(Int32 rowIndex)
   在 System.Windows.Forms.DataGridViewLinkCell.GetErrorIconBounds(Graphics graphics, DataGridViewCellStyle cellStyle, Int32 rowIndex)
   在 System.Windows.Forms.DataGridViewCell.GetErrorIconBounds(Int32 rowIndex)
   在 System.Windows.Forms.DataGridViewCell.UpdateCurrentMouseLocation(DataGridViewCellMouseEventArgs e)
   在 System.Windows.Forms.DataGridViewCell.OnMouseMoveInternal(DataGridViewCellMouseEventArgs e)
   在 System.Windows.Forms.DataGridView.OnCellMouseMove(DataGridViewCellMouseEventArgs e)
   在 System.Windows.Forms.DataGridView.UpdateMouseEnteredCell(HitTestInfo hti, MouseEventArgs e)
   在 System.Windows.Forms.DataGridView.OnMouseMove(MouseEventArgs e)
   在 System.Windows.Forms.Control.WmMouseMove(Message& m)
   在 System.Windows.Forms.Control.WndProc(Message& m)
   在 System.Windows.Forms.DataGridView.WndProc(Message& m)
   在 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   在 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   在 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

最后,我们尝试换一种方法来绑定数据:即手动设置数据

this.BeginInvoke(new System.Action(() =>                {                    gvPhoneList.Rows.Clear();                    foreach (DataRow dr in phoneList.Rows)                    {                        gvPhoneList.Rows.Add(dr["NUMBER"], dr["ADDAT"]);                    }                }));
至此,风平浪静,没有在出错了。 

猜测通过DataSource属性设置数据源的时候可能起内部实现可能需要过多的处理,导致在最开始设置给DataSource的对象(取到了某些属性)在最终绑定的时候不一致,导致报错。因此通过手动添加行的方式可以避免掉此类问题!

基于上面的猜测,采用下面的方式来处理DataSource仍然可以解决问题。

this.Invoke(new Action(() =>                {                    dataGridView1.DataSource = source.Copy();                }));
至于详细的原因需要分析datagridview的绑定原理了!