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.

Tags
no comments
No Comments

Please login or register to post comments.