I tried it again and it works just fine. My unittest must have been screwy at
the time. If you're curious about what I was doing, I've attached the source.
If you're familiar with .Net, think DependencyProperty.
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);
}