Wednesday, September 21, 2011

Creating a State Machine module for eXpand Framework–Part 2

Prerequisites
Part 1

In this post we are going to enhance the State Machine module UI. Remember that along with all the usual XAF goodies we can now use Xpand code base which gives us a lot more options. Our StateMachineTransitionPermission has 2 lookups, StateMachineName and StateMachine. Our goal is to populate both of these cascading lookups without creating a platform specific module.

StateMachineNames

Creating lookups is a common scenario for which Xpand provides a set of property editors and controllers. By contrast with other business frameworks XAF allows maximum flexibility. Therefore in most cases we are able to code in such a generic way that everything could live in separate frameworks such as eXpand. Now, in order to populate the StateMachine name I am going to derive a new controller from a specialized abstract controller which is Xpand.ExpressApp.SystemModule.PopulateController<T>. This controller uses the PredefinedValues attribute of the XAF model. When filling the attribute with a set of values separated by semicolons XAF will create a lookup with these values targeting each supported platform.

image

However, if at runtime we set the value of the PredefinedValues attribute this will be written at model’s lastlayer and it will make it dirty. We want to avoid this because we want to leave the lastlayer intact.. To cater for this need the populate controller uses a hack. First it stores the lastlayer in a variable then removes it from the model’s layers collection. As a result it is possible to modify the new lastlayer as shown in the Populate method and then return the clean old one to its place. Now the model has all the necessary information with a clean userdiffs layer and while XAF is creating a new view can get the PredefinedValues string from it and create the lookups.

public abstract class PopulateController<T> : ViewController<ObjectView> {

protected virtual void Populate(Func<IModelMember, string> collect) {

var name = PropertyName;

if (name != null) {

var model = ((ModelApplicationBase)Application.Model);

var lastLayer = model.LastLayer;

model.RemoveLayer(lastLayer);

PopulateCore(collect, name);

model.AddLayer(lastLayer);

}

}

private void PopulateCore(Func<IModelMember, string> collect, string propertyName) {

IModelMember modelMember = View.Model.ModelClass.AllMembers.FirstOrDefault(member => member.Name == propertyName);

if (modelMember != null) {

modelMember.PredefinedValues = collect.Invoke(modelMember);

}

}

}

Although this seems like a complicated explanation users need not be intimidated! The implementation of our controller that will populate all StateMachineNames is as simple as this,

public class StateMachinePopulateController : PopulateController<StateMachineTransitionPermission> {

protected override string GetPredefinedValues(IModelMember wrapper) {

IList<XpoStateMachine> xpoStateMachines = ObjectSpace.GetObjects<XpoStateMachine>(null);

return xpoStateMachines.Select(machine => machine.Name).AggregateWith(";");

}

protected override Expression<Func<StateMachineTransitionPermission, object>> GetPropertyName() {

return permission => permission.StateMachineName;

}

}

The first thing we did was to provide the propertyName in the GetPropertyName method. Then using the GetPredifinedalues method we return the semincolon delimited string with the machine names. This very simple controller is capable of populating the statemachine lookup for win and web platforms!.
StateCaptions
This is a cascading lookup and as a result when the current StateMachineName changes it needs to provide a list of all its StateCaptions. To this end we are going to use a specialized property editor, Xpand’s StringLookupEditor. This supports the DataSourceProperty XAF attribute which will be used to provide the StateCaption collection. Moreover when using Xpand it is possible to mark editors with an interface and host it in a transparent module. We can then use the Xpand PropertyEditor attribute with the type of the interface as parameter to tell XAF which propertyeditor will be created at runtime. Finally we need to apply all these along with an ImmediatePostData to the StateMachineName property. The permission will look like this,

[ImmediatePostData]

public string StateMachineName { get; set; }

//IStringLookupPropertyEditor lives in Xpand.ExpressApp assembly

//Xpand.ExpressApp.Web.PropertyEditors.StringLookupPropertyEditor, Xpand.ExpressApp.Win.PropertyEditors.StringLookupPropertyEditor inherit from IStringLookupPropertyEditor

[PropertyEditor(typeof(IStringLookupPropertyEditor))]

[DataSourceProperty("StateCaptions")]

public string StateCaption { get; set; }

IList<string> _stateCaptions = new List<string>();

[Browsable(false)]

public IList<string> StateCaptions {get {return _stateCaptions;}}

If you look carefully at this code however you may notice that __stateCaptions count is always zero. Let me remind you here that the StateMachineTransitionPermission is a non persistent sessionless object. This means that the object is not handled by an ObjectSpace therefore a call like ObjectSpace.FindObjectSpaceByObject(this) will always return null. In addition the permission does not implement INotifyPropertyChanged so we need to synchronize the class just before the StateCaptions are requested. Below you can see a modified version of the StateMachinePopulateController,

public class StateMachinePopulateController : PopulateController<StateMachineTransitionPermission> {

protected override void OnViewControlsCreated() {

base.OnViewControlsCreated();

var stringLookupPropertyEditor = GetPropertyEditor(permission => permission.StateCaption) as IStringLookupPropertyEditor;

if (stringLookupPropertyEditor != null)

stringLookupPropertyEditor.ItemsCalculating += StringLookupPropertyEditorOnItemsCalculating;

}

void StringLookupPropertyEditorOnItemsCalculating(object sender, HandledEventArgs handledEventArgs) {

var propertyEditor = GetPropertyEditor(permission => permission.StateMachineName);

if (propertyEditor != null && View.IsControlCreated) {

var stateMachineTransitionPermission = ((StateMachineTransitionPermission)View.CurrentObject);

var readOnlyCollection = GetStateCaptions(propertyEditor);

stateMachineTransitionPermission.SyncStateCaptions(readOnlyCollection, propertyEditor.ControlValue as string);

}

}

ReadOnlyCollection<string> GetStateCaptions(PropertyEditor propertyEditor) {

var stateMachineName = propertyEditor.ControlValue as string;

return ObjectSpace.GetObjects<XpoState>(state => state.StateMachine.Name == stateMachineName).Select(

state => state.Caption).ToList().AsReadOnly();

}

Finally we add the new SyncStateCaptions method and the full version of the permission will be,

[NonPersistent]

public class StateMachineTransitionPermission : PermissionBase {

[ImmediatePostData]

public string StateMachineName { get; set; }

[PropertyEditor(typeof(IStringLookupPropertyEditor))]

[DataSourceProperty("StateCaptions")]

public string StateCaption { get; set; }

IList<string> _stateCaptions = new List<string>();

[Browsable(false)]

public IList<string> StateCaptions {get {return _stateCaptions;}}

public void SyncStateCaptions(IList<string> stateCaptions, string machineName) {

StateMachineName = machineName;

_stateCaptions = stateCaptions;

}

}

To support platform independent cascading lookups we wrote only about 10 lines of code! This is proof of how much XAF architecture cuts down on development costs. The module can be downloaded from the Xpand download page and we are happy to hear your feedback. Remember that your questions are the best candidates for future posts!

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

0 comments:

Post a Comment