Как я могу предотвратить бесконечную рекурсию при использовании событий для привязки элементов UI с полями?

Следующее, кажется, относительно общий шаблон (мне, не сообществу в целом) для привязки строковой переменной с содержанием TextBox.

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

Проблема состоит в том, что изменение TextBox запускает изменения значение данных в бэкенд, который заставляет текстовое поле изменяться и т.д. Это работает навсегда.

Существует ли лучший шаблон разработки (кроме обращения к противному булеву флагу), который обрабатывает этот случай правильно?

Править: Чтобы быть ясным, в реальном дизайне, класс бэкенда используется для синхронизации изменений между несколькими формами. Поэтому я не могу только использовать SomeTextBox. Текстовое свойство непосредственно.

Billy3

5
задан Billy ONeal 22 April 2010 в 16:02
поделиться

6 ответов

Хорошо, я написал какой-то код, но он может вам не понравиться :)

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

Основная идея здесь:

// unsubscribe
BackEnd.Current.OnDataChanged -= NotifyConsumer;
BackEnd.Current.Data = Control.Text;
// subscribe again
BackEnd.Current.OnDataChanged += NotifyConsumer;
2
ответ дан 14 December 2019 в 13:30
поделиться

Использовать привязки.

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

Редактировать: Приношу свои извинения, это BindingContext , и он должен работать, если вы привяжете все свои формы к внутреннему объекту, когда кто-то обновляет BEO, он обновляет все прикрепленные к нему формы (и он не взорвется рекурсивно.)

1
ответ дан 14 December 2019 в 13:30
поделиться

Вы можете проверить отправителя внутри set accessor свойства Data класса MyBackEndClass Если его SomeForm - просто не вызывайте событие.

0
ответ дан 14 December 2019 в 13:30
поделиться

Несмотря на то, что я не могу воспроизвести эту проблему, у меня есть идея, как ее исправить.

В настоящее время у вас есть DataSetEvent , а не 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
            }
        }
    }
}

Это должно остановить рекурсию, потому что теперь вы получаете TextBoxTextChanged-> DataChanged-> TextBoxChanged -> Данные не изменились, здесь останавливаются события.

РЕДАКТИРОВАТЬ: Возможно, переместите этот код в TextBox, чтобы убрать мерцание:
Замените ваши System.Windows.Forms.TextBox следующим:

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);
        }
    }
}
3
ответ дан 14 December 2019 в 13:30
поделиться

Это немного грязно ... но вы можете попробовать проанализировать свойство Environment.StackTrace для предыдущего вызова TextChanged. Я думаю, что это немного менее грязно, чем логическое значение, которое, как мне кажется, требует безопасности потоков.

0
ответ дан 14 December 2019 в 13:30
поделиться

Я думаю, вы застрянете с логическим флагом или, еще лучше, каким-то значением перечисления, которое вы передаете в свой метод HandleTextBox.

Вы можете сравнить старые и новые строковые значения, чтобы увидеть, действительно ли значение изменилось, и гарантирует ли изменение значения, отображаемого в текстовом поле.

0
ответ дан 14 December 2019 в 13:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: