7

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.

Ryszard Dżegan
  • 24,366
  • 6
  • 38
  • 56
  • I think there's a TextInput event that does what you're looking for. I had this problem too (with another event), and I did just this. – It'sNotALie. Aug 05 '13 at 11:06
  • @It'sNotALie. Yes, in that particular problem it could be a sufficient solution. Thank you. But I will still look for something more general... – Ryszard Dżegan Aug 05 '13 at 11:12
  • 1
    To answer your original problem, the general approach to this type of thing is to use a flag of some sort. In terms of a pattern to use, I already answered a similar question to this [here](http://stackoverflow.com/questions/17547518/determine-who-fired-an-event/17547754#17547754) but there are other suggestions that could definitely do the job for you. – James Aug 05 '13 at 11:13
  • @James, I wanted to link my own question just to find out you beat me to it. So +1 for you :) – Jordy Aug 05 '13 at 11:22
  • @Jordy it isn't far off as being a duplicate but because the OP didn't focus purely on it being a UI issue I decided not to flag it. – James Aug 05 '13 at 11:24
  • @James, it actually never came to mind as a duplicate. Altough yBee has this issue with an UI control. I was just happy to actually witness my questions helping other people :) – Jordy Aug 05 '13 at 11:31
  • @Jordy yeah his original problem appears to be pretty much identical to yours, but the question is a bit more in-depth as it's asking for a general approach (so looking at it with a more conceptual hat on) & considering things like threading etc. – James Aug 05 '13 at 11:38
  • what's the implementation of your property? I'm guessing you don't just have a setter that calls fires an event but that you are also changing some value? – Rune FS Aug 06 '13 at 08:10
  • @RuneFS originally I thought about the TextBox.Text from WPF. But I'm also curious about something more general. I've tried to point out the problem without obscuring the view. – Ryszard Dżegan Aug 06 '13 at 08:12

5 Answers5

2

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.

Vadim
  • 2,847
  • 15
  • 18
2

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

Rune FS
  • 21,497
  • 7
  • 62
  • 96
1

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); 
    }
}
EZLearner
  • 1,614
  • 16
  • 25
1

Great question.

As a general case, you can not mess around with event handlers of sealed classes. Normally you could override A's hypothetical OnPropertyChanged and based on some flag either raise the event or not. Alternatively you could provide a setter method that does not raise event, as suggested by @Vadim. However, if A is sealed your best option is to add flag to a lister, just as you did. That will enable you to recognize PropertyChanged event raised by B, but you won't be able to suppress the event for other listeners.

Now, since you provided context... There is a way of doing exactly this in WPF. All that needs to be done is B's handler for TextBox.TextChanged needs to set e.Handled = _dontDoThis. That will supress notifications for all other listeners, provided B's one was added as the first one. How to make sure this happens? Reflection!

UIElement exposes only AddHandler and RemoveHandler methods, there is no InsertHandler that would allow to manually specifiy the priority for the handler. However, a quick peek into .NET source code (either download the whole thing or query what you need) reveals that AddHandler forwards arguments to an interal method EventHandlersStore.AddRoutedEventHandler, which does this:

// Create a new RoutedEventHandler 
RoutedEventHandlerInfo routedEventHandlerInfo =
    new RoutedEventHandlerInfo(handler, handledEventsToo); 

// Get the entry corresponding to the given RoutedEvent
FrugalObjectList<RoutedEventHandlerInfo> handlers = (FrugalObjectList<RoutedEventHandlerInfo>)this[routedEvent];
if (handlers == null) 
{
    _entries[routedEvent.GlobalIndex] = handlers = new FrugalObjectList<RoutedEventHandlerInfo>(1); 
} 

// Add the RoutedEventHandlerInfo to the list 
handlers.Add(routedEventHandlerInfo);

All this stuff is internal, but can be recreated using reflection:

public static class UIElementExtensions
{
    public static void InsertEventHandler(this UIElement element, int index, RoutedEvent routedEvent, Delegate handler)
    {
        // get EventHandlerStore
        var prop = typeof(UIElement).GetProperty("EventHandlersStore", BindingFlags.NonPublic | BindingFlags.Instance);
        var eventHandlerStoreType = prop.PropertyType;
        var eventHandlerStore = prop.GetValue(element, new object[0]);

        // get indexing operator
        PropertyInfo indexingProperty = eventHandlerStoreType.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance)
            .Single(x => x.Name == "Item" && x.GetIndexParameters().Length == 1 && x.GetIndexParameters()[0].ParameterType == typeof(RoutedEvent));

