Welcome to part 8 of a series of blog posts illustrating the building of our project management application. In this post we’ll continue our work with Project statuses.
Currently a user can change from one status to another without any consideration as to whether that change makes sense or not. This behaviour is not ideal, so we want to be able to specify which changes are valid and, as we already have the ProjectTaskStatuses enumeration, we’ll be using that to specify the order of the changes. The first thing we are going to do is to add the NextStatus property to the TaskStatus class:
[Persistent]
public TaskStatus NextStatus
{
get { return GetPropertyValue<TaskStatus>("NextStatus"); }
protected set { SetPropertyValue<TaskStatus>("NextStatus", value); }
}
Then we’ll modify the constructor so that it takes the next valid Status as a parameter:
public TaskStatus(Session session, TaskStatusSet owner, TaskStatus nextStatus, string name)
: base(session)
{
Guard.ArgumentNotNull(owner, "owner");
Guard.ArgumentNotNullOrEmpty(name, "name");
Owner = owner;
Name = name;
NextStatus = nextStatus;
}
To reflect these changes, we’ll also have to change the ProjectTask’s UpdateDatebase method:
public static void UpdateDatabase(Session session)
{
TaskStatusSet statusSet = FindTaskStatusSet(session);
if (statusSet == null)
{
string[] statusNames = Enum.GetNames(typeof(ProjectTaskStatuses));
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();
}
}
And, being good programmers, we’ll provide a test for this functionality:
[Test]
public void UpdateDatabase_CorrectStatusSequence()
{
TaskStatusSet statusSet = ProjectTask.FindTaskStatusSet(Session);
Assert.IsNotNull(statusSet);
TaskStatus currentStatus = statusSet.FirstStatus;
Assert.AreEqual(ProjectTaskStatuses.Draft.ToString(), currentStatus.Name);
currentStatus = currentStatus.NextStatus;
Assert.AreEqual(ProjectTaskStatuses.NotStarted.ToString(), currentStatus.Name);
currentStatus = currentStatus.NextStatus;
Assert.AreEqual(ProjectTaskStatuses.InProgress.ToString(), currentStatus.Name);
currentStatus = currentStatus.NextStatus;
Assert.AreEqual(ProjectTaskStatuses.Paused.ToString(), currentStatus.Name);
currentStatus = currentStatus.NextStatus;
Assert.AreEqual(ProjectTaskStatuses.Completed.ToString(), currentStatus.Name);
Assert.AreEqual(null, currentStatus.NextStatus);
}
We also need to write a functional test, in EasyTest, to ensure that users can’t set an incorrect status:
#DropDB XProjectEasyTest
#Application XProjectWin
#Application XProjectWeb
*Action Navigation(Project Tasks)
*Action New
*CheckFieldValues
Status = Draft
!FillForm
Status = Completed
When we run this test it fails, of course, as we’ve not yet specified how we are going to limit the user’s choice of Status. Now, there are two ways of going about this:
- We can limit the available Statuses in the combobox.
- We can make the combobox read only and apply the Status via an Action which would hold all the required logic for specifying the legal next status.
I think we’ll favour the second option as the cleaner solution, so let’s mark the ProjectTask’s Status property with the Custom attribute, to make it read-only:
[DataSourceProperty("StatusSet.Items"), Custom("AllowEdit", "false")]
public TaskStatus Status {
get { return GetPropertyValue<TaskStatus>("Status"); }
protected set { SetPropertyValue<TaskStatus>("Status", value); }
}
This makes the Status field read-only on the UI, but we still have to implement the status changing functionality. To do this we’ll implement a new View Controller:
public class ChangeTaskStatusController : ViewController
{
SimpleAction changeTaskStatusAction;
public ChangeTaskStatusController()
{
changeTaskStatusAction = new SimpleAction(this, "ChangeStatus", PredefinedCategory.RecordEdit);
changeTaskStatusAction.Execute += new SimpleActionExecuteEventHandler(action_Execute);
changeTaskStatusAction.SelectionDependencyType = SelectionDependencyType.RequireSingleObject;
TargetObjectType = typeof(ProjectTask);
}
private void action_Execute(object sender, SimpleActionExecuteEventArgs e)
{
((ProjectTask)e.CurrentObject).Status = ((ProjectTask)e.CurrentObject).Status.NextStatus;
}
}
As you can see from the constructor declaration, we didn’t use the Controller Designer. Instead we subscribe to the events and create an Action manually, adding the following functionality to our application:

