2009-04-08 15 views
9

El evento SelectedIndexChanged es despedido en mi aplicación desde un cuadro combinado cuando:La diferenciación entre los eventos provocados por la interacción del usuario y mi propio código

  1. el usuario elige un elemento diferente en el cuadro combinado, o cuando:
  2. mi propio código actualiza el cuadro combinado SelectedItem para reflejar que el cuadro combinado ahora muestra propiedades para un objeto diferente.

Estoy interesado en el evento SelectedIndexChanged para el caso 1, por lo que puedo actualizar las propiedades del objeto actual. Pero en el caso 2, no quiero que el evento se active, porque las propiedades del objeto no han cambiado.

Un ejemplo puede ayudar. Consideremos que tengo un cuadro de lista que contiene una lista de personas y tengo un cuadro combinado que representa la nacionalidad de la persona actualmente seleccionada en la lista. El caso 1 podría ocurrir si Fred está actualmente seleccionado en la lista, y yo uso el cuadro combinado para cambiar su nacionalidad de inglés a galés. Caso 2 podría suceder si luego selecciono a Bob, que es escocés, en la lista. Aquí, el código de mi controlador de eventos de actualización de lista ve que Bob ahora está seleccionado y actualiza el cuadro combinado para que Scottish sea ahora el elemento seleccionado. Esto provoca que se desencadene el evento SelectedIndexChanged del cuadro combinado para establecer la nacionalidad de Bob en escocés, aunque ya sea escocés.

¿Cómo puedo actualizar la propiedad SelectedItem de mi cuadro combinado sin causar que el evento SelectedIndexChanged se desencadene? Una forma sería anular el registro del controlador de eventos, establecer SelectedItem, luego volver a registrar el controlador de eventos, pero esto parece tedioso y propenso a errores. Debe haber una mejor manera.

Respuesta

7

Creé una clase que llamé SuspendLatch. Las ofertas en un nombre mejor son bienvenidos, pero sí lo que necesita y que usaría así:

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

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

    // Do some work 
} 

No es bonito, pero funciona, ya diferencia de anular el registro de eventos o banderas booleanas, es compatible con las operaciones anidadas un poco como TransactionScope. Sigue sacando fichas del bloqueo y solo cuando se elimina el último token, el HasOutstandingTokens devuelve falso. Agradable y seguro. No multi-hilo, aunque ...

Aquí está el código para 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

Might me me. Pero, ¿esto no es un poco exagerado? "¿Cómo puedo actualizar la propiedad SelectedItem de mi cuadro combinado sin hacer que el evento SelectedIndexChanged se active?" ¿Podría recibir comentarios sobre mi solución? –

+0

Podría ser excesivo. Establecer indicadores o quitar el controlador de eventos es una solución más simple, pero creé mi SuspendLatch cuando me di cuenta de que tenía varios lugares donde quería suspender el evento SelectedIndexChanged –

+0

"Se ofrecen ofertas con un mejor nombre" - clase pública SuspendThingerMajig? – Juliet

4

Creo que la mejor forma sería la de utilizar una variable bandera:

bool updatingCheckbox = false; 

void updateCheckBox() 
{ 
    updatingCheckBox = true; 

    checkbox.Checked = true; 

    updatingCheckBox = false; 
} 

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

[Editar: solamente la publicación el código no es realmente claro]

En este caso, el controlador de eventos no realizaría sus operaciones normales cuando la casilla se cambie a través de updateCheckBox().

+0

Esta es la versión más simple de mi SuspendLatch (publicado en otra parte de esta pregunta) y el método que utilicé hasta que me di cuenta de que tenía varios lugares en los que quería suspender el evento SelectedIndexChanged. –

+0

De hecho, votó a favor su respuesta ya que probablemente voy a utilizar esa idea yo mismo;) – Lennaert

+0

Me alegra que sea de alguna utilidad. Es bueno tener al menos una buena idea para todos de vez en cuando :) –

1

Deje que el evento se dispare. Pero, configuro una bandera antes de cambiar el índice y voltearlo después. En el controlador de eventos, verifico si la bandera está configurada y salgo del manejador si es así.

1

Creo que su enfoque debe estar en el objeto y no en el evento que está ocurriendo.

decir, por ejemplo que tiene el evento

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

y PerformActions hicieron algo en el sentido de

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

entonces dentro de la persona que se puede esperar para ver algo en el sentido de

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

El punto aquí es que deje que el objeto haga un seguimiento de lo que está sucediendo a sí mismo, no la interfaz de usuario. Esto también le permite realizar un seguimiento del seguimiento sucio de la bandera en sus objetos, lo que podría ser útil para la persistencia más adelante.

Esto también mantiene su UI limpia y evita que obtenga un código de registro de evento extraño que probablemente sea propenso a errores.

3

Siempre he usado una variable de indicador booleano para proteger contra los controladores de eventos no deseados. La aplicación de ejemplo TaskVision me enseñó cómo hacer esto.

Su código de controlador de eventos para todos los eventos se verá así:

private bool lockEvents; 

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

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

por fin he encontrado una solución para evitar el evento uncessary de ser despedido demasiados tiempo.

Utilizo un contador y solo engancho/desenganche los eventos que quiero enmascarar una vez cuando no es necesario, y cuando es necesario de nuevo.

El siguiente ejemplo muestra cómo oculto el evento CellValueChanged de una cuadrícula de datos.

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(); 
    } 
} 
Cuestiones relacionadas