The SimpleBinding class (StockSample.Core.Framework.SimpleBinding) is based on an article from CodeProject: Understanding Simple Data Binding by Marc Clifton. Here is the usage pattern we’re looking for:
1: SimpleBinding.Bind(obj1, "Property1", obj2, "Property2");
This statement will bind obj1.Property1 to obj2.Property2. In terms of garbage collection, the Bind method will link the two objects together so that they will have the same lifetime. In some cases, this is not desirable and we may want to look into using WeakReferences in the binding. This makes the code more complicated, but will help in cases where we want everything coupled a lot more loosely.
This example statement, however, is not what is actually in the source code right now. I added an extra parameter of type ISynchronizeInvoke that is called “affinity”. This parameter can be null if you don’t care about affinity (I’ll add an extra method overload to make it look prettier later). Affinity in this case refers to thread affinity. This is very important when binding to UI components because if you make any calls into Windows Forms, you have to make those calls on the thread that created the handle for the controls.
Whenever you want to make a call into the UI (or potentially make a call into the UI), you want to follow this pattern:
1: void DoSomething()
2: {
3: if (affinity != null && affinity.InvokeRequired)
4: {
5: affinity.BeginInvoke(new Action(DoSomething), new object[0]);
6: return;
7: }
8:
9: // action to perform...
10: }
You could easily substitute Action for an anonymous delegate or some other construct that makes sense. The most important line there is line 6, if you don’t return, then you’ll end up falling through and executing the code anyway.
Looking at the Control type, you’ll see that it implements ISynchronizeInvoke. Once the internal handle for the control is created, you can use Control.Invoke and Control.BeginInvoke to post messages on the UI thread’s message loop to execute delegates. Invoke will wait until the message is processed before returning. BeginInvoke will post the message and return immediately. Unlike calling BeginInvoke on a delegate instance, calling Control.BeginInvoke will not post anything into a thread pool, it uses the message loop. Moreover, if the control handle hasn’t been created yet, this method will fail. The safe bet is to find the topmost container for the control and use it as your ISynchronizeInvoke reference. This will make it more likely that the handle has been created. You can even call Control.CreateHandle to force the handle to be created in a more timely manner.
Now that we know why there’s a fifth parameter in the SimpleBinding.Bind method, we should discuss what the method actually does:
- Get information about the properties being bound (using reflection).
- Some properties may not have setters, some properties may not have getters. This implicitly establishes if the binding is two-way or one-way.
- We can only bind the properties that we have access to. If you can’t call the getter or setter from your code, then you can’t bind to it.
- Figure out how the property change is triggered. Right now, the SimpleBinding supports three types of triggers (technically two, but I guess “no trigger” could be construed as a type of trigger… or lack thereof).
- INotifyPropertyChanged: if the object implements this interface, then the SimpleBinding will subscribe to the PropertyChanged event and use that to trigger the synchronization.
- PropertyNameChanged event: This is a pattern that is evident in Windows Forms as well as in other places. In .NET 1.1, there was no INotifyPropertyChanged interface to implement. If the object does not implement INotifyPropertyChanged, then the SimpleBinding will use reflection to find an accessible event named PropertyNameChanged. For example, to look for the change in the “Text” property, we look for an event called “TextChanged”.
- No trigger: If the SimpleBinding can’t find any of the other triggers, then it just won’t bother with trying to figure out when the property changes.
- Force synchronization from obj1.property1 to obj2.property2.
- The convention is that the left property is pushed to the right. If everything is set up ok, then these two should be synchronized.
- A more complete binding will check to see if the binding is one-way and which direction it should go before forcing synchronization.
In the current version of the SimpleBinding, I take the naive approach and assume that the types are the same. This is VERY naive and is totally unrealistic, but will work for the time being.
1: // precondition: _prop1get != null && _prop2set != null
2: object value = _prop1get.Invoke(_object1, new object[0]);
3: _prop2set.Invoke(_object2, new object[] { value });
This code will definitely throw an exception if the property types don’t match up. It would be totally great if there was some pattern or functionality in the .NET Framework that would help us arbitrarily convert types and add new type conversions as needed. Luckily, such a thing exists: IConvertible. All the basic types in .NET implement this interface and we can make the following adjustments:
1: object value = _prop1get.Invoke(_object1, new object[0]);
2: value = Convert.ChangeType(value, _prop2.PropertyType);
3: _prop2set.Invoke(_object2, new object[] { value });
This is a simple change (I got rid of the comment though, the precondition is the same as the code snippet above). Convert.ChangeType will “do the right thing” or throw an exception if the cast is not valid. If you don’t want an exception, you can add the appropriate try/catch and deal with it however you see fit.
The SimpleBinding is just that, simple. It definitely has a lot of room for growth and extension. I purposely made it simple and not very extensible. If someone wanted to really make a flexible binding system, then a pipeline type approach might work. In this way, you can insert a sequence of operations that will be performed when the binding is initialized and when the triggers are hit. The other problem is the strong references between the two objects that are being bound. These are not insurmountable issues and it is not SimpleBinding’s job to solve them all. SimpleBinding is a pattern that I like to follow and it can be adapted to different situations and changed to fit the needs of the application. I find it very useful to be able to use one line of clear code to keep two properties in sync.
1 comment:
Post a Comment