R.Tanaka.Ichiro's Blog

主にC# な話題です

目次

Blog 利用状況

ニュース

イベントの連鎖発生のコントロール

以下のような仕様があるとします。


全・1・2・3というチェックボックスが 4 つある。
全をチェックすると、1・2・3のチェックボックスが全てチェックされる。
全のチェックを外すと、1・2・3のチェックボックスの全てのチェックが外れる。
1・2・3のチェックの状態が変更された場合、1・2・3の全てがチェックされていたら全はチェックされ、そうでなければ全のチェックは外れる。


これを何も考えずに書くと、以下のコードになります。
(ちなみに、checkBox1 が全、それ以外が1・2・3だと思って下さい)


var a = new List<CheckBox> { checkBox2, checkBox3, checkBox4 };
foreach(var x in a) {
  x.CheckedChanged += (sender, e) => {
    checkBox1.Checked = (a.Where(p => p.Checked).Count() == 3);
  };
}
checkBox1.CheckedChanged += (sender, e) => {
  a.ForEach(x => x.Checked = checkBox1.Checked);
};


しかし、これでは正常に動作しません。
何故なら、Checked の値を更新する都度、更新されたコントロールの CheckedChanged イベントが連鎖的に発生するからです。

つまりは、最初に発生したイベントによって Checked プロパティを変更した場合、そのコントロールで CheckedChanged イベントが発生しなければ良いのです。
方法は、いくつか考えられますが、この程度の規模なら僕はフラグを使ってしまうことが多いです。


var flag = true;
InitializeComponent();
var a = new List<CheckBox> { checkBox2, checkBox3, checkBox4 };
foreach(var x in a) {
  x.CheckedChanged += (sender, e) => {
    if (!flag) return;
    flag = false;
    checkBox1.Checked = (a.Where(p => p.Checked).Count() == 3);
    flag = true;

  };
}
checkBox1.CheckedChanged += (sender, e) => {
  if (!flag) return;
  flag = false;
  a.ForEach(x => x.Checked = checkBox1.Checked);
  flag = true;
};


フラグを多用する方法は決して推奨できるものではありません。
しかし、最初に発生したイベントによって

二次的に発生したイベントを無視する方法

としては、これ以上複雑にはならないので僕としてはギリギリセーフだと思っています。

他にも、イベントの登録と削除を動的に行う方法や、1・2・3のチェックボックスを一つのコントロールとして独立させるべくユーザーコントロールにする方法も考えられますね。

皆さんは、イベントの連鎖発生をコントロールする場合は、どのようにしているのでしょうか?

投稿日時 : 2008年11月18日 13:39

Feedback

# re: イベントの連鎖発生のコントロール 2008/11/18 14:23 渋木宏明(ひどり)

>ギリギリセーフ

の前に

var flag = 0;

がローカル変数ではなく、クラスメンバであることを強調しておかないと、コピペer が悩むこと必至。

>どのようにしているのでしょうか?

大抵、bool 型のメンバ変数1個で片付けます。

# re: イベントの連鎖発生のコントロール 2008/11/18 14:25 NAL-6295

こんな感じに書いてみました。

private void Form1_Load(object sender, EventArgs e)
{
イベントの登録();
}

private void イベントの登録()
{
var a = new List<CheckBox> { checkBox2, checkBox3, checkBox4 };
foreach (var x in a)
{
x.CheckedChanged += 全選択チェックボックスのONOFF;
}
checkBox1.CheckedChanged += 個別チェックボックスの全選択ONOFF;
}

private void 全選択チェックボックスのONOFF(object sender, EventArgs e)
{
var a = new List<CheckBox> { checkBox2, checkBox3, checkBox4 };
checkBox1.CheckedChanged -= 個別チェックボックスの全選択ONOFF;
checkBox1.Checked = (a.Where(p => p.Checked).Count() == 3);
checkBox1.CheckedChanged += 個別チェックボックスの全選択ONOFF;
}

private void 個別チェックボックスの全選択ONOFF(object sender, EventArgs e)
{
var a = new List<CheckBox> { checkBox2, checkBox3, checkBox4 };
a.ForEach(x => x.CheckedChanged -= 全選択チェックボックスのONOFF);
a.ForEach(x => x.Checked = checkBox1.Checked);
a.ForEach(x => x.CheckedChanged += 全選択チェックボックスのONOFF);
}

# re: イベントの連鎖発生のコントロール 2008/11/18 14:32 みきぬ

> 大抵、bool 型のメンバ変数1個で片付けます。
>
これを聞いてほっとした人 ノ

# re: イベントの連鎖発生のコントロール 2008/11/18 15:39 R・田中一郎

渋木宏明(ひどり) さん
みきぬ さん

あー、なるほど。
実行権限を取った最初の人だけが実行できれば良い訳だから bool でいけますね。
修正しました。

#ローカル変数じゃダメなの?

------------------------------
NAL-6295 さん

>こんな感じに書いてみました。

まぁ、イベントの登録数って多くないから動的に操作しちゃっても良いですよね。

# re: イベントの連鎖発生のコントロール 2008/11/18 15:49 NyaRuRu

a.Where(p => p.Checked).Count() == 3

a.All(p => p.Checked)
でいいのでは?

# re: イベントの連鎖発生のコントロール 2008/11/18 15:56 R・田中一郎

純粋に All を知りませんでした^^;

# re: イベントの連鎖発生のコントロール 2008/11/18 19:52 アキラ

All(全てtrueならtrue)とAny(どれかがtrueならtrue)は便利ですね。
None(全てfalseならtrue)はないのかーw

# re: イベントの連鎖発生のコントロール 2008/11/19 13:15 R・田中一郎

LINQ 系の拡張メソッドは、きちんと頭に入れておかないと損ですね。

# イベントの連鎖発生のコントロール(その2) 2008/11/19 14:32 R.Tanaka.Ichiro's Blog

イベントの連鎖発生のコントロール(その2)

# nQnWsiQfzLwV 2022/04/19 13:25 johnanz

http://imrdsoacha.gov.co/silvitra-120mg-qrms

タイトル
名前
Url
コメント