I've been running into the problem of databinding controls to properties that are updated from threads other than the UI thread. I'm using Winforms but am adopting somewhat of an MVVM architecture so I'm using databindings similar to:
button1.DataBindings.Add("Enabled",_viewModel,"IsActive");
My ViewModel implements
INotifyPropertyChanged
and fires a
NotifyPropertyChanged
event every time one of its parameters, such as IsActive, is changed. I have several processes that are run continuously throughout the entirety of the application's lifetime which I execute on separate threads to keep the UI responsive.
Of course, the problem is that when the property is set from one of these other threads, an
InvalidOperationException
is thrown when the
PropertyChanged
event is fired.
I am trying to stick with the architecture of MVVM so I do not want to have my ViewModel have any knowledge of how the View (Form)is using its parameters. So
my (hopefully temporary) solution to the problem is creating a
Control
in the constructor of my ViewModel object so that I have something to Invoke on within the NotifyPropertyChanged method which looks like:
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
Action action = new Action(() =>
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
if (!_control.InvokeRequired)
action();
else
_control.BeginInvoke(action);
}
}
I have 3 questions regarding all of this:
1) Creating a control for the sole purpose of having something to invoke on so that there is no cross-threading problems seems both ridiculous and wasteful. I thought perhaps a little less wasteful (less memory involved because no control object would need to be created) way of doing this would be to capture and save in a local variable the thread that the ViewModel object is created on by putting
this._uiThread = System.Threading.Thread.CurrentThread;
in the objects constructor and then somehow get the
PropertyChanged
event to fire on this thread but I can't figure out what code would be needed to do this in the <notifypropertychanged> method.
So shortly put: How can I execute a block of code on a specific thread given only a reference to the thread I want it executed on (the UI thread)?
2) Regardless of my solution to the problem I'm having, how would
you resolve this issue given the constraints that the properties
WILL be updated from a different thread and the ViewModel has no knowledge of the View?
3) This last question is just a generalized
WHY?! How is this not such a common problem with databinding that there is not a built in solution for this issue or at the very least an extremely simple solution to implement? In my mind I don't see why, when designing databindings, there isn't another constructor parameter that is of type
Thread
so that the Add method looks like:
Control.DataBindings.Add(string propertyName, object dataSource, string dataMember, Thread bindOnThread);
and whenever a PropertyChanged event is fired, the Binding handles the invoke if it is required. This way there could be multiple forms that all have separate UI threads and the same object's property could be bound to each of them in some way and yet the ViewModel doesn't have to have
any knowledge of the Views. Even in my solution to the problem that I outlined in part (1), I make the assumption that the ViewModel was created on the UI thread and that it is only bound to controls created on that thread. The solution in (3) wouldn't care about any of that and would work regardless... so again WHY isn't there something like this that is built in to handle this common problem?