Phew, well I’m back from a hectic week at BASTA and all set to post the latest in our series on creating an project management application using XAF. So, what are we going to do in this post? Well, first of all our status changing algorithm isn’t very good is it? For a start you can’t go backwards to a previous status and it assumes that their is only one possible status to go forward to. These two things make our model of status pretty unrealistic. So, let’s do something about that.
A state machine or workflow solution sounds best suited to fixing this problem and, as I’ve said, there are plans for such things, but for now we’ll allow users to decide which status suits best. We’ll get rid of the “Change status” button and make the status list editable. To do this, we’ll remove the “protected” keyword from the TaskBase’s Status property setter declaration.
Now we’re able to change the status manually:

Okay, well that sort of worked, but there are a couple of issues:
- The New and Clear buttons are displayed in the lookup.
- Only the statuses corresponding to the current task type should be displayed, not all of them.
- Statuses are sorted in the alphabetical order instead of their logical sequence;
So, let’s write a test for the New button to ensure it’s unavailable:
#DropDB XProjectEasyTest
#Application XProjectWin
#Application XProjectWeb
*Action Navigation(Project Tasks)
*Action New
#IfDef XProjectWin
*ExecuteEditorAction Status
!ActionAvailable New
#EndIf
#IfDef XProjectWeb
!ExecuteEditorAction Status(Add)
#EndIf
Looking at this test, you can see that the syntax is different between the web and winforms actions; oops. :-) Don’t worry we are aware of this and will fix it. Running that test at the moment fails, not surprisingly. To remove the New button we’ll create a new AvailableStatuses property on the TaskBase Class:
public abstract class TaskBase : BaseObject {
…
private XPCollection<TaskStatus> availableStatuses;
[Browsable(false)]
public XPCollection<TaskStatus> AvailableStatuses {
get {
if(availableStatuses == null) {
availableStatuses = new XPCollection<TaskStatus>(StatusSet.Items);
availableStatuses.BindingBehavior = CollectionBindingBehavior.AllowNone;
}
return availableStatuses;
}
}
…
}
Check out the [Browsable(false)] attribute above, using this means that the property will not be visible in the UI. Meantime, the AvailableStatuses field will hold a local status collection. We’ll set this property’s BindingBehavior to CollectionBindingBehavior.AllowNone, so that the New button won’t be available in the AvailableStatuses lookup. Also note that the availableStatuses field is initialized only once when its value is first accessed. Now let’s limit the statuses displayed in the lookup to those that belong to the current task type; to do that we apply the DataSourceProperty("AvailableStatuses") attribute to the Status property:
[Persistent, DataSourceProperty("AvailableStatuses")]
public TaskStatus Status {
get { return GetPropertyValue<TaskStatus>("Status"); }
set { SetPropertyValue<TaskStatus>("Status", value); }
}
Launching the application, we see that the “new” button has gone and the status list looks much better:
Running the functional test, above, now passes as a result of this change. :-)
The next thing we are going to do is to nail that “Clear” button problem, we need to remove it so that the Status can’t be set to NULL. We’ll also write a unit test to check this:
[Test]
public void Status_CannotAssignNull() {
TestProjectTaskClass task = new TestProjectTaskClass(Session);
Assert.IsNotNull(task.Status);
try {
task.Status = null;
Assert.Fail();
}
catch (ArgumentNullException){ }
}
No surprise, the test fails. So let’s modify the Status property a little:
public TaskStatus Status
{
get { return GetPropertyValue<TaskStatus>("Status"); }
set
{
if (!IsLoading)
Guard.ArgumentNotNull(value, "Status");
SetPropertyValue<TaskStatus>("Status", value);
}
}
Running the test again shows it passes. Ah, don’t you just love the little victories? :-)
Now we need to remove the “Clear” button from the Windows Forms application. To do this we’ll add a new TaskStatusLookupController View Controller. We want this Controller to be activated only for the Status lookup and so we’ll set its TargetViewId property to TaskStatus_LookupListView. We’ll write the code to remove the “Clear” button in the Controller’s OnActivated method:
public class TaskStatusLookupController : ViewController
{
public TaskStatusLookupController()
{
TargetViewId = "TaskStatus_LookupListView";
}
protected override void OnActivated()
{
base.OnActivated();
LookupWindowController standardController = Frame.GetController<LookupWindowController>();
if (standardController != null)
standardController.Active.SetItemValue("TaskStatusLookupController", false);
}
}
As you can see, the “Clear” button is now gone:
Now, let’s go ahead and remove the “Clear” button from the ASP.Net application. Umm… yeah… Turns out there’s no easy way to do this at the moment, so we’ll create a suggestion for the XAF team to write such functionality, and quietly move on. :-)
Moving on… let’s fix the issue of the alphabetical order of the statuses. We’ll replace our enumerations with string arrays that contain statuses in the required order and we’ll modify the UpdateDatabase method in the TaskBase class to achieve this:
protected static void UpdateDatabase(Session session, string[] statusNames, string statusSetName)
{
TaskStatusSet statusSet = FindTaskStatusSet(session, statusSetName);
if (statusSet == null)
{
TaskStatus[] statusList = new TaskStatus[statusNames.Length];
statusSet = new TaskStatusSet(session, statusSetName);
TaskStatus nextStatus = null;
for (int j = statusNames.Length - 1; j >= 0; j--)
{
string statusName = statusNames[j];
statusList[j] = new TaskStatus(session, statusSet, nextStatus, statusName);
nextStatus = statusList[j];
}
statusSet.FirstStatus = statusList[0];
statusSet.LastStatus = statusList[statusList.Length - 1];
statusSet.Save();
}
}
We also need to change the initialisation code of our Task:
public class ProjectTask : TaskBase {
public static readonly string[] InitialStatusNames = new string[] { "Draft",
"NotStarted",
"InProgress",
"Paused",
"Completed" };
…
public new static void UpdateDatabase(Session session) {
UpdateDatabase(session, InitialStatusNames, StatusSetName);
}
…
}
Having made this change, I’ll leave you to remove the enumerations and amend the unit tests accordingly. Also, we need to amend the AvailableValuesForTaskStatus functional test. Change:
!FillForm
Status = Completed
To:
*FillForm
Status = Completed
To change the sort order of the statuses in the lookup we’ll add an Index property to the TaskStatus class. Since we are going to use indexes instead of an ordered list, we’ll remove the Next property. In the TaskStatus constructor we’ll replace the nextStatus parameter with the index parameter:
public class TaskStatus : BaseObject {
public TaskStatus(Session session, TaskStatusSet owner, int index, string name) : base(session) {
Guard.ArgumentNotNull(owner, "owner");
Guard.ArgumentNotNullOrEmpty(name, "name");
Owner = owner;
Name = name;
Index = index;
}
public int Index {
get { return GetPropertyValue<int>("Index"); }
set { SetPropertyValue<int>("Index", value); }
}
...
}
We’ll also initialize the new field in the TaskBase class’ UpdateDatabase method:
protected static void UpdateDatabase(Session session, string[] statusNames, string statusSetName)
{
TaskStatusSet statusSet = FindTaskStatusSet(session, statusSetName);
if (statusSet == null)
{
TaskStatus[] statusList = new TaskStatus[statusNames.Length];
statusSet = new TaskStatusSet(session, statusSetName);
for (int j = 0; j < statusNames.Length; j++)
{
statusList[j] = new TaskStatus(session, statusSet, j, statusNames[j]);
}
statusSet.FirstStatus = statusList[0];
statusSet.LastStatus = statusList[statusList.Length - 1];
statusSet.Save();
}
}
Now we must get rid of the NextStatus button and the TaskBase class’ SetNextStatus method. We’ll remove this method as well as the ChangeTaskStatusController Controller and we’ll remove some unit tests which are now useless. For this purpose we’ll update and move the correct status order checking logic from the descendant tasks tests to the TaskBase tests:
[Test]
public void UpdateDatabase_CorrectStatusSequence()
{
TaskStatusSet statusSet = TestProjectTaskClass.FindTaskStatusSet(Session, TestProjectTaskClass.StatusSetName);
Assert.IsNotNull(statusSet);
List<string> statusList = new List<string>(TestProjectTaskClass.InitialStatusNames);
foreach (TaskStatus currentStatus in statusSet.Items)
{
Assert.AreEqual(statusList.IndexOf(currentStatus.Name), currentStatus.Index);
}
}
Now we’ll configure the order of the items in the lookup editor for the status property. In the Model Editor open the TaskStatus_LookupListView node and add a new column for the Index property, disable sorting for the Name property and enable sorting for the Index property (set the required value for the SortOrder attribute):
Right, so let’s fire up our applications now:

Yay! The winform application looks great and the webform application… meh, not so much. Looks like we’ve found a bug. The list of objects in Simple mode (ComboBox edit) is always sorted by Name regardless of what the settings in ListView node are.
Well, while that bug is getting fixed we’ll go ahead and introduce the capability to modify/edit status sets from the UI. First, we’ll apply the [NavigationItem("Settings")] attribute to the TaskStatusSet class this will mean that we can now edit the StatusSet objects in the UI:

Hmm, well it’s a start but there are still issues: the Name property is editable, which can cause a mismatch between a task type and the corresponding status set. To fix this, we’ll make the TaskStatusSet’s Name property setter protected. Additionally we’ll apply the Persistent attribute, so that XPO will persist this property:
[Persistent]
public string Name
{
get { return GetPropertyValue<string>("Name"); }
protected set { SetPropertyValue<string>("Name", value); }
}
Also, the “Task Status Set” caption in the navigation control isn’t very useful. We’ll rename it “Task Statuses”. To do this, add the DisplayName attribute to the TaskStatusSet class:
[NavigationItem("Settings"), System.ComponentModel.DisplayName("Task Statuses")]
public class TaskStatusSet : BaseObject
{
…
}
Next, the default layout of controls isn’t very convenient. For example, the Name property should be displayed first. We’ll use the Model Editor to fix this. Double click the Model.DesignedDiffs.xafml file in the module project, navigate to the Application\Views\TaskStatusSet_DetailView\Layout node. Right click the design surface and choose “Customize Layout”:
Statuses should be sorted by their indexes. To configure the sort order we’ll use the Model Editor (again <grin>). If we navigate to the TaskStatusSet_Items_ListView node, we’ll see that by default the Name property is used for sorting purposes (as it’s the default property). We want to sort by the Index property. To achieve this, change the SortIndex and SortOrder attributes’ values for the Name and Index properties. To check the default sorting we’ve written a functional test - StatusSet_Items_DefaultSortByIndex.ets which now passes:
#DropDB XProjectEasyTest
#Application XProjectWin
#Application XProjectWeb
*Action Navigation(Task Statuses)
*ProcessRecord
Name = ProjectTask
*CheckTable Items
Columns = Index
Row[1] = 0
Row[2] = 1
Row[3] = 2
Row[4] = 3
Row[5] = 4
Users shouldn’t be able to create/delete StatusSets, we should only allow editing.Again we invoke the Model Editor and set the AllowNew and AllowDelete attribute to False for the TaskStatusSet_ListView and TaskStatusSet_DetailView Views. And again we write a functional test to check that the New and Delete Actions are disabled (StatusSet_DisabledNewDeleteActions.ets script file):
#DropDB XProjectEasyTest
#Application XProjectWin
#Application XProjectWeb
*Action Navigation(Task Statuses)
!ProcessRecord
Name = ProjectTask
Action = Delete
!ActionAvailable TaskStatusSet.New
;using ProjectTask's existing statuses
*ProcessRecord
Name = ProjectTask
!ActionAvailable TaskStatusSet.Delete
We still need to filter the drop-down lookups for the FirstStatus and LastStatus properties. To do this we’ll apply the [DataSourceProperty("Items")] attribute to both properties of the TaskStatusSet class:
public class TaskStatusSet : BaseObject
{
…
[DataSourceProperty("Items")]
public TaskStatus FirstStatus {
get { return GetPropertyValue<TaskStatus>("FirstStatus"); }
set { SetPropertyValue<TaskStatus>("FirstStatus", value); }
}
[DataSourceProperty("Items")]
public TaskStatus LastStatus {
get { return GetPropertyValue<TaskStatus>("LastStatus"); }
set { SetPropertyValue<TaskStatus>("LastStatus", value); }
}
}
Phew, well I don’t know about you, but I’ve had quite enough for this post. If you want to play along at home, you can download the code from
here. Next time, we’ll integrating his application with another internal application, in the meantime, if you are doing anything interesting with XAF or XPO drop me a line and tell me all about it and… happy XAFing!