Reputation: 35517
I have an Image control with it's source bound to a property on an object(string url to an image). After making a service call, i update the data object with a new URL. The exception is thrown after it leaves my code, after invoking the PropertyChanged event.
The data structure and the service logic are all done in a core dll that has no knowledge of the UI. How do I sync up with the UI thread when i cant access a Dispatcher?
PS: Accessing Application.Current.RootVisual in order to get at a Dispatcher is not a solution because the root visual is on a different thread(causing the exact exception i need to prevent).
PPS: This only is a problem with the image control, binding to any other ui element, the cross thread issue is handled for you.
Upvotes: 6
Views: 2605
Reputation: 5987
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. To provide generic property-change notification, the Person type implements the INotifyPropertyChanged interface and raises a PropertyChanged event when FirstName is changed.
For change notification to occur in a binding between a bound client and a data source, your bound type should either:
Implement the INotifyPropertyChanged
interface (preferred).
Provide a change event for each property of the bound type.
Do not do both.
Example:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
// Change the namespace to the project name.
namespace TestNotifyPropertyChangedCS
{
// This form demonstrates using a BindingSource to bind
// a list to a DataGridView control. The list does not
// raise change notifications. However the DemoCustomer type
// in the list does.
public partial class Form1 : Form
{
// This button causes the value of a list element to be changed.
private Button changeItemBtn = new Button();
// This DataGridView control displays the contents of the list.
private DataGridView customersDataGridView = new DataGridView();
// This BindingSource binds the list to the DataGridView control.
private BindingSource customersBindingSource = new BindingSource();
public Form1()
{
InitializeComponent();
// Set up the "Change Item" button.
this.changeItemBtn.Text = "Change Item";
this.changeItemBtn.Dock = DockStyle.Bottom;
this.changeItemBtn.Click +=
new EventHandler(changeItemBtn_Click);
this.Controls.Add(this.changeItemBtn);
// Set up the DataGridView.
customersDataGridView.Dock = DockStyle.Top;
this.Controls.Add(customersDataGridView);
this.Size = new Size(400, 200);
}
private void Form1_Load(object sender, EventArgs e)
{
// Create and populate the list of DemoCustomer objects
// which will supply data to the DataGridView.
BindingList<DemoCustomer> customerList = new BindingList<DemoCustomer>();
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
// Bind the list to the BindingSource.
this.customersBindingSource.DataSource = customerList;
// Attach the BindingSource to the DataGridView.
this.customersDataGridView.DataSource =
this.customersBindingSource;
}
// Change the value of the CompanyName property for the first
// item in the list when the "Change Item" button is clicked.
void changeItemBtn_Click(object sender, EventArgs e)
{
// Get a reference to the list from the BindingSource.
BindingList<DemoCustomer> customerList =
this.customersBindingSource.DataSource as BindingList<DemoCustomer>;
// Change the value of the CompanyName property for the
// first item in the list.
customerList[0].CustomerName = "Tailspin Toys";
customerList[0].PhoneNumber = "(708)555-0150";
}
}
// This is a simple customer class that
// implements the IPropertyChange interface.
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// The constructor is private to enforce the factory pattern.
private DemoCustomer()
{
customerNameValue = "Customer";
phoneNumberValue = "(312)555-0100";
}
// This is the public factory method.
public static DemoCustomer CreateNewCustomer()
{
return new DemoCustomer();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
NotifyPropertyChanged();
}
}
}
public string PhoneNumber
{
get
{
return this.phoneNumberValue;
}
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged();
}
}
}
}
}
Upvotes: 0
Reputation: 1
When ever we want to update UI related items that action should happen in the UI thread else you will get an invalid cross thread access exception
Deployment.Current.Dispatcher.BeginInvoke( () =>
{
UpdateUI(); // DO the actions in the function Update UI
});
public void UpdateUI()
{
//to do :Update UI elements here
}
Upvotes: 0
Reputation: 6679
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => {...});
Also look here.
Upvotes: 7
Reputation: 1279
I ran into a similar issue to this, but this was in windows forms:
I have a class that has it's own thread, updating statistics about another process, there is a control in my UI that is databound to this object. I was running into cross-thread call issues, here is how I resolved it:
Form m_MainWindow; //Reference to the main window of my application
protected virtual void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
if(m_MainWindow.InvokeRequired)
m_MainWindow.Invoke(
PropertyChanged, this, new PropertyChangedEventArgs(propertyName);
else
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
This seems to work great, if anyone has suggestions, please let me know.
Upvotes: 0
Reputation: 15621
The property getter for RootVisual on the Application class has a thread check which causes that exception. I got around this by storing the root visual's dispatcher in my own property in my App.xaml.cs:
public static Dispatcher RootVisualDispatcher { get; set; }
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new Page();
RootVisualDispatcher = RootVisual.Dispatcher;
}
If you then call BeginInvoke on App.RootVisualDispatcher rather than Application.Current.RootVisual.Dispatcher you shouldn't get this exception.
Upvotes: 0