Using your own value types
When we plan our work on the whiteboard, we write down work estimations in the form of hours, days or weeks of work: "1w", "2.5d", "4h". Now I have to write an application that supports this kind of estimation. So I thought it would be good to have a special value type called Effort:
[TestFixture] public class EffortTests { [Test] public void TestParse() { Assert.AreEqual(5 * 8, new Effort("1w").Hours); Assert.AreEqual(4, new Effort("0.5d").Hours); Assert.AreEqual(5 * 8 + 1, new Effort("1w1h").Hours); Assert.AreEqual(0, new Effort("1.2.3h").Hours); } [Test] public void TestToString() { Assert.AreEqual("1w", new Effort(5 * 8).ToString()); Assert.AreEqual("2d", new Effort(16).ToString()); Assert.AreEqual("1w1h", new Effort(5 * 8 + 1).ToString()); } }
Here is the code to make these tests work:
public struct Effort { private decimal hours; public Effort(string value) { this.hours = Parse(value); } public Effort(int hours) { this.hours = hours; } public decimal Hours { get { return hours; } set { hours = value; } } private static decimal workDayHours = 8.0m; public static decimal WorkDayHours { get { return workDayHours; } set { workDayHours = value; } } private static decimal workWeekHours = 40.0m; public static decimal WorkWeekHours { get { return workWeekHours; } set { workWeekHours = value; } } private static Regex regex = new Regex(@"((?'weeks'\d+(\.\d+)?)w)?((?'days'\d+(\.\d+)?)d)?((?'hours'\d+(\.\d+)?)h)?"); private static decimal Parse(string value) { decimal result = 0; Match m = regex.Match(value); if (m.Groups["weeks"].Value != String.Empty) { result += decimal.Parse(m.Groups["weeks"].Value) * WorkWeekHours; } if (m.Groups["days"].Value != String.Empty) { result += decimal.Parse(m.Groups["days"].Value) * WorkDayHours; } if (m.Groups["hours"].Value != String.Empty) { result += decimal.Parse(m.Groups["hours"].Value); } return result; } public override string ToString() { string result = ""; int w = (int)Math.Floor(hours / WorkWeekHours); if (w > 0) { result += w.ToString("d") + "w"; } int d = (int)Math.Floor((hours - w * WorkWeekHours) / WorkDayHours); if (d > 0) { result += d.ToString("d") + "d"; } decimal h = hours - w * WorkWeekHours - d * WorkDayHours; if (h > 0) { result += h.ToString("n0") + "h"; } return result; } }
While it is normally a bad idea to make value types mutable, here we need a setter for the Hours property for XPO to be able to restore it from the database.
To use a value type in the persistent XPO class, you have to use the Persistent attribute:
public class Task : BaseObject { ... private Effort estimatedWork; [Persistent] public Effort EstimatedWork { get { return estimatedWork; } set { estimatedWork = value; OnChanged(); } } ... }
Now you can persist Effort type values in the database, but EAF still has no idea what kind of UI control to create for such types.
I will create a Web property editor for the Effort type:
... using DevExpress.ExpressApp; using DevExpress.ExpressApp.Editors; using DevExpress.ExpressApp.Web.Editors; ... [PropertyEditor(typeof(Effort))] public class WebEffortEditor : WebStringPropertyEditor { public WebEffortEditor(Type objectType, DictionaryNode info) : base(objectType, info) { } protected override object GetControlValue() { return new Effort(Editor.Text); } }
This editor will be discovered and used automatically by EAF as long as the module is loaded.