views:

258

answers:

2

I have a scenario which is causing strange behavior with WPF data binding and INotifyPropertyChanged. I want a private member of the data binding source to handle the INotifyPropertyChanged.PropertyChanged event.

Here's the source code:

XAML

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="TestApplication.MainWindow"
    DataContext="{Binding RelativeSource={RelativeSource Self}}"
    Height="100" Width="100">
    <StackPanel>
        <CheckBox IsChecked="{Binding Path=CheckboxIsChecked}" Content="A" />
        <CheckBox IsChecked="{Binding Path=CheckboxIsChecked}" Content="B" />
    </StackPanel>
</Window>

Normal implementation works

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public bool CheckboxIsChecked
    {
        get { return this.mCheckboxIsChecked; }
        set
        {
            this.mCheckboxIsChecked = value;
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs("CheckboxIsChecked"));
        }
    }

    private bool mCheckboxIsChecked = false;

    public MainWindow() { InitializeComponent(); }
}

Desired implementation doesn't work

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged
    {
        add { lock (this.mHandler) { this.mHandler.PropertyChanged += value; } }
        remove { lock (this.mHandler) { this.mHandler.PropertyChanged -= value; } }
    }

    public bool CheckboxIsChecked
    {
        get { return this.mHandler.CheckboxIsChecked; }
        set { this.mHandler.CheckboxIsChecked = value; }
    }

    private HandlesPropertyChangeEvents mHandler = new HandlesPropertyChangeEvents();

    public MainWindow() { InitializeComponent(); }

    public class HandlesPropertyChangeEvents : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public bool CheckboxIsChecked
        {
            get { return this.mCheckboxIsChecked; }
            set
            {
                this.mCheckboxIsChecked = value;
                PropertyChangedEventHandler handler = this.PropertyChanged;
                if (handler != null)
                    handler(this, new PropertyChangedEventArgs("CheckboxIsChecked"));
            }
        }

        private bool mCheckboxIsChecked = false;
    }
}
+2  A: 

That's just a guess, but I think it might be because the sender parameter passed to the event handler is an instance of HandlesPropertyChangeEvents, when the binding expects an instance of MainWindow.

Try to change your code so that the sender is the MainWindow instance :

private PropertyChangedEventHandler _propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
    add { lock (this.mHandler) { this._propertyChanged += value; } }
    remove { lock (this.mHandler) { this._propertyChanged -= value; } }
}


...

public MainWindow()
{
    InitializeComponent();
    mHandler.PropertyChanged += mHandler_PropertyChanged;
}

private void mHandler_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    var handler = _propertyChanged;
    if (handler != null)
        _propertyChanged(this, e);
}
Thomas Levesque
Interesting... a good guess at least.
emddudley
A complicating factor was that I updated Windows and hadn't restarted... I don't get exceptions anymore, and your guess was right! WPF was expecting the sender to be the MainWindow object.
emddudley
FWIW your example code is not exactly what I was looking to do, but your guess helped me fix the issue.
emddudley
@emddudley: could you please post the solution as I am curious about this one! thanks
VoodooChild
+2  A: 

My working solution is almost exactly the same as the "desired implementation" in my question, with the addition of the Sender property.

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged
    {
        add { lock (this.mHandler) { this.mHandler.PropertyChanged += value; } }
        remove { lock (this.mHandler) { this.mHandler.PropertyChanged -= value; } }
    }

    public bool CheckboxIsChecked
    {
        get { return this.mHandler.CheckboxIsChecked; }
        set { this.mHandler.CheckboxIsChecked = value; }
    }

    private HandlesPropertyChangeEvents mHandler = new HandlesPropertyChangeEvents();

    public MainWindow()
    {
        InitializeComponent();
        this.mHandler.Sender = this;
    }

    public class HandlesPropertyChangeEvents : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public Sender { get; set; }

        public bool CheckboxIsChecked
        {
            get { return this.mCheckboxIsChecked; }
            set
            {
                this.mCheckboxIsChecked = value;
                PropertyChangedEventHandler handler = this.PropertyChanged;
                if (handler != null)
                    handler(this.Sender, new PropertyChangedEventArgs("CheckboxIsChecked"));
            }
        }

        private bool mCheckboxIsChecked = false;
    }
}

This example is a bit artificial, but in my application moving the event handling code outside of the bound class makes sense.

emddudley
+1 - thanks....
VoodooChild