ListView(Details)のカラムヘッダ上でコンテキストメニューを出したくないっ！


# 背景
ListViewをDetailsモードで使っているとき、コンテキストメニューを行コマンドとして扱う、ってのは自然な事ですよね
この場合、「有効な行」の上でのコンテキストメニューと、「有効な行がない」時にでるコンテキストメニューは項目が違ったりします

でもこのコンテキストメニュー、ヘッダ行の上でも出てきちゃうんです（ListViewに対するコンテキストメニューなので、当然っちゃ当然ですが）
でもヘッダ領域ってのは、「有効な行」であるとは到底言えませんが、「有効な行ではない」場所と断定されても困るなぁ…
と云う、お・は・な・し

# 方針
当然基本的には、ContextMenuStripのOpeningイベントハンドラ内で、マウスクリック位置がヘッダであったらメニューを開かなければ良いだけです

だけ、何ですけどね…
ヘッダ行上でマウスクリックが起きたのかを判定する簡単で直接的な方法が見つからないんです
で、それを判定する方法を纏めてみたいと思います

# 実験①
取り敢えず何も考えずにコンテキストメニュー

* 有効な行の上で右クリック<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/33ad8a6d-eef5-faca-3d9c-4ecaeee7dd12.png)
* 行のない処で右クリック<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/0d2ddfe2-491e-ba25-b633-cbadb54361e8.png)
* カラムヘッダ領域でクリック<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/dff81037-6e10-f90f-8dc7-c1866d20e3f1.png)

# 実験②
有効な行があるか否かでメニューを変える
ここでは、ListViewのSelectedItemsにListViewItemがあるか無いかで判定

```csharp
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;
	}
}
```

で、結果

* 有効な行が存在するところ<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/d78d957f-ab5e-453c-fe97-b5c55216c837.png)
* 行が存在しないところ<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/0c03b4b9-dca5-08ff-8eb7-0d3545817f64.png)
* カラムヘッダ…
は、行が選択状態にあるかどうかで挙動が変わる…と<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/6ce49480-48a5-72e5-117e-1d6f5a55b96e.png)
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/a3273f37-3ef4-8f78-ffc8-1cb870bc55db.png)

うーん、使えない

# 実験③
標準で準備されているプロパティ～Item(s)とかは余りアテにならなそう
こういう時はListView.HitTest()を使え！
と先人達が仰っておられる
つまり、何があるかは自分で判断しなさい、と云う事ですね

```csharp
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;
    }
}
```

* 有効な行の上で右クリック<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/71c3af79-21ef-3b38-7dab-0ed78104ee87.png)
* 行のない処で右クリック<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/d64207e0-a214-7b3b-d601-8095d9e859e6.png)
* カラムヘッダ領域でクリック<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/2bbe5ff6-0e9a-a65d-b777-080462e0c3f4.png)

うむむー…
行のない処では期待通りの動きをしているが、ヘッダ領域ではあたかも有効な行が存在するかの様な判定結果になっている

# 対策
ListViewの云う事を素直に聞けないならばどうすれば良いのか？
それは、関係者の意見を集めて矛盾点を衝く！
という事で以下の作戦を立てる

1.  ListViewが云うところの、カレントなItem君を取得する
2.  カレントなItem君に自身が画面上に占める領域を答えて貰う
3.  上記領域と現在のマウスの位置関係を明らかにする

ここで、カレント君の中にマウスがいなければListViewはウソを吐いているっ！

と云う事で以下のコードで対応

```csharp
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;
	}
}
```

* 有効な行の上<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/25899c40-4517-f578-0159-dff97e37d164.png)
* 行のない処<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/72d1bb7e-6c2f-f9f7-adda-648caf77165f.png)
* カラムヘッダの上では？<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/949f2c59-4823-f587-51df-3abdfec77daf.png)<br/>
静止画では良く分からないかも知れませんが、コンテキストメニューは出なくなりました<br/>
とさ…

めでたしめでたし