Using your own value types

XAF Team Blog
03 May 2006

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.

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
Tags
No Comments

Please login or register to post comments.