Question

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.

Était-ce utile?

La solution

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:

  • It exposes a BindingMode property which describes the binding scenario - typically either OneWay or TwoWay
  • It exposes two interesting events: 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.

Autres conseils

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

It's quite a simple idea, but not necessarily simple to implement. You need 2 way event notification. Your model object notifies the data binding framework when it changes, and the UI notifies the data binding framework of any user interactions.

On the model side this means writing your models to notify of any changes in properties (e.g. implement the INotifyPropertyChanged interface), and changes to collections (e.g. use ObservableColleciton). On the UI side you can just hook up to the events provided by the UI system.

If you don't want to change your model (i.e. you want the data binding to work on POCOs) then you would need some trigger to tell the databinding system to check the model for changes using reflection. You'd probably call this manually whenever your code changes the model.

After that it's just plumbing up all the events, and that's probably where it gets messy as you need a library of different types of binding objects that connect various types of data to various types of UI.

It may be worth reviewing the documentation for knockout.js, http://knockoutjs.com/, obviously is a web solution, but the principals are the same and it goes into a lot of detail on the components that are in the library which in principle will be very similar to the components of any system.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top