2010-04-22 13 views
5

Poniższe wydaje się dość powszechnym wzorem (dla mnie, nie dla całej społeczności) do wiązania zmiennej łańcuchowej do zawartości TextBox.Jak mogę zapobiec nieskończonej rekursji podczas używania zdarzeń do wiązania elementów interfejsu użytkownika z polami?

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

Problem polega na tym, że zmiana TextBox wyzwala zmianę wartości danych w backend, który powoduje, że pole tekstowe, aby zmienić itp, który działa zawsze.

Czy istnieje lepszy wzór projektu (oprócz użycia nieprzyjemnej flagi boolowskiej), który obsługuje ten przypadek poprawnie?

EDYCJA: Aby było jasne, w prawdziwym projekcie klasa zaplecza służy do synchronizowania zmian między wieloma formularzami. Dlatego nie mogę po prostu użyć właściwości SomeTextBox.Text bezpośrednio.

Billy3

+0

Jestem zdezorientowany. Ustawienie TextBox.Text na tę samą wartość dwa razy z rzędu powoduje, że ** jedno zdarzenie TextChanged zostanie wywołane? – ParmesanCodice

+0

@ParmesanCodice: Nie. Za każdym razem, gdy ktoś zmieni zawartość pola tekstowego - nawet jeśli zawartość napisanego ciągu nie zmieni się - zdarzenie TextChanged zostanie wywołane. –

+0

dziwne, że prowadzę program w tej chwili, nie widząc tego zachowania. – ParmesanCodice

Odpowiedz

2

Ok mam trochę kodu, ale nie może się to podoba :)

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

Głównym idia jest tutaj:

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

Masz rację, nie podoba mi się to, ale jest lepsze niż idea flagi boolowskiej. +1. –

0

Można sprawdzić nadawcę wewnątrz akcesor set of MyBackEndClass rekreacyjnego klasa Danych Jeśli jego SomeForm - po prostu nie podnieść zdarzenie.

+0

Ale potrzebuję zdarzenia do ognia. Zdarzenie musi zostać uruchomione, aby zaktualizować inne formularze w aplikacji korzystającej z danych z usługi SomeForm. –

1

Użyj powiązań.

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

Edit: Moi appologies, to BindingContext i powinno działać, jeśli wiążą wszystkie formularze do obiektu typu back-end, gdy jeden aktualizuje BEO, to będzie zaktualizować wszystkie postacie z nim związane (i to przyzwyczajenie eksploduje rekurencyjnie.)

+0

Err .. Jestem trochę zdezorientowany. System.Forms.TextBox nie ujawnia właściwości "Wiązania". –

+0

OK - Zajrzę do tego. –

0

Myślę, że utkniesz z flagą typu Boolean, lub jeszcze lepiej jakąś wartością wyliczeniową, którą przekażesz do metody HandleTextBox.

Można porównać stare i nowe wartości ciągów, aby sprawdzić, czy wartość rzeczywiście się zmieniła i uzasadnia zmianę wartości wyświetlanej w polu tekstowym.

3

Chociaż nie może replikować tego problemu, Mam pomysł, jak to naprawić.

Aktualnie masz DataSetEvent, a nie 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 
      } 
     } 
    } 
} 

ten powinien przerwać rekurencję, bo teraz masz TextBoxTextChanged-> DataChanged-> TextBoxChanged -> Dane nie zmienił zdarzenia zatrzymać tutaj.

EDIT: Może przenieść ten kod do pola tekstowego, aby usunąć migotanie:
zastąpić System.Windows.Forms.TextBox „s z tym:

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 tak, to zapobiega rekursji, ale nadal powoduje migotanie pola tekstowego za każdym razem, gdy użytkownik wprowadza znak, gdy kontrola jest przerysowana. Nie idealne, ale działa. +1 –

+0

@Billy ONeal co, jeśli przeniesiesz kod do niestandardowego pola tekstowego, tak jak edytowałem swoją odpowiedź, aby wskazać? Ponieważ nie mogę powtórzyć twojego problemu, nie mogę tego przetestować, przepraszam. – ParmesanCodice

+1

Zamiast podklasy TextBox lub modyfikacji MyBackEndClass, możesz przenieść test! = Do HandleTextBox() i HandleData() – ajs410

0

To trochę brudny ... ale można spróbować podczas analizowania Właściwość Environment.StackTrace dla poprzedniego połączenia z TextChanged. Myślę, że jest trochę mniej brudny niż boolean, co do mnie woła o bezpieczeństwo nici.

+0

Formularze systemu Windows nie mogą być wielowątkowe, co oznacza, że ​​nie ma problemu. –

+0

Uh ... masz na myśli, że kontrolki GUI można zmienić tylko w wątku, który utworzył uchwyt? Klasa backend nie jest formantem, więc nie ma powodu, dla którego nie można zmienić właściwości mbe.Data z wątku BackgroundWorker. – ajs410

Powiązane problemy