Previously I was working on an implementation of DynamicObject data binding (see Dynamic object two way data binding), and the solution works great. Since then I have run into the need to be able to update the values from different threads it seems to break the bindings. I am able to update the DynamicObject instance from multiple threads however am unable to keep the bindings updated.
I tried to implement the SynchronizedNotifyPropertyChanged solution provided by Cody Barnes in the SO: INotifyPropertyChanged causes cross-thread error with no success.
Any help with binding a DynamicObject implementation that is updated via a non-ui thread would be greatly appreciated. The solution and gist attached work great (values updated on any thread (ui or non-ui) no problem), it is just the DataBinding to a Form Control that is not.
Edit #1 - (Thank you Reza Aghaei)
I see where my question is a bit vague, here is the implementation and goal I am trying to accomplish.
First, I have the Form which handles the creation of a logic 'engine' which can run tasks (based on internal class methods as well as external method calls or events). So somewhere inside the Form class, I generate this logic 'engine' object (will call it GameEngine for this example).
// GameEngine initializes and provides the DynamicData (MyCustomObject)
// --> engine is defined via public or private field of (this) Form class
engine = new GameEngine();
// Create the binding
LabelTestCounter.Databindings.Add("Text", engine.Data, "TestValue", true, DataSourceUpdateMode.OnPropertyChanged);
Now in the GameEngine class, I instantiate the DynamicData (MyCustomObject)
public class GameEngine
{
public dynamic Data;
// Constructor
public GameEngine()
{
Data = new MyCustomObject();
// I would like to initialize the data here as ownership really
// belongs to the 'GameEngine' object, not the Form
engine.Data.TestValue = "Initial test value";
}
public void StartBusinessLogic()
{
// In reality, this would be a long-running loop that updates
// multiple values and performs business logic.
Task.Run(() => {
data.TestValue = "Another Text";
});
}
}
Note in this example above, the MyCustomObject is (currently) an exact copy of what Reza has provided in his gist in his answer below. In the blog post and the gist provided, I lean towards the 'Option 1' provided as I would like the DynamicData object (MyCustomObject), to be self-containing the logic for synchronization for portability purposes.
Another note, the GameEngine, outside of holding the DynamicData object in a property, should not care whether or not the UI thread is using it, the responsibility of synchronization should (in my mind) remain with the UI thread. With that said, the DynamicData object should be aware of cross threaded calls and handle them accordingly as needed.
Edit #2 - Original question reference update
In reference to:
I tried to implement the SynchronizedNotifyPropertyChanged solution provided by Cody Barnes in the SO: INotifyPropertyChanged causes cross-thread error with no success.
When working with the original SO Dynamic object two way data binding, and the original solution (again thank you Reza), I attempted to implement the referenced 'wrapper' to overcome the problem with cross-threaded updates. However, the only way that I could get the above solution to work was when the properties were hard-coded into the class. When any attempts were made to use a dynamic object, the bindings would either update once and lose the binding or throw some kind of binding exception.
I hope this clarifies the original question a little bit better.
Edit #3 - Symbol not resolved
Not sure if this is a problem, as compilation is working, (and this may be Resharper, not sure as I don't recall the problem previously).
In 'MyCustomObject':
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = new List<MyCustomPropertyDescriptor>();
foreach (var e in dictionary)
properties.Add(new MyCustomPropertyDescriptor(e.Key, (e.Value?.GetType()) ?? typeof(object)));
return new PropertyDescriptorCollection(properties.ToArray());
}
(e.Value?.GetType()) <-- GetType is showing 'Cannot resolve symbol 'GetType'.
however the code does compile without errors so I am not sure why/when exactly I started seeing this.
Edit #4 - Resolved Edit #3
No idea what caused the problem in Edit 3, but it grew into showing other errors (mysteriously) such as Cannot apply indexing with to an expression of type 'System.Collections.Generic.Dictionary'
, however, I did a Build -> Clean Solution
and then closed the solution and reopened it in Visual Studio and the problem seemed to go away with that error highlighting in the editor (which I have been seeing a bit of strange behavior like that since I started playing with Resharper (EAP), so possibly early access bugs? but that problem is unrelated to this SO, the original SO has been resolved and the strange behavior in edit 3 will be better handled by JetBrains/Resharper Team rather than here at this point.
Edit #5 - Special Thanks
I hope this is not inappropriate (if it is, admins feel free to delete/edit this), however, I would like to send a special thank you to Reza Aghaei for all your help both here on SO as well as in the background. Reza, your blog, gists and other assistance with this ongoing problem has really helped both in solving the problem and helping me understand the reasons behind the solution.
To raise OnPropertyChanged
from a non-UI thread in a way which updates the UI, you need to pass an instance of ISynchronizeInvoke
to your implementation of ICustomTypeDescriptor
:
ISynchronizeInvoke syncronzeInvoke;
public MyCustomObject(ISynchronizeInvoke value = null)
{
syncronzeInvoke = value;
}
Then on OnPropertyChanged
use that ISynchronizeInvoke
to Invoke
when InvokeRequired
:
private void OnPropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
{
if (syncronzeInvoke != null && syncronzeInvoke.InvokeRequired)
syncronzeInvoke.Invoke(handler, new object[]
{ this, new PropertyChangedEventArgs(name) });
else
handler(this, new PropertyChangedEventArgs(name));
}
}
This way, the event will raise in UI thread when needed.