2010-04-22 17 views
5

Lo siguiente parece ser un patrón relativamente común (para mí, no para la comunidad en general) para vincular una variable de cadena al contenido de un TextBox.¿Cómo puedo evitar la recursión infinita cuando uso eventos para vincular elementos de IU con campos?

class MyBackEndClass 
{ 
    public event EventHandler DataChanged; 
    string _Data; 
    public string Data 
    { 
     get { return _Data; } 
     set 
     { 
      _Data = value; 
      //Fire the DataChanged event 
     } 
    } 
} 

class SomeForm : // Form stuff 
{ 
    MyBackEndClass mbe; 
    TextBox someTextBox; 
    SomeForm() 
    { 
     someTextBox.TextChanged += HandleTextBox(); 
     mbe.DataChanged += HandleData(); 
    } 

    void HandleTextBox(Object sender, EventArgs e) 
    { 
     mbe.Data = ((TextBox)sender).Text; 
    } 

    void HandleData(Object sender, EventArgs e) 
    { 
     someTextBox.Text = ((MyBackEndClass) sender).Data; 
    } 
} 

El problema es que al cambiar el cuadro de texto dispara los cambios del valor de los datos en el back-end, lo que hace que el cuadro de texto para cambiar, etc, que se ejecuta siempre.

¿Hay un mejor patrón de diseño (que no sea recurriendo a una desagradable bandera booleana) que maneja este estuche correctamente?

EDITAR: Para ser claros, en el diseño real, la clase backend se utiliza para sincronizar los cambios entre varias formas. Por lo tanto, no puedo usar la propiedad SomeTextBox.Text directamente.

Billy3

+0

Estoy confundido. Establecer TextBox.Text con el mismo valor dos veces seguidas solo provoca ** un ** evento ** TextChanged para disparar? – ParmesanCodice

+0

@ParmesanCodice: No. Cada vez que alguien cambia el contenido del cuadro de texto, incluso si el contenido de la cadena que se mantiene allí no cambia, se desencadena el evento TextChanged. –

+0

extraño. Estoy ejecutando tu programa en este momento, sin ver ese comportamiento. – ParmesanCodice

Respuesta

2

autorización I he escrito algo de código, pero puede que no les guste :)

public class DataChangedEventArgs : EventArgs 
{ 
    public string Data { get; set; } 

    public DataChangedEventArgs(string data) 
    { 
     Data = data; 
    } 
} 
public delegate void DataChangedEventHander(DataChangedEventArgs e); 
public class BackEnd 
{ 
    public event DataChangedEventHander OnDataChanged; 
    private string _data; 
    public string Data 
    { 
     get { return _data; } 
     set 
     { 
      _data = value; 
      RaiseOnDataChanged(); 
     } 
    } 

    private static readonly object _sync = new object(); 
    private static BackEnd _instance; 
    public static BackEnd Current 
    { 
     get 
     { 
      lock (_sync) 
      { 
       if (_instance == null) 
        _instance = new BackEnd(); 
       return _instance; 
      } 
     } 
    } 
    private void RaiseOnDataChanged() 
    { 
     if(OnDataChanged != null) 
      OnDataChanged(new DataChangedEventArgs(Data)); 
    } 
} 
public class ConsumerControl 
{ 
    public event EventHandler OnTextChanged; 
    private string _text; 
    public string Text 
    { 
     get 
     { 
      return _text; 
     } 
     set 
     { 
      _text = value; 
      if (OnTextChanged != null) 
       OnTextChanged(this, EventArgs.Empty); 
     } 
    } 
} 
public class Consumer 
{ 
    public ConsumerControl Control { get; set; } 

    public Consumer() 
    { 
     Control = new ConsumerControl(); 
     BackEnd.Current.OnDataChanged += NotifyConsumer; 
     Control.OnTextChanged += OnTextBoxDataChanged; 
    } 

    private void OnTextBoxDataChanged(object sender, EventArgs e) 
    { 
     NotifyBackEnd(); 
    } 

    private void NotifyConsumer(DataChangedEventArgs e) 
    { 
     Control.Text = e.Data; 
    } 
    private void NotifyBackEnd() 
    { 
     // unsubscribe 
     BackEnd.Current.OnDataChanged -= NotifyConsumer; 
     BackEnd.Current.Data = Control.Text; 
     // subscribe again 
     BackEnd.Current.OnDataChanged += NotifyConsumer; 
    } 
} 
public class BackEndTest 
{ 
    public void Run() 
    { 
     var c1 = new Consumer(); 
     var c2 = new Consumer(); 
     c1.Control.Text = "1"; 
     BackEnd.Current.Data = "2"; 
    } 
} 

