在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)
后来在优快云上找到了另外的一种方法,但是仍然会有错,只是错误的消息不一样了而已:
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的绑定原理了!