.net System.Windows.Forms.ListViewのカラムヘッダー右クリックに対応する
タイトルの通りのことをしようとしたら嵌ったのでメモ。
ダメなこと
- ColumnClickは左クリックのみ
- MouseDown/MouseClickはデータ領域のみ
解決策
- ContextMenuStripを追加してListViewに設定する(メニュー出す気がなくても)
- contextMenuStripのOpeningイベントで右クリックを処理する
この時メニュー出さないならCancelしたらよさげ - どこでクリックしたかは、HitTestさんが結構がんばってくれる(でも補助が必要)
実際のOpeningイベント処理
下記コードでは、ListViewはlistViewMain、ContextMenuStripはcontextMenuStripListViewになっとりやす。
Log関数は適当に作っといて。
private void contextMenuStripListView_Opening(object sender, System.ComponentModel.CancelEventArgs e) { var pos = listViewMain.PointToClient(Cursor.Position); var top = listViewMain.TopItem.Bounds.Top; if (pos.Y < top) pos.Y = top - 1; var lvhti = listViewMain.HitTest(pos); if (lvhti.Item != null && lvhti.SubItem != null) { if (pos.Y < top) { var si = listViewMain.Items[lvhti.Item.Index].SubItems.IndexOf(lvhti.SubItem); Log(string.Format("contextMenuStripListView_Opening Header {0} {1} {2} {3}", pos.X, pos.Y, si, listViewMain.Columns[si].Text)); } else { Log(string.Format("contextMenuStripListView_Opening {0} {1} {2} {3}", pos.X, pos.Y, lvhti.Item.Index, lvhti.SubItem.Text)); } } else { Log(string.Format("contextMenuStripListView_Opening Blank {0} {1}", pos.X, pos.Y)); } e.Cancel = true; }
注意点
ListViewのHitTestには細かい注意点がある。
- ヘッダーの下の方でクリックすると、(画面上)TopのアイテムにHitする
- ヘッダーの上の方でクリックすると、何もHitしない
データ行の高さ分より高いとダメそう
なので、一件目より上(ようはヘッダー部)をクリックした場合は、一件目のすぐ上をクリックしたことにする - SubItemがないと、Hitできない。すなわち一件目のSubItemがないとヘッダーのどこかわからない
そんなわけで、未設定のSubItemがある場合、上のコードではダメ。
もうちょっとベタな調査が必要。
private void contextMenuStripListView_Opening(object sender, System.ComponentModel.CancelEventArgs e) { var pos = listViewMain.PointToClient(Cursor.Position); var top = listViewMain.TopItem.Bounds.Top; if (pos.Y < top) pos.Y = top - 1; var lvhti = listViewMain.HitTest(pos); int sc = listViewMain.Columns.Count, si = sc; if (lvhti.Item != null && lvhti.SubItem != null) { si = listViewMain.Items[lvhti.Item.Index].SubItems.IndexOf(lvhti.SubItem); } else { var x = pos.X; for (si = 0; si < sc; si++) { x -= listViewMain.Columns[si].Width; if (x < 0) { break; } } } if (lvhti.Item != null && si < sc) { if (pos.Y < top) { Log(string.Format("contextMenuStripListView_Opening Header {0} {1} {2} {3}", pos.X, pos.Y, si, listViewMain.Columns[si].Text)); } else { Log(string.Format("contextMenuStripListView_Opening Data {0} {1} {2} {3}", pos.X, pos.Y, lvhti.Item.Index, si)); } } else { Log(string.Format("contextMenuStripListView_Opening Blank {0} {1}", pos.X, pos.Y)); } e.Cancel = true; }
ループするしか思いつかなかった、、、。
この方法だと、Data行にSubItemがない場合もBlankにならないで済みますです。
ループがダサいねぇ。
ヘッダーの幅が固定な場合は、もうちょっと上手くできそう。