Well okay, this works but putting the business logic in the controller is not best practice as it’s hard to test and you get no reuse from your code, it also breaks the “separation of concerns rule”. So, to fix this, we’ll add the SetNextStatus method to the ProjectTask class:
public void SetNextStatus()
{
if (this.Status == this.StatusSet.LastStatus)
{
throw new InvalidOperationException();
}
this.Status = this.Status.NextStatus;
}
And we’ll modify the controller’s execute event handler:
private void action_Execute(object sender, SimpleActionExecuteEventArgs e)
{
ProjectTask currentTask = (ProjectTask)e.CurrentObject;
currentTask.SetNextStatus();
if (currentTask.Status == currentTask.StatusSet.LastStatus)
{
changeTaskStatusAction.Enabled.SetItemValue(string.Empty, false);
}
}
And, just for good measure, we’ll write a test to ensure that an exception is thrown if a wrong status is set:
[Test]
public void IncorrectStatusSet()
{
ProjectTask task = new ProjectTask(Session);
for (int i = 0; i < task.StatusSet.Items.Count - 1; i++)
{
task.SetNextStatus();
}
try
{
task.SetNextStatus();
Assert.Fail();
}
catch (InvalidOperationException) { }
}
Since a task’s status is now changed by a dedicated method, we’ll make the ProjectTask’s Status field read-only, by making the setter protected, clearing all the old attributes and applying the Persistent attribute:
[Persistent]
public TaskStatus Status {
get { return GetPropertyValue<TaskStatus>("Status"); }
protected set { SetPropertyValue<TaskStatus>("Status", value); }
}
To ensure that the ChangeStatus Action is activated/deactivated correctly, we’ll write a functional test. The test requires a couple of test ProjectTasks in the test database. So, we’ll extend the Updater’s UpdateDatabaseAfterUpdateSchema method of the Xproject.Module project:
public class Updater : ModuleUpdater {
...
public override void UpdateDatabaseAfterUpdateSchema() {
base.UpdateDatabaseAfterUpdateSchema();
ProjectTask.UpdateDatabase(Session);
#if EASYTEST
ProjectTask task = new ProjectTask(Session);
task.Name = "DraftTask";
task.Save();
task = new ProjectTask(Session);
task.Name = "CompletedTask";
task.SetNextStatus();
task.SetNextStatus();
task.SetNextStatus();
task.SetNextStatus();
task.Save();
#endif
}
}
And here’s the EasyTest test:
#DropDB XProjectEasyTest
#Application XProjectWin
#Application XProjectWeb
*Action Navigation(Project Tasks)
*Action Filter(All Tasks)
*SelectRecords
Columns = Name
Row = DraftTask
*ActionAvailable Change Status
*ClearSelection
*SelectRecords
Columns = Name
Row = CompletedTask
!ActionAvailable Change Status
*ClearSelection
*SelectRecords
Columns = Name
Row = DraftTask
*ActionAvailable Change Status
Umm… Which doesn’t work. If you run it, it fails with: “Error in test: Action: 'Change Status' is available, line 22”. It looks like, once disabled, the button stays disabled even for other project tasks. To correct this, we’ll modify the controller – update the action_Execute event handler and add another couple of event handler to update the Action’s status based on the currently selected task:
public class ChangeTaskStatusController : ViewController {
…
protected override void OnActivated() {
base.OnActivated();
View.CurrentObjectChanged += new EventHandler(View_CurrentObjectChanged);
}
void View_CurrentObjectChanged(object sender, EventArgs e) {
UpdateChangeStatusActionState();
}
private void UpdateChangeStatusActionState() {
if(View.CurrentObject != null)
changeTaskStatusAction.Enabled["ChangeTaskStatusController"] = ((ProjectTask)View.CurrentObject).CanChangeStatus;
}
private void action_Execute(object sender, SimpleActionExecuteEventArgs e) {
ProjectTask currentTask = (ProjectTask)e.CurrentObject;
currentTask.SetNextStatus();
UpdateChangeStatusActionState();
}
}
For this updated Controller to work, we’ll add a new CanChangeStatus property to the ProjectTask class:
[Browsable(false)]
public bool CanChangeStatus
{
get { return this.Status != this.StatusSet.LastStatus; }
}
Now, if we run the test again we see that it passes – Phew!
Well, I think we’ll leave it there for this post, the code is available if you want to play along at home. In the next post we’ll add a new kind of task with a new set of statuses, I hope you’ll join me for that. Until then, happy XAFing!