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.