背景
ListViewをDetailsモードで使っているとき、コンテキストメニューを行コマンドとして扱う、ってのは自然な事ですよね
この場合、「有効な行」の上でのコンテキストメニューと、「有効な行がない」時にでるコンテキストメニューは項目が違ったりします
でもこのコンテキストメニュー、ヘッダ行の上でも出てきちゃうんです(ListViewに対するコンテキストメニューなので、当然っちゃ当然ですが)
でもヘッダ領域ってのは、「有効な行」であるとは到底言えませんが、「有効な行ではない」場所と断定されても困るなぁ…
と云う、お・は・な・し
方針
当然基本的には、ContextMenuStripのOpeningイベントハンドラ内で、マウスクリック位置がヘッダであったらメニューを開かなければ良いだけです
だけ、何ですけどね…
ヘッダ行上でマウスクリックが起きたのかを判定する簡単で直接的な方法が見つからないんです
で、それを判定する方法を纏めてみたいと思います
実験①
取り敢えず何も考えずにコンテキストメニュー
実験②
有効な行があるか否かでメニューを変える
ここでは、ListViewのSelectedItemsにListViewItemがあるか無いかで判定
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) {
if(listView1.SelectedItems.Count == 0) {
appendToolStripMenuItem.Visible = true;
deleteToolStripMenuItem.Visible = false;
} else {
appendToolStripMenuItem.Visible = false;
deleteToolStripMenuItem.Visible = true;
}
}
で、結果
うーん、使えない
実験③
標準で準備されているプロパティ~Item(s)とかは余りアテにならなそう
こういう時はListView.HitTest()を使え!
と先人達が仰っておられる
つまり、何があるかは自分で判断しなさい、と云う事ですね
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) {
Point pnt = listView1.PointToClient(Cursor.Position);
if(listView1.HitTest(pnt).Item == null) {
appendToolStripMenuItem.Visible = true;
deleteToolStripMenuItem.Visible = false;
} else {
appendToolStripMenuItem.Visible = false;
deleteToolStripMenuItem.Visible = true;
}
}
うむむー…
行のない処では期待通りの動きをしているが、ヘッダ領域ではあたかも有効な行が存在するかの様な判定結果になっている
対策
ListViewの云う事を素直に聞けないならばどうすれば良いのか?
それは、関係者の意見を集めて矛盾点を衝く!
という事で以下の作戦を立てる
- ListViewが云うところの、カレントなItem君を取得する
- カレントなItem君に自身が画面上に占める領域を答えて貰う
- 上記領域と現在のマウスの位置関係を明らかにする
ここで、カレント君の中にマウスがいなければListViewはウソを吐いているっ!
と云う事で以下のコードで対応
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) {
Point pnt = listView1.PointToClient(Cursor.Position);
ListViewItem item = listView1.HitTest(pnt).Item;
if(item == null) {
appendToolStripMenuItem.Visible = true;
deleteToolStripMenuItem.Visible = false;
} else if(item.Bounds.Contains(pnt)) {
appendToolStripMenuItem.Visible = false;
deleteToolStripMenuItem.Visible = true;
} else {
e.Cancel = true;
}
}
めでたしめでたし