        object handlers = indexingProperty.GetValue(eventHandlerStore, new object[] { routedEvent });
        if (handlers == null)
        {
            // just add the handler as there are none at the moment so it is going to be the first one
            if (index != 0)
            {
                throw new ArgumentOutOfRangeException("index");
            }
            element.AddHandler(routedEvent, handler);
        }
        else
        {
            // create routed event handler info
            var constructor = typeof(RoutedEventHandlerInfo).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).Single();
            var handlerInfo = constructor.Invoke(new object[] { handler, false });

            var insertMethod = handlers.GetType().GetMethod("Insert");
            insertMethod.Invoke(handlers, new object[] { index, handlerInfo });
        }
    }
}

Now calling InsertEventHandler(0, textBox, TextBox.TextChangedEvent, new TextChangedEventHandler(textBox_TextChanged)) will make sure your handler will be the first one on the list, enabling you to suppress notifications for other listeners!

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        var textBox = new TextBox();
        textBox.TextChanged += (o, e) => Console.WriteLine("External handler");
        var b = new B(textBox);
        textBox.Text = "foo";
        b.MakeProblem();
    }
}

class B
{
    private TextBox _a;
    bool _dontDoThis;

    public B(TextBox a)
    {
        _a = a;
        a.InsertEventHandler(0, TextBox.TextChangedEvent, new TextChangedEventHandler(Handler));
    }

    void Handler(object sender, TextChangedEventArgs e)
    {
        Console.WriteLine("B.Handler");
        e.Handled = _dontDoThis;
        if (_dontDoThis)
        {
            e.Handled = true;
            return;
        }
        // do this!
    }

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

    }
}

Output:

B.Handler
External handler
B.Handler
gwiazdorrr
  • 6,181
  • 2
  • 27
  • 36
  • I think that in order to sum that up, your proposition is to insert an event handler as the first one and then to suppress event propagation. Sounds as good alternative when we have two things: 1. event insertion method and 2. event args with suppression. Then we don't need any virtual method in the base class responsible for event distribution and we don't need to replace the class by its subclass. – Ryszard Dżegan Aug 06 '13 at 09:17
  • Exactly. And as I understand, this was the original case, wasn't it? – gwiazdorrr Aug 06 '13 at 09:24
  • Yes, that was the original case (TextBox.Text in WPF) – Ryszard Dżegan Aug 06 '13 at 11:08
  • Yes, it is one of the solutions. – Ryszard Dżegan Aug 26 '13 at 08:19
0

I found one solution with regard to third parties, that are connected to the property and we don't want to nofify them when that property changed.

There are though the requirements:

  • We are capable of override the A.
  • The A has a virtual method that is invoked when property changed and allows to suspend the event to be raised.
  • The event is raised immediately when property is being changed.

The solution is to replace the A by MyA, as follows:

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

    // This is required
    protected virtual void OnPropertyChanged(EventArgs e)
    {
        PropertyChanged(this, e);
    }

    public EventHandler PropertyChanged;
}

// Inject MyA instead of A
class MyA : A
{
    private bool _dontDoThis;

    public string MyProperty
    {
        set
        {
            try
            {
                _dontDoThis = true;
                Property = value;
            }
            finally
            {
                _dontDoThis = false;
            }
        }
    }

    protected override void OnPropertyChanged(EventArgs e)
    {
        // Also third parties will be not notified
        if (_dontDoThis)
            return;

        base.OnPropertyChanged(e);
    }
}

class B
{
    private MyA _a;

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

    void Handler(object s, EventArgs e)
    {
        // Now we know, that the event is not raised by us.
    }

    public void MakeProblem()
    {
        _a.MyProperty = "no problem";
    }
}

Unfortunately we still use back bool field and we assume a single thread. To rid of the first, we could use a refactored solution suggest by EZSlaver (here). First, create a disposable wrapper:

class Scope
{
    public bool IsLocked { get; set; }

    public static implicit operator bool(Scope scope)
    {
        return scope.IsLocked;
    }
}

class ScopeGuard : IDisposable
{
    private Scope _scope;

    public ScopeGuard(Scope scope)
    {
        _scope = scope;
        _scope.IsLocked = true;
    }

    public void Dispose()
    {
        _scope.IsLocked = false;
    }
}

Then the MyProperty might be refactored to:

private readonly Scope _dontDoThisScope = new Scope();

public string MyProperty
{
    set
    {
        using (new ScopeGuard (_dontDoThisScope))
            Property = value;
    }
}
Community
  • 1
  • 1
Ryszard Dżegan
  • 24,366
  • 6
  • 38
  • 56