none
SelectedIndexChanged イベントの発生設定を確認する方法 RRS feed

  • 質問

  • SelectedIndexChanged イベントが発生する状態に設定されているかどうかを調べる方法はあるのでしょうか。
    というのも、
    this.ComboBox1.SelectedIndexChanged += new System.EventHandler(ComboBox1_SelectedIndexChanged);
    を2度繰り返すと、イベントが2度発生してしまうし、
    += を -= にしたときに、2度発生する状態のものが0になるわけではないので、

    1.発生する状態かどうかを確かめるか、
    2.発生しない状態にしてしまうか
    のどちらかの方法がないと、何度 += や -= のコードを通ったかを確かめることが出来ないので、Yes / No 操作が出来ずに困っています。

    この辺りを宜しくお願い致します。

    2011年1月8日 17:41

回答

すべての返信

  • SelectionChangeCommittedイベント にはこう書かれています。

    SelectionChangeCommitted は、ユーザーがコンボ ボックスの選択項目を変更したときにのみ発生します。 SelectedIndexChanged イベントと SelectedValueChanged イベントは、プログラム上で選択項目が変更されたときにも発生するため、ユーザーによる変更をキャプチャする目的でこれらのイベントを使用しないでください。

    ユーザーがリスト内の選択した項目を変更するときに、SelectionChangeCommitted イベント ハンドラーを作成して ComboBox に対して特別な処理を実行できます。
    2011年1月8日 22:55
  • イベントが2回発生したり発生しなかったりするのではなく、ハンドラが2回呼ばれたり1回も呼ばれなかったりするんですよね。

    こうしてはどうですか?

    // ハンドラインスタンスはフィールドとして持っておく
    private System.EventHandler handler = new System.EventHandler(ComboBox1_SelectedIndexChanged);
    
    :
    :
    
    // ハンドラを仕込むとき
    lock (handler) ComboBox1.SelectedIndexChanged += handler;
    
    :
    :
    
    // ハンドラの処理では、まず自分をイベントから外す
    protected void ComboBox1_SelectedIndexChanged(Object sender,EventArgs e)
    {
    	lock (handler) ComboBox1.SelectedIndexChanged -= handler;
    	:
    	:
    }
    
    ハンドラは仕込んだあと1回だけ呼ばれ、呼ばれたらもう呼ばれなくなります。

    2011年1月8日 23:23
  • 正直に書きますと、何を質問したいのかがわかりづらいです。

    SelectedIndexChanged イベントが発生する状態に設定されているかどうかを調べる方法はあるのでしょうか。

    これは「イベントハンドラをすでに設定したかどうか知る方法がないか?」という質問でしょうか?
    そうだと仮定したとしても、正直、その質問が発生する経緯がよくわかりません。

    少なくとも、イベント側にはそういった仕組みはないので、どうしても必要なら bool 型変数でも用意して、セットしたかどうかを true/false で管理すれば何ともできるでしょう。

    イメージ:
    void イベント設定()
    {
      if (_isSet) return;
      _isSet = true;
      target.hogehoge += nantoka_event;
    }
    void イベント解除()
    {
      if (!_isSet) return;
      target.hogehoge -= nantoka_event;
      _isSet = false;
    }

    こういったようにすればガードできます。
    しかし、こういったガードが必要になる現状の作り方に問題があるように思います。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2011年1月9日 1:02
    モデレータ
  • 以前、以下のような質問がありました。参考になるのではないでしょうか?

    イベントの削除・入替方法は?
    http://social.msdn.microsoft.com/forums/ja-JP/csharpgeneralja/thread/d723e83e-f8cf-443d-89ef-3b257ec7c79b/

    具体的には上記に載っているEventDatumクラスを利用すれば良いと思います。
    ただ、他の方も言われてますが、イベントハンドラを複数セットしないように管理できれば、それが一番良いので、まずはそこを見直されることをお勧めします。

     #(追記)上記のスレッドに、

    イベントの存在チェック
    http://social.msdn.microsoft.com/forums/ja-JP/vbgeneralja/thread/8812fa50-b4f4-4bbe-8079-ac02ff4c9805/

    があります。イベントの存在チェックだけでよければこちらの方が良さそうです。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年1月9日 1:13
    モデレータ
  • bool 型変数 を用意してみた上での質問ではありました。

    この方法ですと常にチェックが必要になりそうです。
    希望としてはどんな状態の時も、単純にOnにしたいときはOnにし、OffにしたければOffに出来るようにしたいというものです。

    また、ぜひお伺いしたいのですが、「こういったガードが必要になる現状の作り方に問題がある」ということについて、もう少し詳しい内容を頂けると有難いと思います。
    知識や熟練による能力が違うためなのでしょうけれど、根本的な問題になると漠然としたことしか思い浮かばないのです。

    2011年1月9日 9:59
  • この方法ですと常にチェックが必要になりそうです。
    希望としてはどんな状態の時も、単純にOnにしたいときはOnにし、OffにしたければOffに出来るようにしたいというものです。

    これはどんな方法であれ、何かチェックがいると思います。

    1.ご提示の通り、イベントハンドラ自体を入れたり、抜いたりする。
    2.イベントハンドラ自体は初期化の段階で一度セットしておき、ハンドラの中で処理する・しないを分岐する。

    また、ぜひお伺いしたいのですが、「こういったガードが必要になる現状の作り方に問題がある」ということについて、もう少し詳しい内容を頂けると有難いと思います。

    断言してしまいましたが、どういった状況かを確認できていなかったので、断言すべきではなかったのかもしれません。
    頭にあったのは、「自分(あるクラス・ある処理)がどういう状態か知らないというのは、作りとしてまずいのではないか」ということです。

    bool 型なり、列挙型なりで自分がどういう状態(例:ハンドラ設定済み、未設定)を知っておくような作り方であれば、問題ないということで訂正させてください。(断言してしまい、申し訳ありません)


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2011年1月9日 11:02
    モデレータ
  • ご参考になるかどうか分かりませんが、イベントのラッパークラスを書いてみました。

      class EventWrapper<TEventArgs>
        where TEventArgs : EventArgs
      {
        event Action<object, TEventArgs> myEvent;
    
        public Action<object, TEventArgs> Handler
        {
          get { return HandlerImplement; }
        }
    
        void HandlerImplement(object sender, TEventArgs e)
        {
          if (IsAttached)
          {
            myEvent(sender, e);
          }
        }
    
        public void Attach(Action<object, TEventArgs> handler)
        {
          Clear();
          myEvent += handler;
        }
    
        public void Clear()
        {
          myEvent = null;
        }
    
        public bool IsAttached
        {
          get { return myEvent != null; }
        }
      }
    
    


    このクラスを次のように使うと、イベントハンドラーを登録したり、解除したり、状態を確認したりできます。
    (この例はButton.Clickイベントですが、イベントハンドラー型とイベント引数型を変えれば他の種類のイベントでも使えると思います)
      public partial class Form1 : Form
      {
        EventWrapper<EventArgs> button2ClickWrapper = new EventWrapper<EventArgs>();
    
        public Form1()
        {
          InitializeComponent();
          button1.Click += button1_Click;
    
          //button2_Clickメソッドを直接登録する代わりに、ラッパークラスのイベントハンドラーを登録します
          button2.Click += new EventHandler(button2ClickWrapper.Handler);
        }
    
        void button1_Click(object sender, EventArgs e)
        {
          //button1をクリックするたびに、button2のイベントハンドラーを登録したり解除したりします
          if (button2ClickWrapper.IsAttached)
          {
            button2ClickWrapper.Clear();
            MessageBox.Show("button2のClickイベントハンドラーを解除しました");
          }
          else
          {
            button2ClickWrapper.Attach(button2_Click);
            MessageBox.Show("button2のClickにイベントハンドラーを登録しました");
          }
        }
    
        void button2_Click(object sender, EventArgs e)
        {
          MessageBox.Show("button2のClickイベントハンドラーを実行しました");
        }
      }
    
    

    イベントの管理は、イベントを実装しているクラス(発生元)でしかできません。それなら自前で用意してしまえ、という単純な発想です。
    ご参考になれば幸いです。

    2011年1月9日 13:42
  • yasheeki さん
    > 希望としてはどんな状態の時も、単純にOnにしたいときはOnにし、OffにしたければOffに出来るようにしたいというものです。

    以下のように判断しない方法はいかがでしょうか?
    これでしたら現状の += の行の前に -= の行を1行追加するだけなので、単純に対処できると思います。

    ●ハンドラを割り当てるとき
    // (無条件に)先に解除してみてから割り当てる。
    this.ComboBox1.SelectedIndexChanged -=
        new System.EventHandler(ComboBox1_SelectedIndexChanged);
    this.ComboBox1.SelectedIndexChanged +=
        new System.EventHandler(ComboBox1_SelectedIndexChanged);

    ●ハンドラを解除するとき
    this.ComboBox1.SelectedIndexChanged -=
        new System.EventHandler(ComboBox1_SelectedIndexChanged);

    ただし、割り当て済みかどうかを状態管理すべき場合もあるとは思います。
    その場合は、「イベントが割り当てられているか」を調べなくても、「イベントを割り当てたか」を示すフラグ等を用意するだけで済むように思います。

    なお、デリゲートでの値の等価は以下のように判定されます(匿名メソッド等がターゲットの場合は別)。

    var x = new EventHandler(ComboBox1_SelectedIndexChanged);
    var y = new EventHandler(ComboBox1_SelectedIndexChanged);
    var z = x == y; // true

    Delegate.Equals メソッド
    http://msdn.microsoft.com/ja-jp/library/99bthb1z(v=VS.100).aspx

    なので、もしハンドラが自身のメンバーだったりするなどでしたら、miuras_net さんのように割り当てたデリゲートを保持しておかなくても、-= の時点で new したものを -= に渡せます。
    (保持しないといけない場合ももちろんありますし、保持しておけばそれをフラグにも使えますけどね)

    2011年1月10日 1:44
  • ありがとうございます。

    難しい部分も多々ありますが、参考になりました。

    2011年1月10日 16:20
  • newしたもの同士でも等価判定されるんですね。勉強になりました。ありがとうございます。
    2011年1月11日 2:52
  • ありがとうございます。これは、いい解決方法ですね。

    難しく考えずに出来る方法として最適のように感じました。

    2011年1月11日 10:38