Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A design pattern to disable event handling

To simply illustrate my dilemma, let say that I have the following code:

class A
{
    // May be set by a code or by an user.
    public string Property
    {
        set { PropertyChanged(this, EventArgs.Empty); }
    }

    public EventHandler PropertyChanged;
}

class B
{
    private A _a;

    public B(A a)
    {
        _a = a;
        _a.PropertyChanged += Handler;
    }

    void Handler(object s, EventArgs e)
    {
        // Who changed the Property?
    }

    public void MakeProblem()
    {
        _a.Property = "make a problem";
    }
}

In order to perform its duty, class B have to react on A's PropertyChanged event but also is capable of alternating that property by itself in certain circumstances. Unfortunately, also other objects can interact with the Property.

I need a solution for a sequential flow. Maybe I could just use a variable in order to disable an action:

bool _dontDoThis;

void Handler(object s, EventArgs e)
{
    if (_dontDoThis)
        return;

    // Do this!
}

public void MakeProblem()
{
    _dontDoThis = true;
    _a.Property = "make a problem";
    _dontDoThis = false;
}

Are there a better approaches?

Additional considerations

  • We are unable to change A.
  • A is sealed.
  • There are also other parties connected to the PropertyChanged event and I don't know who their are. But when I update the Property from B, they shouldn't be also notified. But I'm unable to disconnect them from the event because I don't know them.
  • What if also more threads can interact with the Property in the mean time?

The more bullets solved, the better.

Original problem

My original problem is a TextBox (WPF) that I want to complement depending on its content and focus. So I need to react on TextChanged event and I also need to omit that event if its origin is derived from my complements. In some cases, other listeners of a TextChanged event shouldn't be notified. Some strings in certain state and style are invisible to others.

like image 442
Ryszard Dżegan Avatar asked Aug 05 '13 11:08

Ryszard Dżegan


3 Answers

If it is so important not to handle events you initiated, maybe you should change the way you set Property to include the initiator of the change?

public class MyEventArgs : EventArgs
{
    public object Changer;
}

public void SetProperty(string p_newValue, object p_changer)
{
   MyEventArgs eventArgs = new MyEventArgs { Changer = p_changer };
   PropertyChanged(this, eventArgs); 
}

And then in your handler - simply check your are not the initiator.

I find all these changes in registration and members very problematic in terms on multi threading and extensibility.

like image 118
Vadim Avatar answered Nov 19 '22 08:11

Vadim


Well essentially you are trying to break the event delegation mechanism and any "solution" to that is going to be brittle since updates to the BCL might break your code. You could set the backing field using reflection. This of course would require that you do have permissions to do this and seeing the generic framing of the question it might not always be that you have the needed permissions

public void MakeProblem()
{
  if (_backingField == null) {
    _backingField = = _a.GetType().GetField(fieldname)
  }
  _backingField.SetValue(_a,"make a problem");
}

but as I started out, you are trying to break the event delegation mechanism. The idea is that the receivers of the event are independent. Disabling might lead to so very hard to find bugs because looking at any given piece of code it looks correct but only when you realize that some devious developer has hack the delegation mechanism do you realize why the information that is shown on screen seems to be a cached version of the actual value. The debugger shows the expected value of the property but because the event was hidden the handler responsible for updating the display was never fired and hence an old version is displayed (or the log shows incorrect information so when you are trying to recreate a problem a user has reported based on the content of the log you will not be able to because the information in the log is incorrect because it was based on no one hacking the event delegation mechanism

like image 32
Rune FS Avatar answered Nov 19 '22 09:11

Rune FS


To my opinion your solution is possible, though I would have created a nested IDisposable class inside B that does the same thing with 'using', or put the '_dontDoThis = false' inside a 'finally' clause.

class A
{
    // May be set by a code or by an user.
    public string Property
    {
        set { if (!_dontDoThis) PropertyChanged(this, EventArgs.Empty); }
    }

    public EventHandler PropertyChanged;
    bool _dontDoThis;
}

class B
{

    private class ACallWrapper : IDisposable
    {
        private B _parent;
        public ACallWrapper(B parent)
        {
            _parent = parent;
            _parent._a._dontDoThis = true;
        }

        public void Dispose()
        {
            _parent._a._dontDoThis = false;
        }
    }

    private A _a;

    public B(A a)
    {
        _a = a;
        _a.PropertyChanged += Handler;
    }

    void Handler(object s, EventArgs e)
    {
        // Who changed the Property?
    }

    public void MakeProblem()
    {
        using (new ACallWrapper(this))
            _a.Property = "make a problem";
    }
}

On the other hand, I would've used the 'internal' modifier for these things if those two classes are inside the same assembly.

internal bool _dontDoThis;

That way, you keep a better OOP design.

Moreover, if both classes are on the same assembly, I would've written the following code inside A:

    // May be set by a code or by an user.
    public string Property
    {
        set 
        { 
            internalSetProperty(value);
            PropertyChanged(this, EventArgs.Empty); 
        }
    }
    internal internalSetProperty(string value)
    {
        // Code of set.
    }

In this case, B could access internalSetProperty without triggering to PropertyChanged event.

Thread Sync:
NOTE: The next section applies to WinForms - I don't know if it applies to WPF as well.
For thread synchronizations, because we're referring to a control. you could use the GUI thread mechanism for synchronization:

class A : Control
{
    public string Property
    {
        set 
        { 
            if (this.InvokeRequired) 
            {
                this.Invoke((Action<string>)setProperty, value);
                reutrn;
            }
            setProperty(value);
        }
    }


    private void setProperty string()
    {
        PropertyChanged(this, EventArgs.Empty); 
    }
}
like image 1
EZLearner Avatar answered Nov 19 '22 09:11

EZLearner