El ídia principal está aquí:

// unsubscribe 
BackEnd.Current.OnDataChanged -= NotifyConsumer; 
BackEnd.Current.Data = Control.Text; 
// subscribe again 
BackEnd.Current.OnDataChanged += NotifyConsumer; 
+0

Tienes razón, no me gusta, pero es mejor que la idea de la bandera booleana. +1. –

0

se puede comprobar en el interior emisor de acceso set de propiedad de clase de datos de MyBackEndClass Si su someform - apenas no elevar evento.

+0

Pero necesito el evento para disparar. El evento debe activarse para actualizar otros formularios en la aplicación que usan datos de SomeForm. –

1

Usar enlaces.

someTestBox.BindingContext.Add(new Binding("Text", mbe, "Data")); 

Editar: Mis disculpas, es BindingContext y debería funcionar si enlaza todas sus formas al objeto de fondo, cuando se actualiza la BEO, que va a actualizar todos los formularios adjuntos a la misma (y no lo puedo explotar recursivamente.)

+0

Err .. Estoy un poco confundido. System.Forms.TextBox no expone una propiedad "Vinculaciones". –

+0

OK - Investigaré esto. –

0

Creo que vas a estar atrapado con una bandera booleana, o mejor aún algún tipo de valor enum, que pasas a tu método HandleTextBox.

Puede comparar valores de cadena antiguos y nuevos para ver si el valor realmente ha cambiado y garantiza cambiar el valor que se muestra en el cuadro de texto.

3

A pesar de que no puedo reproducir este problema, Tengo una idea sobre cómo solucionarlo.

Actualmente tiene un DataSetEvent y no un DataChangedEvent.

class MyBackEndClass 
{ 
    public event EventHandler DataChanged; 

    private string data = string.Empty; 

    public string Data 
    { 
     get { return this.data; } 
     set 
     { 
      // Check if data has actually changed   
      if (this.data != value) 
      { 
       this.data = value; 
       //Fire the DataChanged event 
      } 
     } 
    } 
} 

Esto debe parar la recursividad, porque ahora se obtiene TextBoxTextChanged-> DataChanged-> TextBoxChanged -> Los datos no ha cambiado eventos se detienen aquí.

EDIT: mover Tal vez este código en el cuadro de texto para eliminar el parpadeo:
reemplazar su System.Windows.Forms.TextBox 's con esto:

class CleverTextBox : TextBox 
{ 
    private string previousText = string.Empty; 

    public CleverTextBox() : base() 
    { 
     // Maybe set the value here, not sure if this is necessary..? 
     this.previousText = base.Text; 
    } 

    public override OnTextChanged(EventArgs e) 
    { 
     // Only raise the event if the text actually changed 
     if (this.previousText != base.Text) 
     {     
      this.previousText = this.Text; 
      base.OnTextChanged(e); 
     } 
    } 
} 
+0

Ah, sí, esto impide la recursión, pero todavía hace que el cuadro de texto parpadee cada vez que el usuario ingresa un carácter cuando se vuelve a dibujar el control. No es ideal, pero funciona. +1 –

+0

@Billy ONeal ¿Qué pasa si mueves el código a un TextBox personalizado como he editado mi respuesta para indicar? Como no puedo replicar tu problema, no puedo probar esto, lo siento. – ParmesanCodice

+1

En lugar de subclasificar TextBox o modificar MyBackEndClass, podría mover la prueba! = A HandleTextBox() y HandleData() – ajs410

0

Es un poco sucio ... pero se podría intentar analizar el Propiedad Environment.StackTrace para una llamada previa a TextChanged. Sin embargo, creo que es un poco menos sucio que el booleano, lo cual para mí es una cuestión de seguridad.

+0

Los formularios de Windows no pueden ser multiproceso, lo que significa que no hay ningún problema allí. –

+0

Uh ... ¿quiere decir que los controles de GUI solo se pueden cambiar en el hilo que creó el identificador? La clase backend no es un control, por lo que no hay ninguna razón por la que no pueda cambiar la propiedad mbe.Data de un subproceso BackgroundWorker. – ajs410

Cuestiones relacionadas