Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

背景

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

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

方針

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

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

実験①

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

  • 有効な行の上で右クリック
    image
  • 行のない処で右クリック
    image
  • カラムヘッダ領域でクリック
    image

実験②

有効な行があるか否かでメニューを変える
ここでは、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;
    }
}

で、結果

  • 有効な行が存在するところ
    image
  • 行が存在しないところ
    image
  • カラムヘッダ… は、行が選択状態にあるかどうかで挙動が変わる…と
    image image

うーん、使えない

実験③

標準で準備されているプロパティ~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;
    }
}
  • 有効な行の上で右クリック
    image
  • 行のない処で右クリック
    image
  • カラムヘッダ領域でクリック
    image

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

対策

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

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

ここで、カレント君の中にマウスがいなければ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;
    }
}
  • 有効な行の上
    image
  • 行のない処
    image
  • カラムヘッダの上では?
    image
    静止画では良く分からないかも知れませんが、コンテキストメニューは出なくなりました
    とさ…

めでたしめでたし

Toraja
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away