虽然DataGridView单双击事件都有,但双击事件其实也会触发单击事件的处理,所以如果双击事件和单击事件的行为不同,或者双击时不想触发单击事件,或者单击事件会阻塞双击事件的处理时(比如单击后会有弹窗),就需要自行去实现了。
- 整体思路:
- 对于DataGridView当前页的每一行,维护一份点击信息,用于后续判断和处理。
- 两个线程或者说处理逻辑之间互相依赖。主线程的事件处理依赖timer线程去清理数据,比如在判断是否发生了双击时,其实隐含依赖于timer线程处理中未超时清理第一次点击标识;timer线程的处理逻辑依赖主线程的是否发生过一次点击和是否发生了双击的标识。
- 当点击某一行时(UI主线程)
- 如果是第一次点击,则标识这一行已经点击过一次了,并启动一个timer开始计时和处理(另一个线程)。
- 如果是第二次点击,则标识这一行发生了双击,这个标识用于timer中判断是否触发双击事件处理函数。
- timer的处理逻辑(timer线程)
- 这个timer运行到系统认为的双击间隔时间后(一定要跑到这个时间点,否则不知道是否会再有第二次点击)
- 如果没有再次发生第二次单击,则认为这一行是发生了双击事件,然后调用双击事件处理函数后,最后清理数据(以便下次判断单双击和计时)。
- 如果已经发生了第二次单击,则认为这一行是发生了单击事件,然后调用单击事件处理函数后,最后清理数据(以便下次判断单双击和计时)。
- 这个timer运行到系统认为的双击间隔时间后(一定要跑到这个时间点,否则不知道是否会再有第二次点击)
- 当点击某一行时(UI主线程)
- 缺点:即使只是单击,也要等到双击间隔接触才能确认不是单击,才能触发单击事件处理函数,感觉上有一点延迟,但这个逻辑貌似也是合理的。
- 即使发生连续点击多次的情况,也可以近似完美处理,因为主线程的事件处理肯定触发多次,第二次点击时已经设为了双击,timer事实上是只有达到双击间隔时才触发处理逻辑,并没有清理数据,所以大不了设置多次的双击标识。但timer的间隔可以考虑设置短一点,因为达到双击间隔后,最差情况下会经过一个间隔时间才会触发处理逻辑。
如果是自定义的一个DataGridView控件,需要定义一个单独的类,作为内部的列表或数组变量去储存DataGridView每一行对应的点击信息,用来区分单击还是双击。
1 /// <summary> 2 /// saved row click info to identify whether this mouse click is double click 3 /// </summary> 4 private RowClickInfo[] rowClickInfoOfCurrentPage;
详细类定义如下:
1 /// <summary>
2 /// click info for each data row to distinguish click and double click, and has a timer to execute single/double click action
3 /// </summary>
4 class RowClickInfo
5 {
6 public int CurRowIndex { get; set; }
7
8 /// <summary>
9 /// mark if the first click happened
10 /// </summary>
11 public bool HasFirstClickHappened { get; set; }
12
13 public bool IsDoubleClick { get; set; }
14
15 private int _milliseconds { get; set; }
16
17 /// <summary>
18 /// this timer is a must, or we don't know when to do single or double click action only using CellMouseDown event
19 /// </summary>
20 public System.Windows.Forms.Timer DoubleClickTimer { get; set; }
21
22 private const int _timerInterval = 100;
23
24 public RowClickInfo()
25 {
26 HasFirstClickHappened = false;
27 IsDoubleClick = false;
28 _milliseconds = 0;
29
30 DoubleClickTimer = new System.Windows.Forms.Timer();
31 DoubleClickTimer.Interval = _timerInterval;
32 DoubleClickTimer.Tick += new EventHandler(_doubleClickTimer_Tick);
33 }
34
35 private void _doubleClickTimer_Tick(object sender, EventArgs e)
36 {
37 _milliseconds += _timerInterval;
38
39 // if exceed double click time span, stop timer, do single/double click action and clear data
40 if (_milliseconds >= SystemInformation.DoubleClickTime)
41 {
42 DoubleClickTimer.Stop();
43
44 if (IsDoubleClick)
45 {
46 RowDoubleClick(CurRowIndex);
47 }
48 else
49 {
50 RowSingleClick(CurRowIndex);
51 }
52
53 // clear data
54 IsDoubleClick = false;
55 HasFirstClickHappened = false;
56 _milliseconds = 0;
57 }
58 }
59
60 public delegate void RowSingleClickHandler(int curRowIndex);
61 public event RowSingleClickHandler RowSingleClick;
62
63 public delegate void RowDoubleClickHandler(int curRowIndex);
64 public event RowDoubleClickHandler RowDoubleClick;
65 }
然后在绑定数据后要初始化当前页所有行的点击信息类:
1 // init row click info 2 rowClickInfoOfCurrentPage = new RowClickInfo[this.pageSize]; 3 for (int i = 0; i < this.pageSize; i++) 4 { 5 rowClickInfoOfCurrentPage[i] = new RowClickInfo(); 6 rowClickInfoOfCurrentPage[i].CurRowIndex = i; 7 rowClickInfoOfCurrentPage[i].RowSingleClick += this.DataSourceRowClick; 8 rowClickInfoOfCurrentPage[i].RowDoubleClick += this.DataSourceRowDoubleClick; 9 }
接着在构造函数中绑定自定义的点击事件处理函数:
1 this.CellMouseDown += new DataGridViewCellMouseEventHandler(CellMouseDownHandler);
具体的点击事件处理函数如下:
1 /// <summary> 2 /// handle mouse down event of both single and double click event 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void CellMouseDownHandler(object sender, DataGridViewCellMouseEventArgs e) 7 { 8 int rowIndex = e.RowIndex; 9 if (rowIndex >= 0) 10 { 11 RowClickInfo curRowClickInfo = rowClickInfoOfCurrentPage[rowIndex]; 12 if (!curRowClickInfo.HasFirstClickHappened) 13 { 14 // triggered the first time 15 curRowClickInfo.HasFirstClickHappened = true; 16 curRowClickInfo.DoubleClickTimer.Start(); 17 } 18 else 19 { 20 // triggered the second time and it must be double click, 21 // because HasFirstClickHappened will change to false again if interval is too long 22 curRowClickInfo.IsDoubleClick = true; 23 } 24 } 25 }