7

I intend to use data binding between a few of my classes. In other words, I am not binding values between a model class and the UI, but to bind variables between different classes.

I have read about data binding in C# on several places, but most of them are referring to binding between Windows Form and a source object.

I am still new to C#. This is how I understand what I should do:

First, for my source object, say has a class name of DataObject. The source object has to implement a INotifyPropertyChange interface, and then trigger the event whenever the property, health, is set to change. I have no problem with this.

Now, suppose I have a target object called CharacterClass. life is a property in CharacterClass, and is also the target property that I want to bind to the source object's health property.

How can I bind the two properties together (both one-way and two-way) in code with only just the ordinary .NET framework?

A bit of background information on why I ask this question:

Just in case you think this is a duplicated question, it's not. I have searched through SE. The other questions on databinding in code are in the context of WPF or XAML, which is not for me. I have also read several articles on MSDN and it seems that I could create a Binding object, and then bind the source and target via BindingOperations.SetBinding(). However, the Binding class seems to be part of the WPF library under the namespace of System.Windows.Data.Binding. Although I am using C#, I doubt I would have the luxury to access to WPF libraries because I'm mainly using C# as only a scripting language within Unity3D. I believe I only have access to the the vanilla .Net framework. But, I am not very sure about this because I am still new to C#.

Carven
  • 14,988
  • 29
  • 118
  • 161

2 Answers2

9

Though there is a lot of support for bindings that is tightly coupled with the UI framework being used, you can still easily write your own binding framework.

Here is a POC which implements one-way binding between properties of two objects.

Note: This is just one of the possible ways, a POC at best (may need fine-tuning for high performance/production scenario) and uses .Net 2.0 classes and interfaces with no dependency on any UI framework (the 'vanilla' .net framework in your words :)). Once you have understood this, you can easily extend this to support 2-way binding as well

class Program
{
    public static void Main()
    {
        Source src = new Source();
        Destination dst = new Destination(src);
        dst.Name = "Destination";
        dst.MyValue = -100;
        src.Value = 50; //changes MyValue as well
        src.Value = 45; //changes MyValue as well
        Console.ReadLine();
    }
}

//A way to provide source property to destination property 
//mapping to the binding engine. Any other way can be used as well
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
internal class BindToAttribute : Attribute
{
    public string PropertyName
    {
        get;
        private set;
    }

    //Allows binding of different properties to different sources
    public int SourceId
    {
        get;
        private set;
    }

    public BindToAttribute(string propertyName, int sourceId)
    {
        PropertyName = propertyName;
        SourceId = sourceId;
    }
}

//INotifyPropertyChanged, so that binding engine knows when source gets updated
internal class Source : INotifyPropertyChanged
{
    private int _value;
    public int Value
    {
        get
        {
            return _value;
        }
        set
        {
            if (_value != value)
            {
                _value = value;
                Console.WriteLine("Value is now: " + _value);
                OnPropertyChanged("Value");
            }
        }
    }

    void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

internal class Destination
{
    private BindingEngine<Destination> _binder;

    private int _myValue;

    [BindTo("Value", 1)]
    public int MyValue
    {
        get
        {
            return _myValue;
        }
        set
        {
            _myValue = value;
            Console.WriteLine("My Value is now: " + _myValue);
        }
    }

    //No mapping defined for this property, hence it is not bound
    private string _name;
    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
            Console.WriteLine("Name is now: " + _name);
        }
    }

    public Destination(Source src)
    {
        //Binder for Source no 1
        _binder = new BindingEngine<Destination>(this, src, 1);
    }
}

internal class BindingEngine<T>
{
    private readonly T _destination;
    private readonly PropertyDescriptorCollection _sourceProperties;
    private readonly Dictionary<string, PropertyDescriptor> _srcToDestMapping;

    public BindingEngine(T destination, INotifyPropertyChanged source, int srcId)
    {
        _destination = destination;

        //Get a list of destination properties
        PropertyDescriptorCollection destinationProperties = TypeDescriptor.GetProperties(destination);

        //Get a list of source properties
        _sourceProperties = TypeDescriptor.GetProperties(source);

        //This is the source property to destination property mapping
        _srcToDestMapping = new Dictionary<string, PropertyDescriptor>();

        //listen for INotifyPropertyChanged event on the source
        source.PropertyChanged += SourcePropertyChanged;

        foreach (PropertyDescriptor property in destinationProperties)
        {
            //Prepare the mapping.
            //Add those properties for which binding has been defined
            var attribute = (BindToAttribute)property.Attributes[typeof(BindToAttribute)];
            if (attribute != null && attribute.SourceId == srcId)
            {
                _srcToDestMapping[attribute.PropertyName] = property;
            }
        }
    }

    void SourcePropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        if (_srcToDestMapping.ContainsKey(args.PropertyName))
        {
            //Get the destination property from mapping and update it
            _srcToDestMapping[args.PropertyName].SetValue(_destination, _sourceProperties[args.PropertyName].GetValue(sender));
        }
    }
}

enter image description here

Amit Mittal
  • 2,646
  • 16
  • 24
2

I believe I only have access to the the vanilla .Net framework

The reality is that data binding is used in UI. So, if anybody talks about data binding, then he automatically implies "data binding in [UI Framework name]".

You may consider object-to-object mapping instead of data binding.

Dennis
  • 37,026
  • 10
  • 82
  • 150
  • Actually, yes, you are right about data binding being for UI. In my situation, I am building an UI, but not with the Windows Form. And all those *target objects* I was referring to are mainly my UI classes. So what I am hoping is for a "custom" of sort data binding technique that I could implement onto my own non-WinForm UI. – Carven Oct 18 '12 at 06:03
  • @xEnOn: the main problem is that binding engines are tightly coupled with UI framework internals. E.g., WPF binding uses dependency properties, so, if you want to use it, you have to write your UI classes to be `DependencyObject` descendants. I think, you ought to write your own binding engine. – Dennis Oct 18 '12 at 06:40
  • I see. Thanks! Looks like I have no luck to use C#'s existing data binding engine. :( Guess I have to look into how I can write my own binding engine. Maybe I will make a new question here to understand how data binding engines work under the hood, to sort of give myself an idea how I can write a data binding engine. Thanks a lot, Dennis. :) – Carven Oct 18 '12 at 08:14
  • Sorry, but this answer doesn't actually attempt to answer the question. The other answer does. – Ian Newson Oct 19 '12 at 21:57