module epic.bindableobject; /** * The Bindable Property System. * A part of the Epic library. * Author: Justin Whear * Created: February 2009 * * This module provides BindableProperty, BindableObject, and AttachedProperty. */ public import std.boxer; debug import std.stdio; static public T DirectCast(T)(Box b) { return unbox!(T)(b); } static public Box Package(T)(T val) { return box(val); } /* public typedef void* nulltype; public nulltype Null; */ private template tSetValue(T) { public void SetValue(BindableProperty bp, T value) { Box v = box(value); _SetValue(bp, v); } } private template tRegister(T) { /** * Registers a property with the Bindable Property system. Takes the property name, the TypeInfo of the property * type (use typeid(x)), a reference to the object which the property will reside on (usually "this"), and a * default value. */ static public BindableProperty Register(string propName, TypeInfo ti, BindableObject owner, T defaultValue) { return _Register(propName, ti, owner, box(defaultValue)); } } /** * BindableProperty, together with the BindableObject form the basis of the Bindable Property System. * Use BindableProperty.Register to add properties to a BindableObject. */ public class BindableProperty { /// The name of the property. This must be unique or it will replace existing properties with the same name. public string PropertyName; /// The type of the underlying property. public TypeInfo Type; /// A default value. The property will be initialized to this. public Box DefaultValue; /// If set, the property will attempt to coerce the value using this path public string DerivedValuePath; // public TypeConverter Converter; // public Event Changed; /** * Default constructor */ public this() { } /** * Constructor overload. * @param string propName The name of the property * @param TypeInfo ti The type of the underlying value * @param Box defaultValue A default value. */ public this(string propName, TypeInfo ti, Box defaultValue) { PropertyName = propName; Type = ti; DefaultValue = defaultValue; } /* mixin tRegister!(bool) tm_bool; alias tm_bool.Register Register; mixin tRegister!(byte) tm_byte; alias tm_byte.Register Register; mixin tRegister!(ubyte) tm_ubyte; alias tm_ubyte.Register Register; mixin tRegister!(int) tm_int; alias tm_int.Register Register; mixin tRegister!(uint) tm_uint; alias tm_uint.Register Register; mixin tRegister!(long) tm_long; alias tm_long.Register Register; mixin tRegister!(ulong) tm_ulong; alias tm_ulong.Register Register; mixin tRegister!(float) tm_float; alias tm_float.Register Register; mixin tRegister!(double) tm_double; alias tm_double.Register Register; mixin tRegister!(real) tm_real; alias tm_real.Register Register; mixin tRegister!(char) tm_char; alias tm_char.Register Register; mixin tRegister!(char[]) tm_string; alias tm_string.Register Register; mixin tRegister!(Object) tm_Object; alias tm_Object.Register Register; mixin tRegister!(nulltype) tm_void; alias tm_void.Register Register; */ /* * This is actually better than using the mixins above, as it forces the using class to box the defaulValue correctly. */ static public BindableProperty Register(string propName, TypeInfo ti, BindableObject owner, Box defaultValue) { return _Register(propName, ti, owner, defaultValue); } /** * Does the actual work of registering this property with the BindableObject. */ static protected BindableProperty _Register(string propName, TypeInfo ti, BindableObject owner, Box defaultValue) { BindableProperty bp = new BindableProperty(); bp.PropertyName = propName; bp.Type = ti; bp.DefaultValue = defaultValue; owner.RegisterBP(bp); return bp; } } public class AttachedProperty : BindableProperty { public Object AttachmentOwner; public this() { super(); } public this(string propName, TypeInfo ti, Object attacher, Box defaultValue) { super(propName, ti, defaultValue); AttachmentOwner = attacher; } } public class BindableObject { protected Box[string] _propHash; this() { InitBindableProperties(); } public bool HasProperty(string name) { Box* loc = (name in _propHash); return (loc !is null); } public Box GetValue(BindableProperty bp) { return _propHash[bp.PropertyName]; } public Box GetValue(string name) { return _propHash[name]; } mixin tSetValue!(bool) tm_bool; alias tm_bool.SetValue SetValue; mixin tSetValue!(byte) tm_byte; alias tm_byte.SetValue SetValue; mixin tSetValue!(ubyte) tm_ubyte; alias tm_ubyte.SetValue SetValue; mixin tSetValue!(int) tm_int; alias tm_int.SetValue SetValue; mixin tSetValue!(uint) tm_uint; alias tm_uint.SetValue SetValue; mixin tSetValue!(long) tm_long; alias tm_long.SetValue SetValue; mixin tSetValue!(ulong) tm_ulong; alias tm_ulong.SetValue SetValue; mixin tSetValue!(float) tm_float; alias tm_float.SetValue SetValue; mixin tSetValue!(double) tm_double; alias tm_double.SetValue SetValue; mixin tSetValue!(real) tm_real; alias tm_real.SetValue SetValue; mixin tSetValue!(char) tm_char; alias tm_char.SetValue SetValue; mixin tSetValue!(string) tm_string; alias tm_string.SetValue SetValue; mixin tSetValue!(Object) tm_Object; alias tm_Object.SetValue SetValue; // String setters public void SetValue(string propName, float value) { Box v = box(value); _SetValue(propName, v); } public void SetValue(string propName, string value) { Box v = box(value); _SetValue(propName, v); } public void SetValue(string propName, Object value) { Box v = box(value); _SetValue(propName, v); } public void SetValue(string propName, Box value) { _SetValue(propName, value); } // Bottom level set functions protected void _SetValue(BindableProperty bp, Box value) { _SetValue(bp.PropertyName, value); } protected void _SetValue(string name, Box value) { if (name in _propHash) { if (_propHash[name] !is value) { _propHash[name] = value; // bp.Changed.Raise(null); } } else throw new Exception("SetValue: Property "~name~" does not exist on "~this.toString); } private void RegisterBP(BindableProperty bp) { _propHash[bp.PropertyName] = bp.DefaultValue; } /** * Attach a property to this object. */ public void AttachProperty(AttachedProperty ap) { _propHash[ap.PropertyName] = ap.DefaultValue; } /** * Detach a property from this object. */ public void DetachProperty(AttachedProperty ap) { _propHash.remove(ap.PropertyName); } protected void InitBindableProperties() { } } /** * Test class */ class Duck : BindableObject { public string Name() { return DirectCast!(string)(GetValue(NameProperty)); } public void Name(string value) { SetValue(NameProperty, value); } public BindableProperty NameProperty; public uint Anger() { return DirectCast!(uint)(GetValue(AngerProperty)); } public void Anger(uint value) { SetValue(AngerProperty, value); } public BindableProperty AngerProperty; public bool CanFly() { return DirectCast!(bool)(GetValue(CanFlyProperty)); } public void CanFly(bool value) { SetValue(CanFlyProperty, value); } public BindableProperty CanFlyProperty; protected override void InitBindableProperties() { super.InitBindableProperties(); NameProperty = BindableProperty.Register("Name", typeid(string), this, Package!(string)("Donald")); AngerProperty = BindableProperty.Register("Anger", typeid(uint), this, Package!(uint)(15)); CanFlyProperty = BindableProperty.Register("CanFly", typeid(bool), this, Package!(bool)(true)); } } unittest { Duck a = new Duck(); Duck b = new Duck(); b.Name = "Daisy"; b.Anger = 3; assert(a.HasProperty("Name")); assert(a.HasProperty("Anger")); assert(!a.HasProperty("A_Fake_Property")); assert(a.Anger == 15); assert(b.Name == "Daisy"); assert(b.Anger == 3); } // Test inheritance unittest { class Mallard : Duck { public float FlightRating() { return DirectCast!(float)(GetValue(FlightRatingProperty)); } public void FlightRating(float value) { SetValue(FlightRatingProperty, value); } public BindableProperty FlightRatingProperty; protected override void InitBindableProperties() { super.InitBindableProperties(); FlightRatingProperty = BindableProperty.Register("FlightRating", typeid(float), this, Package!(float)(0)); } } auto m = new Mallard(); assert(m.HasProperty("Name")); assert(m.HasProperty("Anger")); assert(m.HasProperty("FlightRating")); float testFloat = 0.6; m.FlightRating = testFloat; assert(m.FlightRating == testFloat); } // Test getting and setting by property name unittest { Duck a = new Duck(); a.SetValue("Name", "George"); assert(a.Name == "George"); assert(DirectCast!(string)(a.GetValue("Name")) == "George"); } // Test attached properties unittest { class DuckList { protected Duck[] _list; public Duck[] Flock() { return _list; } public void Add(Duck d) { _list.length = _list.length + 1; _list[$-1] = d; } } class DuckHerder : BindableObject { public DuckList Ducks() { return DirectCast!(DuckList)(GetValue(DucksProperty)); } public void Ducks(DuckList value) { SetValue(DucksProperty, value); } public BindableProperty DucksProperty; public this() { Ducks = new DuckList; } protected override void InitBindableProperties() { super.InitBindableProperties(); DucksProperty = BindableProperty.Register("Ducks", typeid(DuckList), this, Package!(DuckList)(null)); } } DuckHerder herder = new DuckHerder(); herder.Ducks.Add(new Duck()); herder.Ducks.Add(new Duck()); foreach (int i, Duck d; herder.Ducks.Flock) { d.AttachProperty(new AttachedProperty("PlaceInFlock", typeid(int), herder, Package!(int)(99))); assert(d.HasProperty("PlaceInFlock")); d.SetValue("PlaceInFlock", Package!(int)(i)); } assert(DirectCast!(int)(herder.Ducks.Flock[0].GetValue("PlaceInFlock")) == 0); assert(DirectCast!(int)(herder.Ducks.Flock[1].GetValue("PlaceInFlock")) == 1); }