写在前面,老婆问我一个UI的问题。我开始以为很简单,后来弄了一下,发觉挺难。找了不少资料才解决。在这个过程中学到不少东西,记录下来,以便以后复习。
症状
===========
在DataGridView中的CellEndEdit事件中删除当前行报错,信息如下:
Operation is not valid because it results in a reentrant call to the SetCurrentCellAddressCore function
分析
===========
想要在一个控件的事件中删除掉控件本身,这个恐怕就是问题的根本原因了。我的第一感觉还是挺准的,可惜技术基础不扎实,没能贯彻下去。我最开始想:如果在这个事件处理函数中做不到,那老子再起一个线程,活生生等你这个事件执行结束了之后,再回来把你干掉总可以了吧。谁知又遇到了非创建控件的线程访问控件报exception的问题。
已经是半夜十二点多了,头已经有点昏了。没关系,脑子不好使,有google。
找到了如下的经典解决方案。
//事件处理函数 //不直接删掉该行,而是将要删掉的行存入数组,待另起的线程处理 private void m_dataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e) { PushDelIndex(e.RowIndex); Thread delTh = new Thread(new ThreadStart(delRowThread)); delTh.Start(); } private int[] delIndex = null;//待删行号的数组 private bool delFlag = true;//是否要不间断尝试删除数组中的行 private bool deling = false;//是否有线程正在执行删除操作的锁变量 private void PushDelIndex(int _index) { if(delIndex==null) { delIndex=new int[]{_index}; } else { Array.Resize(ref delIndex,delIndex.Length+1);//展示如何扩充数组 delIndex[delIndex.Length - 1] = _index; } } void delRowThread() { while (delFlag) { if (delIndex != null) { if (deling == false) { delRow(); } } Thread.Sleep(100); } } //此处展现了如何在非创建控件的线程里去修改控件的方法 private delegate void delRowCallBack(); void delRow() { if (PlayList.InvokeRequired) { deling = true; delRowCallBack o = new delRowCallBack(delRow); this.Invoke(o); } else { for (int i = 0; i < delIndex.Length; i++) { PlayList.Rows.RemoveAt(delIndex[i]); } deling = false; delIndex = null; } } }