Blogs

Gary's Blog

XAF – Project Management Application #10

     

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:

1

 2

Okay, well that sort of worked, but there are a couple of issues:

  1. The New and Clear buttons are displayed in the lookup.
  2. Only the statuses corresponding to the current task type should be displayed, not all of them.
  3. 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:

3

4

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:

5

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):

6

Right, so let’s fire up our applications now:

7

 8

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:

9

 10

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”:

11

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!
Published Sep 30 2009, 07:55 PM by Gary Short (DevExpress)
Filed under:
Technorati tags: XAF
Bookmark and Share

Comments

 

Valuable Internet Information » XAF ??? Project Management Application #10 - Gary's Blog said:

Pingback from  Valuable Internet Information » XAF ??? Project Management Application #10 - Gary's Blog

October 1, 2009 7:37 AM
 

Chloe Anfield said:

Good work Gary.

October 1, 2009 8:43 AM
 

XAF ??? Project Management Application Index - Gary's Blog said:

Pingback from  XAF ??? Project Management Application Index - Gary's Blog

October 1, 2009 8:47 AM
 

Gary Short (DevExpress) said:

Glad you are enjoying the series Chris!

October 1, 2009 8:49 AM
 

XAF ??? Project Management Application #10 - Gary's Blog « Management said:

Pingback from  XAF ??? Project Management Application #10 - Gary's Blog «  Management

October 1, 2009 9:57 AM
 

Steve Sharkey said:

Okay I'm sure I'm having a thick moment but where did these

Guard.<>

things come from?

Was the idea of having some allowance for us VBers abandoned?

Lots of space dedicated to test scripts - this really wants its own article.

Following the download source from here the status.vb listing is incomplete.

Other than that still very interesting - keep it up.

October 2, 2009 7:26 AM
 

Gary Short (DevExpress) said:

Hello Steve,

The Guard stuff does need it's own blog post and I'll get one up next week. The idea of including VB and C# code in the posts was abandoned yes, as it made the posts far too long, but VB code is provided if you follow the link to the source. I note that you say this is incomplete at the moment and I'll pass that information on. With regard to the test scripts, they do need to be in a post of their own and I'll be blogging about that shortly; however, this series is on how we built this internal application and the tests are part of that and so should be in the post IMHO.

I'm glad you are enjoying the series.

Cheers,

Gary

October 4, 2009 9:29 AM
 

Steve Sharkey said:

Gary,

Just to clarrify - it's not the download that is incomplete but the browsable version. I prefer to browse in this manner to understand examples by working through the source rather than just downloading and running.

No problem with dropping the VB just didn't see it mentioned - I agree that it just gets messy with both and having to put a little effort in to convert/think about what is going on it usually helps it stick in the old grey matter better.

October 5, 2009 2:19 AM
 

Robert Thomas said:

I'm glad you are uncovering issues.  This was one of the main reasons we XAFers wanted you to try and build a RWA.  Thanks for taking the time!!

October 7, 2009 1:01 PM
 

Mike Siegel said:

This is getting good - keep it coming because I'm learning a lot more through this than the training.  

October 22, 2009 4:50 PM
 

Robert Fuchs said:

No post in more than a month?

Is this series already over?

November 2, 2009 6:09 PM
 

Gary Short (DevExpress) said:

Hi Robert.

Nope, don't panic, the series is not done yet. Remember me saying right at the start of this series that the project was a live one we were actually using? Well, the project has gone live and we are waiting for feedback from the team using it.

When we get their feedback, we'll make changes to the current functionality and probably add a bunch of other features too.

November 5, 2009 12:29 PM
 

Lukas Grieger said:

Hi Gary.

Is there a download for the projectfiles or the testfiles? Because I need some support at my project.

December 3, 2009 9:30 AM
 

M. Brekhof said:

Hi Gary,

Any news on the 'The Guard stuff does need it's own blog post and I'll get one up next week. ' or did I miss something?

January 6, 2010 12:12 AM
 

Guillermo Vilas said:

Is there a real purpose to decorate a public property with the persistent attribute? I though that XPO handles all public properties as persistent by default, I meant public not readonly ones.

June 19, 2010 3:34 AM
More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.