2009-04-08 2 views
9

イベントがコンボボックスからアプリケーションで発生するユーザーの操作と自分のコードで発生するイベントを区別する

イベント:

  1. ユーザーがコンボボックスで別の アイテムを選択した場合、または次の場合
  2. 自分のコードがコンボ ボックスのSelectedItemを反映 コンボボックスは、別のオブジェクトの プロパティを表示しています。

私は現在のオブジェクトのプロパティを更新できるように、ケース1のSelectedIndexChangedイベントに興味があります。しかし、ケース2では、オブジェクトのプロパティが変更されていないため、イベントを発生させたくありません。

例が役に立ちます。人のリストを含むリストボックスがあり、リスト内の現在選択されている人の国籍を表すコンボボックスがあると考えてみましょう。ケース1はFredが現在リストで選択されている場合に発生し、コンボボックスを使用して国籍を英語からウェールズ語に変更します。ケース2は、リストのスコットランド人であるボブを選択すると起こります。ここで、私のリスト更新イベントハンドラコードは、ボブが選択されたことを認識し、コンボボックスを更新して、スコットランドが選択アイテムになるようにします。これにより、コンボボックスのSelectedIndexChangedイベントが発生してBobの国籍がスコットランドに設定されます(すでにスコットランドであっても)。

SelectedIndexChangedイベントを発生させずにコンボボックスのSelectedItemプロパティを更新するにはどうすればよいですか? 1つの方法は、イベントハンドラの登録を解除し、SelectedItemを設定してからイベントハンドラを再登録することですが、これは退屈でエラーが発生するようです。より良い方法が必要です。

答えて

7

SuspendLatchというクラスを作成しました。より良い名前にオファーは歓迎されているが、それはあなたが必要なものを行い、あなたはこのようにそれを使用します。

void Method() 
{ 
    using (suspendLatch.GetToken()) 
    { 
     // Update selected index etc 
    } 
} 

void listbox1_SelectedIndexChanged(object sender, EventArgs e) 
{ 
    if (suspendLatch.HasOutstandingTokens) 
    { 
     return; 
    } 

    // Do some work 
} 

それはきれいではありませんが、それは仕事をして、登録解除イベントやブールフラグとは異なり、それは、ネストされた操作をサポートしていますTransactionScopeのようなビット。ラッチからトークンを取りだし続けると、HasOutstandingTokensがfalseを返す最後のトークンが破棄されたときだけです。ニースと安全。スレッドセーフではない、しかし...ここで

がSuspendLatchのコードです:

public class SuspendLatch 
{ 
    private IDictionary<Guid, SuspendLatchToken> tokens = new Dictionary<Guid, SuspendLatchToken>(); 

    public SuspendLatchToken GetToken() 
    { 
     SuspendLatchToken token = new SuspendLatchToken(this); 
     tokens.Add(token.Key, token); 
     return token; 
    } 

    public bool HasOutstandingTokens 
    { 
     get { return tokens.Count > 0; } 
    } 

    public void CancelToken(SuspendLatchToken token) 
    { 
     tokens.Remove(token.Key); 
    } 

    public class SuspendLatchToken : IDisposable 
    { 
     private bool disposed = false; 
     private Guid key = Guid.NewGuid(); 
     private SuspendLatch parent; 

     internal SuspendLatchToken(SuspendLatch parent) 
     { 
      this.parent = parent; 
     } 

     public Guid Key 
     { 
      get { return this.key; } 
     } 

     public override bool Equals(object obj) 
     { 
      SuspendLatchToken other = obj as SuspendLatchToken; 

      if (other != null) 
      { 
       return Key.Equals(other.Key); 
      } 
      else 
      { 
       return false; 
      } 
     } 

     public override int GetHashCode() 
     { 
      return Key.GetHashCode(); 
     } 

     public override string ToString() 
     { 
      return Key.ToString(); 
     } 

     public void Dispose() 
     { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
     } 

     protected virtual void Dispose(bool disposing) 
     { 
      if (!disposed) 
      { 
       if (disposing) 
       { 
        // Dispose managed resources. 
        parent.CancelToken(this); 
       } 

       // There are no unmanaged resources to release, but 
       // if we add them, they need to be released here. 
      } 
      disposed = true; 

      // If it is available, make the call to the 
      // base class's Dispose(Boolean) method 
      //base.Dispose(disposing); 
     } 
    } 
} 
+0

私を当ててください。しかし、これはちょっと残虐ではありません - "SelectedIndexChangedイベントを発生させずにコンボボックスのSelectedItemプロパティをどのように更新できますか?"私のソリューションに関するフィードバックはありますか? –

+0

