2009-04-08 7 views
9

L'evento SelectedIndexChanged viene licenziato nella mia applicazione da una casella combinata quando:Differenziazione tra eventi generati da interazione con l'utente e il mio codice

  1. l'utente sceglie un diverso voce nella casella combinata, o quando:
  2. il mio codice aggiorna la combinazione della casella SelectedItem per riflettere che la casella combinata ora visualizza le proprietà per un oggetto diverso.

Sono interessato all'evento SelectedIndexChanged per il caso 1, in modo da poter aggiornare le proprietà dell'oggetto corrente. Ma nel caso 2, non voglio che l'evento si attivi, perché le proprietà dell'oggetto non sono cambiate.

Un esempio può essere d'aiuto. Consideriamo che ho una casella di riepilogo contenente un elenco di persone e ho una casella combinata che rappresenta la nazionalità della persona attualmente selezionata nell'elenco. Il caso 1 potrebbe accadere se Fred è attualmente selezionato nell'elenco e io uso la casella combinata per cambiare la sua nazionalità dall'inglese al gallese. Il caso 2 potrebbe accadere se seleziono Bob, che è scozzese, nella lista. Qui, il mio elenco aggiornato del codice del gestore di eventi vede che Bob è ora selezionato e aggiorna la casella combinata in modo che Scottish ora sia l'elemento selezionato. Ciò causa l'attivazione dell'evento SelectedIndexChanged della casella combinata per impostare la nazionalità di Bob in scozzese, anche se è già scozzese.

Come posso aggiornare la proprietà SelectedItem della casella combinata senza causare l'evento SelectedIndexChanged da attivare? Un modo sarebbe annullare la registrazione del gestore eventi, impostare SelectedItem, quindi registrare nuovamente il gestore eventi, ma questo sembra noioso e soggetto a errori. Ci deve essere un modo migliore.

risposta

7

Ho creato una classe che ho chiamato SuspendLatch. Offre su un nome migliore sono i benvenuti, ma fa quello che ti serve e si può usare in questo modo:

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

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

    // Do some work 
} 

Non è abbastanza, ma funziona, ea differenza di annullamento della registrazione eventi o bandiere booleane, che supporta le operazioni nidificate un po 'come TransactionScope. Continui a prendere token dal latch ed è solo quando l'ultimo token è disposto che lo HasOutstandingTokens restituisce false. Bello e sicuro. Non threadsafe, anche se ...

Ecco il codice per 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

Me lo concedo. Ma non è un po 'eccessivo: "Come posso aggiornare la proprietà SelectedItem della mia casella combinata senza causare l'evento SelectedIndexChanged?" Per favore potrei avere un feedback sulla mia soluzione? –

+0

Potrebbe essere eccessivo. L'impostazione di flags o la rimozione dell'handler di eventi è una soluzione più semplice, ma ho creato il mio SuspendLatch quando ho realizzato che avevo più posizioni in cui sospendere l'evento SelectedIndexChanged –

+0

"Le offerte con un nome migliore sono benvenute" - public class SuspendThingerMajig? – Juliet

4

penso che il modo migliore sarebbe quella di utilizzare una variabile bandiera:

bool updatingCheckbox = false; 

void updateCheckBox() 
{ 
    updatingCheckBox = true; 

    checkbox.Checked = true; 

    updatingCheckBox = false; 
} 

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

[Edit: solo la pubblicazione il codice non è chiaro]

In questo caso, il gestore eventi non eseguirà le normali operazioni quando la casella di controllo viene modificata tramite updateCheckBox().

+0

Questa è la versione più semplice del mio SuspendLatch (pubblicato altrove su questa domanda), e il metodo che ho usato fino a quando mi sono reso conto che avevo più punti in cui volevo sospendere l'evento SelectedIndexChanged. –

+0

In effetti, ho messo in svantaggio la tua risposta dal momento che probabilmente userò l'idea da sola;) – Lennaert

+0

Felice che sia di qualche utilità. È bello avere almeno una bella idea tutti una volta tanto :) –

1

Lancio l'evento. Ma, ho impostato una bandiera prima di cambiare l'indice e ribaltarlo dopo. Nel gestore di eventi, controllo se il flag è impostato e esco dal gestore, se lo è.

1

Penso che l'attenzione dovrebbe essere sull'oggetto e non sull'evento che si verifica.

diciamo per esempio di avere l'evento

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

e PerformActions ha fatto qualcosa per l'effetto di

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

poi dentro la persona che ci si aspetta di vedere qualcosa per l'effetto di

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

il punto qui è che si lascia che l'oggetto tenga traccia di ciò che sta accadendo a stesso, non l'interfaccia utente. Ciò ti consente anche di tenere traccia del tracciamento del flag sporco sui tuoi oggetti, che potrebbe essere utile in seguito per la persistenza.

Ciò mantiene anche l'interfaccia utente pulita e impedisce di ottenere codice di registrazione degli eventi dispari che molto probabilmente sarà soggetto a errori.

3

Ho sempre usato una variabile di tipo booleano per proteggere da gestori di eventi indesiderati. L'applicazione di esempio TaskVision mi ha insegnato come farlo.

Il codice gestore di eventi per tutti i tuoi eventi sarà simile a questa:

private bool lockEvents; 

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

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

ho finalmente trovato una soluzione per evitare l'evento uncessary da essere licenziato troppo tempo.

Uso un contatore e aggancia/sganci solo gli eventi che voglio mascherare una volta quando non è necessario e quando è necessario nuovamente.

L'esempio seguente mostra come nascondo l'evento CellValueChanged di un datagrid.

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(); 
    } 
} 
Problemi correlati