2010-04-22 9 views
5

Quanto segue sembra essere un modello relativamente comune (per me, non per la comunità in generale) per associare una variabile stringa al contenuto di un controllo TextBox.Come posso evitare la ricorsione infinita quando si usano eventi per collegare elementi dell'interfaccia utente ai campi?

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; 
    } 
} 

Il problema è che il cambiamento di TextBox spara cambia il valore dei dati nel backend, che fa sì che la casella di testo per cambiare, ecc che corre sempre.

Esiste un modello di progettazione migliore (diverso dal ricorso a una brutta bandiera booleana) che gestisce correttamente questa custodia?

MODIFICA: Per essere chiari, nel progetto reale la classe di backend viene utilizzata per sincronizzare le modifiche tra più moduli. Pertanto non posso semplicemente usare direttamente la proprietà SomeTextBox.Text.

Billy3

+0

Sono confuso. L'impostazione di TextBox.Text sullo stesso valore due volte di seguito causa solo ** un evento ** TextChanged da attivare? – ParmesanCodice

+0

@ParmesanCodice: No. Ogni volta che qualcuno cambia il contenuto della casella di testo, anche se il contenuto della stringa contenuta non cambia, viene generato l'evento TextChanged. –

+0

strano sto eseguendo il programma in questo momento, non vedendo quel comportamento. – ParmesanCodice

risposta

2

Ok Ho scritto un codice, ma si potrebbe non piace :)

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"; 
    } 
} 

L'idia principale è qui:

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

Hai ragione, non mi piace, ma è meglio dell'idea della bandiera booleana. +1. –

0

potreste controllare il mittente all'interno di accesso set di proprietà di classe dati di MyBackEndClass Se la sua SomeForm - non solo alzare evento.

+0

Ma ho bisogno che l'evento esploda. L'evento deve essere attivato per aggiornare altri moduli nell'applicazione che utilizzano i dati di SomeForm. –

1

Utilizzare attacchi.

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

Edit: I miei scusato, è BindingContext e dovrebbe funzionare se si associa tutti i moduli per l'oggetto di back-end, quando si aggiorna il BEO, sarà aggiornare tutti i moduli allegati ad esso (e si suol esplodere in modo ricorsivo.)

+0

Err .. Sono un po 'confuso. System.Forms.TextBox non espone una proprietà "Bindings". –

+0

OK - esaminerò questo. –

0

Penso che si resterà bloccati con un flag booleano, o meglio ancora una sorta di valore enum, che si passa al metodo HandleTextBox.

È possibile confrontare i valori di stringa vecchi e nuovi per verificare se il valore è effettivamente cambiato e garantisce di modificare il valore visualizzato nella casella di testo.

3

Anche se non posso replicare questo problema, Ho un'idea su come risolverlo.

Attualmente si dispone di uno DataSetEvent e non di uno 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 
      } 
     } 
    } 
} 

Questo dovrebbe fermare la ricorsione, perché ora si ottiene TextBoxTextChanged-> DataChanged-> TextBoxChanged -> I dati non è cambiata eventi si fermano qui.

EDIT: Forse spostare questo codice nel TextBox per rimuovere il tremolio:
sostituire il vostro System.Windows.Forms.TextBox 's con questo:

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ì, questo impedisce la ricorsione, ma fa comunque tremolare la casella di testo ogni volta che l'utente inserisce un carattere mentre il controllo viene ridisegnato. Non ideale ma funziona. +1 –

+0

@Billy ONeal cosa succede se sposti il ​​codice in un TextBox personalizzato come ho modificato la mia risposta per indicare? Poiché non riesco a replicare il tuo problema, non posso testarlo, mi dispiace. – ParmesanCodice

+1

Invece di creare una sottoclasse di TextBox o modificare MyBackEndClass, è possibile spostare il test! = Su HandleTextBox() e HandleData() – ajs410

0

E' un po 'sporca ... ma si potrebbe provare l'analisi del Proprietà Environment.StackTrace per una precedente chiamata a TextChanged. Penso che sia un po 'meno sporco di quello booleano, che per me grida per la sicurezza dei thread.

+0

I moduli Windows non possono essere multithreading, il che significa che non ci sono problemi lì. –

+0

Uh ... vuoi dire che i controlli della GUI possono essere modificati solo sul thread che ha creato l'handle? La classe di backend non è un controllo, quindi non c'è motivo per cui non sia possibile modificare la proprietà mbe.Data da un thread di BackgroundWorker. – ajs410

Problemi correlati