これは過度なことかもしれません。フラグを設定するかイベントハンドラを削除する方が簡単ですが、SelectedIndexChangedイベントを中断したい場所が複数あることがわかったときにSuspendLatchを作成しました –

+0

"より良い名前のオファーを歓迎します" - public class SuspendThingerMajig? – Juliet

4

私は最善の方法は、フラグ変数を使用することだと思う:

bool updatingCheckbox = false; 

void updateCheckBox() 
{ 
    updatingCheckBox = true; 

    checkbox.Checked = true; 

    updatingCheckBox = false; 
} 

void checkbox_CheckedChanged(object sender, EventArgs e) 
{ 
    if (!updatingCheckBox) 
     PerformActions() 
} 

[編集:のみ投稿しますコードが実際にはっきりしていない]

この場合、イベントハンドラは、updateCheckBox()によってチェックボックスが変更されたとき、通常の操作を実行しません。

+0

これは、私のSuspendLatch(この質問の他の場所に掲載されています)と、SelectedIndexChangedイベントを中断したい場所が複数あることがわかるまで、私が使ったメソッドの単純なバージョンです。 –

+0

確かに、私はおそらく自分自身でその考えを使うつもりであるので、あなたの答えをupvoted;) – Lennaert

+0

それはうまくいくのです。少なくとも1つのいいアイデアを持っているのはいいですね。 –

1

私はイベントを発射させました。しかし、私はインデックスを変更する前にフラグを設定し、後にそれを反転させます。イベントハンドラでは、フラグがセットされているかどうかをチェックし、もしあればハンドラを終了します。

1

私はあなたの焦点がオブジェクトにあるべきであり、発生しているイベントではないと思う。

セイたとえば、あなたがイベント

void combobox_Changed(object sender, EventArgs e) 
{ 
    PerformActions() 
} 

とPerformActionsあなたが

の効果に何かを見ることを期待する人の内側に、その後

void PerformActions() 
{ 
    (listBox.SelectedItem as IPerson).Nationality = 
     (comboBox.SelectedItem as INationality) 
} 

の効果に何かをしたを持っています

class Person: IPerson 
{ 
    INationality Nationality 
    { 
     get { return m_nationality; } 
     set 
     { 
      if (m_nationality <> value) 
      { 
      m_nationality = value; 
      this.IsDirty = true; 
      } 
     } 
    } 
} 

ここでのポイントは、オブジェクトに何が起きているのかを追跡させることですそれはUIではなく、これにより、オブジェクト上のダーティー・フラグ・トラッキングを追跡することもできます。これは、後の永続性に役立ちます。

これはまた、あなたのUIをきれいに保ち、間違いやすいイベント登録コードを得るのを防ぎます。

3

私は、不要なイベントハンドラから保護するために、ブーリアンフラグ変数を常に使用しています。 TaskVisionサンプルアプリケーションでは、これを行う方法を教えてくれました。あなたのすべてのイベントのために

あなたのイベントハンドラのコードは次のようになります。私は最終的にあまりにも多くの時間を解雇されてからuncessaryイベントを回避するための解決策を見つけた

private bool lockEvents; 

protected void MyEventHandler(object sender, EventArgs e) 
{ 
    if (this.lockEvents) 
    { 
     return; 
    } 

    this.lockEvents = true; 
    //Handle your event... 
    this.lockEvents = false; 
} 
0

私はカウンタを使用しています。私が必要としないときにもう一度マスクしたいイベントをフック/アンフックします。

以下の例は、DataGridのCellValueChangedイベントを非表示にする方法を示しています。

EventMask valueChangedEventMask; 

// In the class constructor 
valueChangedEventMask = new EventMask(
    () => { dgv.CellValueChanged += new DataGridViewCellEventHandler(dgv_CellValueChanged); }, 
    () => { dgv.CellValueChanged -= new DataGridViewCellEventHandler(dgv_CellValueChanged); } 
); 

// Use push to hide the event and pop to make it available again. The operation can be nested or be used in the event itself. 
void changeCellOperation() 
{ 
    valueChangedEventMask.Push(); 

    ... 
    cell.Value = myNewCellValue 
    ... 

    valueChangedEventMask.Pop(); 
} 

// The class 
public class EventMask 
{ 
    Action hook; 
    Action unHook; 

    int count = 0; 

    public EventMask(Action hook, Action unHook) 
    { 
     this.hook = hook; 
     this.unHook = unHook; 
    } 

    public void Push() 
    { 
     count++; 
     if (count == 1) 
      unHook(); 
    } 

    public void Pop() 
    { 
     count--; 
     if (count == 0) 
      hook(); 
    } 
}