Как я могу предотвратить бесконечную рекурсию при использовании событий для привязки элементов пользовательского интерфейса к полям? - PullRequest
5 голосов
/ 22 апреля 2010

Следующее, кажется, является довольно распространенной схемой (для меня, а не для сообщества в целом), чтобы связать строковую переменную с содержимым 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.Text напрямую.

Billy3

Ответы [ 6 ]

3 голосов
/ 22 апреля 2010

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

В настоящее время у вас есть 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);
        }
    }
}
2 голосов
/ 22 апреля 2010

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

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;
1 голос
/ 22 апреля 2010

Используйте привязки.

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

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

0 голосов
/ 22 апреля 2010

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

0 голосов
/ 22 апреля 2010

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

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

0 голосов
/ 22 апреля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...