Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

INotifyPropertyChanged and Threading

I have a base class implementing INotifyPropertyChanged:

protected void OnNotifyChanged(string pName)
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(pName));
    }
}

public event PropertyChangedEventHandler PropertyChanged;

I have a derived class with a property Latitude like so:

private double latitude;

public double Latitude
{
    get { return latitude; }
    set { latitude = value; OnNotifyChanged("Latitude"); }
}

My derived class also has a method Fly that manipulates Latitude.

I also have a Form with a TextBox bound to Latitude of my derived class:

txtLat.DataBindings.Clear();    
txtLat.DataBindings.Add("Text", bindSrc, "Latitude");

A thread is used to kick off Fly like so:

Thread tFly = new Thread(f.Fly);
tFly.IsBackground = true;
tFly.Start();

When Latitude changes, an exception is thrown:

DataBinding cannot find a row in the list that is suitable for all bindings.

like image 515
wulfgarpro Avatar asked Jan 28 '11 05:01

wulfgarpro


People also ask

Is INotifyPropertyChanged thread safe?

The new way is thread-safe because the compiler generates code to evaluate PropertyChanged one time only, keeping the result in a temporary variable.

What is the purpose of INotifyPropertyChanged?

The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed. For example, consider a Person object with a property called FirstName .

How do you implement INotifyPropertyChanged in WPF?

To implement INotifyPropertyChanged you need to declare the PropertyChanged event and create the OnPropertyChanged method. Then for each property you want change notifications for, you call OnPropertyChanged whenever the property is updated.


1 Answers

This seems to be an odd issue with thread affinity. Ultimately, the code is trying to do the update from a non-UI thread - I'm unclear why it isn't just displaying the cross-thread exception, though - I wonder whether this is actually a catch-all exception handler. If I remove the BindingSource (and bind directly to the object, which is valid) you do get a cross-thread exception (which I expected).

Personally, I would be inclined to handle this manually, i.e. subscribe to the event with a method that does an Invoke to the UI thread and updates the Text manually. However, I'm just checking if some previous cross-threaded binding code might help...


Here's an example using Invoke:

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

class FlightUav : INotifyPropertyChanged
{
    protected void OnNotifyChanged(string pName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(pName));
    }
    public event PropertyChangedEventHandler PropertyChanged;
    private double _latitude;
    public double Latitude
    {
        get { return _latitude; }
        set { _latitude = value; OnNotifyChanged("Latitude"); }
    }
    public void Fly()
    {
        for (int i = 0; i < 100; i++)
        {
            Latitude++;
            Thread.Sleep(10);
        }
    }
    [STAThread]
    static void Main()
    {
        using (Form form = new Form())
        {
            FlightUav currentlyControlledFlightUav = new FlightUav();

            currentlyControlledFlightUav.PropertyChanged += delegate
            { // this should be in a *regular* method so that you can -= it when changing bindings...
                form.Invoke((MethodInvoker)delegate
                {
                    form.Text = currentlyControlledFlightUav.Latitude.ToString();
                });
            };


            using (Button btn = new Button())
            {
                btn.Text = "Fly";
                btn.Click += delegate
                {
                    Thread tFly = new Thread(currentlyControlledFlightUav.Fly);
                    tFly.IsBackground = true;
                    tFly.Start();
                };
                form.Controls.Add(btn);
                Application.Run(form);
            }
        }
    }


}

Here's an example using a (modified) version of some old threading code of mine:

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

class FlightUav : INotifyPropertyChanged
{
    protected void OnNotifyChanged(string pName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(pName));
    }
    public event PropertyChangedEventHandler PropertyChanged;
    private double _latitude;
    public double Latitude
    {
        get { return _latitude; }
        set { _latitude = value; OnNotifyChanged("Latitude"); }
    }
    public void Fly()
    {
        for (int i = 0; i < 100; i++)
        {
            Latitude++;
            Thread.Sleep(10);
        }
    }
    [STAThread]
    static void Main()
    {
        using (Form form = new Form())
        {
            FlightUav currentlyControlledFlightUav = new FlightUav();
            BindingSource bindSrc = new BindingSource();
            var list = new ThreadedBindingList<FlightUav>();
            list.Add(currentlyControlledFlightUav);
            bindSrc.DataSource = list;

            form.DataBindings.Clear();
            form.DataBindings.Add("Text", list, "Latitude");

            using (Button btn = new Button())
            {
                btn.Text = "Fly";
                btn.Click += delegate
                {
                    Thread tFly = new Thread(currentlyControlledFlightUav.Fly);
                    tFly.IsBackground = true;
                    tFly.Start();
                };
                form.Controls.Add(btn);
                Application.Run(form);
            }
        }
    }


}
public class ThreadedBindingList<T> : BindingList<T>
{
    private readonly SynchronizationContext ctx;
    public ThreadedBindingList()
    {
        ctx = SynchronizationContext.Current;
    }
    protected override void OnAddingNew(AddingNewEventArgs e)
    {
        SynchronizationContext ctx = SynchronizationContext.Current;
        if (ctx == null)
        {
            BaseAddingNew(e);
        }
        else
        {
            ctx.Send(delegate
            {
                BaseAddingNew(e);
            }, null);
        }
    }
    void BaseAddingNew(AddingNewEventArgs e)
    {
        base.OnAddingNew(e);
    }
    protected override void OnListChanged(ListChangedEventArgs e)
    {
        if (ctx == null)
        {
            BaseListChanged(e);
        }
        else
        {
            ctx.Send(delegate
            {
                BaseListChanged(e);
            }, null);
        }
    }
    void BaseListChanged(ListChangedEventArgs e)
    {
        base.OnListChanged(e);
    }
}
like image 63
Marc Gravell Avatar answered Sep 24 '22 01:09

Marc Gravell