none
項目を動的に追加する時のイベントハンドラ RRS feed

  • 質問

  • Windows フォーム アプリケーションでContextMenuStripを使いシステムトレイのアイコンでメニューを表示しています。

    項目を動的に追加するためクリックイベントなどのハンドラをどう書けば良いのか判りません。

    ご教示頂ければ幸いです。

    宜しくお願い致します。

    2011年5月20日 10:04

回答

  • 本筋ではありませんが、気になったので少しコメントしておきます。

      private void AddedToolStripMenuItem_Click(object sender, EventArgs e)
      {
       MessageBox.Show(string.Format("「{0}」がクリックされたよ!", (sender as ToolStripItem).Name));
      }
    

     as 演算子を使っている割に、キャストが失敗する(null を返す)ことを考慮していないというコードになっています。
    必ずキャストに成功する前提であれば、キャスト式を用いて、((ToolStripItem)sender) とすることを提案します。

    元のコードでは、万が一キャストに失敗すると NullReferenceException になりますが、キャスト式の場合は InvalidCastException になります。
    後者の方が失敗の原因に近い例外が出るという意味では、ベターではないかと考えています。(あくまで私の意見です)


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2011年5月20日 13:13
    モデレータ
  • たとえば、

    イベントハンドラを共有するために追加されたアイテムのうち、どれがクリックされたのかを知りたい

    というシナリオの場合、
    インデックスで管理するより名前で管理した方が保守的に楽でしょう。

    その場合、前回の回答のメソッドでなく、

    ToolStripItemCollection.Add メソッド (ToolStripItem)
     
    こちらの方を使用して、名前をつけたアイテムのインスタンスを追加するという感じになるかと思います。

    例)

        private void button1_Click(object sender, EventArgs e)
        {
          //メニューアイテムのインスタンスを作成し、諸々の設定をする。(一意な名前を必ずつける)
          var item = new ToolStripMenuItem(
            "追加されたメニューの表示テキスト",
            null,
            AddedToolStripMenuItem_Click
          ) { Name = "一意な名前をつけてね" };
    
          //メニューにメニューアイテムを追加する。
          contextMenuStrip1.Items.Add(item);
     
        }
    
        private void AddedToolStripMenuItem_Click(object sender, EventArgs e)
        {
          MessageBox.Show(string.Format("「{0}」がクリックされたよ!", (sender as ToolStripItem).Name));
        }
    

    よろしくお願い致します。

    2011年5月20日 11:56
  • >項目を動的に追加するためクリックイベントなどのハンドラをどう書けば良いのか判りません。

    あらかじめ分かっている範囲内でメニュー項目が増減するなら、メニュー項目の表示・非表示で制御したほうが楽に書ける場合もあります。

    >上記はイベントハンドラをその場で定義していますが、別メソッドにしてもOKです。

    動的に追加した項目を、あとで動的に削除する場合、動的に接続したイベントを解除する必要があります。

    そういった場合、イベントハンドラを直接ラムダ式や匿名デリゲートで記述するとイベント接続が解除不能になって困ります。

     

     

    2011年5月20日 15:42
    モデレータ

