Technically, how does data binding engines work under the hood? Especially, how does the mechanism of the "synchroniser" in data binding look like and work like?
In many frameworks like in .NET, Java, Flex, etc, they provide a data binding engine. I have been just using the API calls, and so every thing is easy for me since all I have to do was to make calls to the API.
Now, I am interested in perhaps trying to write a relatively simple data binding engine for a game that I am working on. Although I'm using C#, I have reasons for not being able to use the built-in WinForms and data binding engine (see background info below for reason). Since I can't use the existing data binding engine in C#, I thought I may have to write one on my own. So, I need to know the nitty gritty details of how data binding usually works under the hood. By this, I don't mean how to use data binding in C#. I mean, how does the data binding work internally and architecturally.
I tried to search the web for tutorials and articles on data binding but most of the results came to me as being how to use the existing data binding in C#, which isn't what I want.
So, before I could start planning on writing my own data binder, I thought I would need to know how does data binding engines work under the hood? And even more importantly, how does the mechanism of the "synchroniser" in data binding engine look and work like, ie, how are the data kept synchronised all the time be it in a one-way or two-way binding?
Some bit of background information of why I asked this question:
A while ago, I made a question on how I could use data binding in C# for UIs that are not using the standard WinForms. The answer I got was that the data binding engine in C# is tightly coupled with the WPF/Windows Forms UI. So, I guess I cannot use the existing data binding engine in C#, and have to probably create one on my own. The purpose of this is for a game and I'm working on. Games usually have its own custom UI (non-WinForm). My intention is to setup a MVVM-like design for the UI and gameobjects within the game.
Data binding is the process that couples two data sources together and synchronizes them. With data binding, a change to an element in a data set automatically updates in the bound data set.
In a data binding process, each data change is reflected automatically by the elements that are bound to the data. The term data binding is also used in cases where an outer representation of data in an element changes, and the underlying data is automatically updated to reflect this change.
Introduction to data binding in Android. Android offers support to write declarative layouts using data binding. This minimizes the necessary code in your application logic to connect to the user interface elements. The user variable within data describes a property that may be used within this layout.
Data binding is the process that establishes a connection between the app UI and the data it displays. If the binding has the correct settings and the data provides the proper notifications, when the data changes its value, the elements that are bound to the data reflect changes automatically.
Your question is a really interesting one, but it's scope is actually very large.
A really useful tool in these situation is ILSpy which allows you to look at the framework implementations.
One thing I would take issue with is the following statement:
The answer I got was that the data binding engine in C# is tightly coupled with the WPF/Windows Forms UI
I disagree; the data binding engine is tightly coupled to the .Net eventing implementation, but the Target and Source can be anything - most examples will be Windows Forms, WPF or ASP.Net because they are the most common front ends for .Net languages, but it's perfectly possible to use multi binding in other scenarios without a UI too.
What happens when you add a two way binding? Well, if we look at the source for MultiBinding we note a few interesting things:
OneWay
or TwoWay
NotifyOnSourceUpdated
and NotifyOnTargetUpdated
Which have the basic form:
// System.Windows.Data.MultiBinding /// <summary>Gets or sets a value that indicates whether to raise the <see cref="E:System.Windows.FrameworkElement.SourceUpdated" /> event when a value is transferred from the binding target to the binding source.</summary> /// <returns>true if the <see cref="E:System.Windows.FrameworkElement.SourceUpdated" /> event will be raised when the binding source value is updated; otherwise, false. The default value is false.</returns> [DefaultValue(false)] public bool NotifyOnSourceUpdated { get { return base.TestFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated); } set { bool flag = base.TestFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated); if (flag != value) { base.CheckSealed(); base.ChangeFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated, value); } } }
i.e. we use events to tell us when the source is updated (OneWay
) and when the target is updated too (for TwoWay
binding)
Note there is also a PriorityBinding
class which operates in a similar way except you can subscribe to multiple data sources, and it will prioritize the one that returns data soonest.
So the shape of how this works is clear - when we create a binding, we are subscribing to changes on one side (for read only updates) or on both sides (when the data can be changed in the GUI for example, and sent back to the data source), with all notifications managed via eventing.
The next question is, really, who manages the events? The simple answer is that both the Target and Source do. That's why implementing INotifyPropertyChanged
is important, for example - all the Bindings really do is create a contract for how both sides should subscribe to each other's changes - it's the contract the Target and Source are tightly coupled to, really.
ObservableCollection is an interesting test case to study as it's widely used in GUI applications for promoting updates in the data source to the UI, and for sending changes to the data in the UI back to the underlying data source.
Notice (by looking at the code) how the actual eventing for communicating that things have changed is really simple, BUT the code for managing Adds, Removes, Updates is actually very dependent on consistency via the SimpleMonitor property (BlockReentrancy
and CheckReentrancy
) - it's effectively guaranteeing that the operations are atomic and that subscribers are notified of the changes in the order they happen AND that the underlying collection is consistent with those updated.
This really is the tricky part of the whole operation.
In short, the DataBinding implementation in .Net is not tightly coupled to the GUI technologies; it's just that most examples will present DataBinding in the context of Windows Forms, WPF or ASP.Net applications. The actual databinding is event driven, and, for you to leverage it, it is more important to synchronize and manage the changes to your data - the DataBinding framework will just allow you to couple Target and Source together in shared data updates via the contract (Interfaces) it defines.
Have fun ;-)
EDIT:
I sat down and created two classes, MyCharacter
and MyCharacterAttribute
with the express aim of setting up TwoWay databinding between the Health
and HealthValue
attributes:
public class MyCharacter : DependencyObject { public static DependencyProperty HealthDependency = DependencyProperty.Register("Health", typeof(Double), typeof(MyCharacter), new PropertyMetadata(100.0, HealthDependencyChanged)); private static void HealthDependencyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { } public double Health { get { return (double)GetValue(HealthDependency); } set { SetValue(HealthDependency, value); } } public void DrinkHealthPotion(double healthRestored) { Health += healthRestored; } } public class MyCharacterAttributes : DependencyObject { public static DependencyProperty HealthDependency = DependencyProperty.Register("HealthValue", typeof(Double), typeof(MyCharacterAttributes), new PropertyMetadata(100.0, HealthAttributeDependencyChanged)); public double HealthValue { get { return (Double)GetValue(HealthDependency); } set { SetValue(HealthDependency, value); } } public List<BindingExpressionBase> Bindings { get; set; } public MyCharacterAttributes() { Bindings = new List<BindingExpressionBase>(); } private static void HealthAttributeDependencyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { } }
The most important things to note here are the inheritance from DependencyObject and the implementation of the DependencyProperty.
In practice, then, what happens is the following. I created a simple WPF form and set up the following code:
MyCharacter Character { get; set; } MyCharacterAttributes CharacterAttributes = new MyCharacterAttributes(); public MainWindow() { InitializeComponent(); Character = new MyCharacter(); CharacterAttributes = new MyCharacterAttributes(); // Set up the data binding to point at Character (Source) and // Property Health (via the constructor argument for Binding) var characterHealthBinding = new Binding("Health"); characterHealthBinding.Source = Character; characterHealthBinding.NotifyOnSourceUpdated = true; characterHealthBinding.NotifyOnTargetUpdated = true; characterHealthBinding.Mode = BindingMode.TwoWay; characterHealthBinding.IsAsync = true; // Now we bind any changes to CharacterAttributes, HealthDependency // to Character.Health via the characterHealthBinding Binding var bindingExpression = BindingOperations.SetBinding(CharacterAttributes, MyCharacterAttributes.HealthDependency, characterHealthBinding); // Store the binding so we can look it up if necessary in a // List<BindingExpressionBase> in our CharacterAttributes class, // and so it "lives" as long as CharacterAttributes does, too CharacterAttributes.Bindings.Add(bindingExpression); } private void HitChracter_Button(object sender, RoutedEventArgs e) { CharacterAttributes.HealthValue -= 10.0; } private void DrinkHealth_Button(object sender, RoutedEventArgs e) { Character.DrinkHealthPotion(20.0); }
Clicking the HitCharacter button decreases the CharacterAttributes.HealthValue
property by 10. This fires an event, which, via the Binding we set up earlier, also subtracts 10.0 from the Character.Health
value. Hitting the DrinkHealth button restores Character.Health
by 20.0 and also increases the CharacterAttributes.HealthValue
by 20.0.
Also note that this stuff is indeed baked into the UI framework - FrameworkElement
(which inherits from UIElement
) has SetBinding
and GetBinding
implemented on it. Which makes sense - DataBinding GUI elements is a perfectly valid scenario for user interfaces! If you look deeper, though, SetValue
, for example, is just calling BindingOperations.SetBinding
on an internal interface, so we can implement it without actually having to use a UIElement
(as per the example above). The one dependency we have to carry over, however, is DependencyObject
and DependencyProperty
- these are mandatory for the DataBinding to work, but, as long as your objects inherit from DependencyObject
, you don't need to go anywhere near a text box :-)
The downside, however, is that some of the Binding stuff has been implemented via internal
methods, so you may encounter scenarios where the binding actions you want to implement may require you to write additional code because you simply can't access the framework implementations like native classes can. However, TwoWay databinding like the example above is perfectly possible, as has been shown.
The part "Life before binding" in this post was easier for me to get the understanding of how two-way binding can be created.
The idea is the same as James describes. You fire an event when a property setter is called. But you do it only if the value of the property has changed. Then you subscribe on the event. And in the subscriber you change a dependent property. For the dependent property you do the same (to get 2-way binding). This schema doesn't die with stack overflow since setter instantly returns if the value hasn't changed.
I reduced the code in the post to this manual implementation of 2-way binding:
static void Main() { var ui = new Ui(); var model = new Model(); // setup two-way binding model.PropertyChanged += (propertyName, value) => { if (propertyName == "Title") ui.Title = (string) value; }; ui.PropertyChanged += (propertyName, value) => { if (propertyName == "Title") model.Title = (string) value; }; // test model.Title = "model"; Console.WriteLine("ui.Title = " + ui.Title); // "ui.Title = model" ui.Title = "ui"; Console.WriteLine("model.Title = " + model.Title);// "model.Title = ui" Console.ReadKey(); } } public class Ui : Bindable { private string _title; public string Title { get { return _title; } set { if (_title == value) return; _title = value; OnChange("Title", value); // fire PropertyChanged event } } } public class Model : Bindable { private string _title; public string Title { get { return _title; } set { if (_title == value) return; _title = value; OnChange("Title", value); // fire PropertyChanged event } } } public class Bindable { public delegate void PropertyChangedEventHandler( string propertyName, object value); public event PropertyChangedEventHandler PropertyChanged; protected void OnChange(string propertyName, object value) { if (PropertyChanged != null) PropertyChanged(propertyName, value); } }
You can use aspects (PostSharp for example) for intercepting property setter calls and hence get rid of backing fields. Your classes will look like this:
public class Ui : Bindable { [Bindable] public string Title { get; set; } [Bindable] public string Name { get; set; } }
And using reflection you can reduce the binding code to just:
Binder.Bind(() => ui.Title, () => model.Title); Binder.Bind(() => ui.Name, () => model.Name);
My proof of concept: https://gist.github.com/barsv/46650cf816647ff192fa
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With