すべての返信

  • こんにちは。
    動的に追加したメニューにクリックイベントを登録したいってことですよね?

    でしたら
    ToolStripItemCollection.Add メソッド (String, Image, EventHandler)
    を使用します。

    例)

          contextMenuStrip1.Items.Add(
            "追加されたメニューの表示テキスト",
            null, 
            (s, ea) => { MessageBox.Show("追加されたメニューが押されたよ"); }
          );
    
    

    上記はイベントハンドラをその場で定義していますが、別メソッドにしてもOKです。

    以上、参考になりましたら幸いです。

    2011年5月20日 10:23
  • 有難うございます。

    お陰さまでイベントハンドラが書けました。

    あと、クリックした項目のインデックスはどう拾えば良いのでしょうか?

    引き続き宜しくお願い致します。

    2011年5月20日 11:04
  • >あと、クリックした項目のインデックスはどう拾えば良いのでしょうか?

    目的は何でしょうか?

    あまりメニューのインデックス拾わないといけない局面に遭遇したことがないので。。。

    もしかするとインデックスを拾うより良いやり方があるかもしれませんので聞いてみました。

    よろしくお願い致します。

    2011年5月20日 11:29
  • たとえば、

    イベントハンドラを共有するために追加されたアイテムのうち、どれがクリックされたのかを知りたい

    というシナリオの場合、
    インデックスで管理するより名前で管理した方が保守的に楽でしょう。

    その場合、前回の回答のメソッドでなく、

    ToolStripItemCollection.Add メソッド (ToolStripItem)
     
    こちらの方を使用して、名前をつけたアイテムのインスタンスを追加するという感じになるかと思います。

    例)

        private void button1_Click(object sender, EventArgs e)
        {
          //メニューアイテムのインスタンスを作成し、諸々の設定をする。(一意な名前を必ずつける)
          var item = new ToolStripMenuItem(
            "追加されたメニューの表示テキスト",
            null,
            AddedToolStripMenuItem_Click
          ) { Name = "一意な名前をつけてね" };
    
          //メニューにメニューアイテムを追加する。
          contextMenuStrip1.Items.Add(item);
     
        }
    
        private void AddedToolStripMenuItem_Click(object sender, EventArgs e)
        {
          MessageBox.Show(string.Format("「{0}」がクリックされたよ!", (sender as ToolStripItem).Name));
        }
    

    よろしくお願い致します。

    2011年5月20日 11:56
  • 本筋ではありませんが、気になったので少しコメントしておきます。

      private void AddedToolStripMenuItem_Click(object sender, EventArgs e)
      {
       MessageBox.Show(string.Format("「{0}」がクリックされたよ!", (sender as ToolStripItem).Name));
      }
    

     as 演算子を使っている割に、キャストが失敗する(null を返す)ことを考慮していないというコードになっています。
    必ずキャストに成功する前提であれば、キャスト式を用いて、((ToolStripItem)sender) とすることを提案します。

    元のコードでは、万が一キャストに失敗すると NullReferenceException になりますが、キャスト式の場合は InvalidCastException になります。
    後者の方が失敗の原因に近い例外が出るという意味では、ベターではないかと考えています。(あくまで私の意見です)


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2011年5月20日 13:13
    モデレータ
  • >Azuleanさん

    こんにちは。
    ご指摘ありがとうございます。

    昔所属していたPJのコーディングルールに、
    「参照型のキャストはas演算子を使うべし。理由はパフォーマンスが良いから」
    というものがあり、どっちでも良い場合(必ずキャストに成功する前提の場合)
    無意識のうちにasを使って書いていたようです。

    今、本当にパフォーマンスが良いのか調べ直してみましたら
    演算子を使った式単体ですとそんなに変わらないようでした。

    # 100000回ループで
    # asは 00:06:40.0118794
    # ()は 00:06:55.3677577

    おそらく、そのコーディングルールを作成した人は
    ■@IT:.NET TIPS as演算子とキャストの違いは? - C#(ずいぶんと古い記事ですが)
    http://www.atmarkit.co.jp/fdotnet/dotnettips/005castandas/castandas.html

    の結果だけ見て書いたのではないかと、今になって考えました。
    (所属していた時に気づけば良かったですが。。。)

    パフォーマンスに違いがあまりないなら、
    必ずキャストに成功する前提ならばAzuleanさんのおっしゃるとおり、
    () 演算子を使った方が万々が一変な型を入れられた時の例外情報の観点から合理的ですね。

    考えるきっかけを与えてくださって大変ありがとうございました。

    2011年5月20日 14:45
  • >項目を動的に追加するためクリックイベントなどのハンドラをどう書けば良いのか判りません。

    あらかじめ分かっている範囲内でメニュー項目が増減するなら、メニュー項目の表示・非表示で制御したほうが楽に書ける場合もあります。

    >上記はイベントハンドラをその場で定義していますが、別メソッドにしてもOKです。

    動的に追加した項目を、あとで動的に削除する場合、動的に接続したイベントを解除する必要があります。

    そういった場合、イベントハンドラを直接ラムダ式や匿名デリゲートで記述するとイベント接続が解除不能になって困ります。

     

     

    2011年5月20日 15:42
    モデレータ
  • こんばんは。

    >渋木宏明さん、

    >動的に追加した項目を、あとで動的に削除する場合、動的に接続したイベントを解除する必要があります。
    >そういった場合、イベントハンドラを直接ラムダ式や匿名デリゲートで記述するとイベント接続が解除不能になって困ります。

    普通にRemoveメソッドで削除すればよい(イベントを解除する必要がない)と思うのですが。。。
    よろしければ、なぜイベントを解除する必要があるのか教えていただけませんか?

    よろしくお願い致します。
    2011年5月20日 16:54
  • >普通にRemoveメソッドで削除すればよい(イベントを解除する必要がない)と思うのですが。。。

    ラムダ式や匿名デリゲートを直接 AddHandler してしまうと、何を RemoveHandler に指定するべきでしょうか?

    AddHandler するラムダ式や匿名デリゲートをメンバ変数などに対比しておけば回避できますが、それだとメンバメソッドを AddHandler するのと代わり映えしないのでは。

    >よろしければ、なぜイベントを解除する必要があるのか教えていただけませんか?

    すべての場合に当てはまるわけではありませんが、例えば、2つのオブジェクトインスタンス間で相互にイベント接続をしているような場合、コード上は片方のインスタンスへの参照が失われているように見えても、イベント接続による参照が残っていて、期待と異なる動作をする場合があります。

     

    2011年5月21日 5:21
    モデレータ
  • >2つのオブジェクトインスタンス間で相互にイベント接続をしているような場合

    とか、混みいったことをする場合に気をつけなければいかないって話なので、イベントハンドラにラムダ式や匿名デリゲートを AddHandler する場合は気にしなくてもいいですね。

    細かいこと心配しすぎました。すみません。

     

     


    2011年5月21日 5:57
    モデレータ
  • >渋木さん
    ありがとうございます。

    私の質問にも舌足らずな部分があったようですみません。

    >普通にRemoveメソッドで削除すればよい

    は、
    ContextMenuStrip.ItemsのRemoveメソッドのつもりでした。

    >イベントハンドラを直接ラムダ式や匿名デリゲートで記述するとイベント接続が解除不能
    ということは知っています。
    が、
    ・サンプルの性格上、できるだけコードを短くしようという考え方があった

    という理由により、上記サンプルのように書きました。

    そこで、
    >動的に追加した項目を、あとで動的に削除する場合、動的に接続したイベントを解除する必要があります。
    の投稿をされたので、「なぜ動的に接続したイベントを解除する必要があるのだろう?」と疑問に思った次第です。

    渋木さんも書いておられますが、常にイベントを解除する必要はないですよね。
    (ソースがリスナより寿命が短い場合はソースもろとも消滅してくれれば良いので)

    2011年5月21日 6:36
  • お世話になります。

    Tetsuaki Uchidaさん:

    コンボボックスとイベントハンドラを共有することが目的です。

    ToolStripItemCollection.Addで上手くいきました。

    Azuleanさん:

    as 演算子は使ったことがないのでキャスト式にしました。

    渋木宏明さん:

    使用する項目は10前後ですが、表示したい項目名は100以上の中から選択するので動的に処理しました。

    皆様のお陰で目的を達成出来そうです。

    ご教示有難う御座いました。

    2011年5月22日 2:22