Customising flow editor

From FlexRule Wiki
Jump to: navigation, search

In FlexRule, it is very easy to customize the behaviour of different editors in Rule Designer.

For this tutorial we are going to introduce a custom activity node to the flow editor.

Steps

There are couple of steps that need to be taken in order to create and register a new custom node in designer.

  1. Creating item builder
  2. Creating toolbox
  3. Defining object model to be displayed in property window
  4. Creating custom activity
  5. Registering custom activity

Required Assembly References

When you create a custom activity project you need to reference the following assemblies in your project:

  1. FlexRule.Designer.Common
  2. FlexRule.Designer.Controls
  3. FlexRule.Designer.Controls.Toolbox
  4. FlexRule.Designer.Core
  5. FlexRule.Designer.FlowEngine

For Shapes you also need to reference the following assembly:

  1. MindFusion.Diagramming

Creating item builder

An item builder is a class that is responsible for:

  1. Building an object model associated with your node in an editor
  2. Loading properties from a model to a defined object model
  3. Attaching the item to the editor when it is built
  4. Validating an item
  5. Saving an item

In order to create a builder, you simply need to override a class named ElementItemBuilderBase and then override the following methods:

  1. /// This is the main class used to create the rule node command 
  2. /// For example, this will be created by the builder strategy (NotifElementItemBuilder)
  3. public abstract class ElementItemBuilderBase
  4. {
  5.     /// <summary>
  6.     /// Create the properties for the item
  7.     /// </summary>
  8.     /// <param name="ctx">Context information related to the builder</param>
  9.     public abstract void InitializePropertyCollection(BuildContext ctx);
  10.  
  11.     /// <summary>
  12.     /// When a rule node is being loaded, this method will be used to assign the property's value
  13.     /// </summary>
  14.     /// <param name="node"></param>
  15.     public abstract void AssignAttributes(XmlNode node);
  16.  
  17.     /// <summary>
  18.     /// Indicates if there are any custom child needs to be created. Return false as default.
  19.     /// </summary>
  20.     /// <param name="node"></param>
  21.     public abstract  bool ShouldCreateChild(string childName);
  22.  
  23.  
  24.     /// <summary>
  25.     /// When a node is being created and attached to the document
  26.     /// </summary>
  27.     /// <param name="creator">node creator</param>
  28.     /// <param name="parent">parent of the node to be created</param>
  29.     /// <param name="reference">reference node to the one that is being created</param>
  30.     /// <param name="position">position of the node to be created</param>
  31.     /// <returns></returns>
  32.     public abstract IElementItem Attach(ILogicalDocumentCreator creator, IElementItem parent, IElementItem reference, Position position);
  33.  
  34.     /// <summary>
  35.     /// Override the behaviour for validating nodes
  36.     /// </summary>
  37.     /// <param name="context"></param>
  38.     /// <param name="item"></param>
  39.     public abstract  void ValidateItem(ElementItemValidationContext context, IElementItem item);
  40.  
  41.     /// <summary>
  42.     /// Writes the content of the rule node via the the xml writer
  43.     /// </summary>
  44.     /// <param name="document">The document logic has all the element builders of the rule </param>
  45.     /// <param name="name">name of the file</param>
  46.     /// <param name="extension">file extension, .help.xml is for the designer help and .xml is for the rule</param>
  47.     /// <param name="writer"></param>
  48.     public abstract void WriteContent(ILogicalDocument document, string name, string extension, XmlWriter writer);
  49.  
  50.     /// <summary>
  51.     /// Properties of the object model for which the builder is responsible. 
  52.     /// </summary>
  53.     public DynamicPropertyCollection Properties { get; protected set; }
  54. }

Creating toolbox

The next step is to define Group and Command (custom node) in the toolbox.

Toolbox.png

Defining a command on the toolbox is as easy as implementing an interface named IToolBoxInitializer.

  1. /// <summary>
  2. /// Implement initializer for the toolbox that allows toolbox items be created
  3. /// </summary>
  4. public interface IToolBoxInitializer
  5. {
  6.     /// <summary>
  7.     ///  Initialize commands on the toolbox
  8.     /// </summary>
  9.     /// <param name="toolBoxBuilder"></param>
  10.     void Initialize(IToolBoxWindowBuilder toolBoxBuilder);
  11.  
  12.     /// <summary>
  13.     ///  Stores and reads the next available toolbox (if required)
  14.     /// <remarks>Implement this as an auto property.</remarks>
  15.     /// </summary>
  16.     IToolBoxInitializer Next { get; set; }
  17. }

With the argument that the Initialize method provides, you can create groups and commands on the toolbox. To add a group, for example, you can use:

toolBoxBuilder.ToolBox.Groups.Add("Custom", new ToolboxGroup("Custom"));

And to add a command, for example:

ToolboxGroup windowsFormsGroup = toolBoxBuilder.ToolBox.Groups["Custom"];
windowsFormsGroup.Items.Add(new ToolboxItem(Commands.Notif, ImageKeyNotif, new string[] { "Activity", Commands.Notif }, true));

Defining object model

Each builder (e.g., NotifElementItemBuilder) would have an implemented method called:

public override void InitializePropertyCollection(BuildContext ctx)

This is where your application gets a chance to register the model object properties to be added into the Properties Window. BuildContext gives you information about:

  • Where does this command belong?
  • What is the command name?
  • What is the parent item or builder?

When an item is added to a document, or an item is selected from a document editor, the Properties Window shows that item with list of all available properties. The Property window then allows user to modify values of the selected item. There are multiple different types of view that Property windows allow you to create:

  1. Simple
  2. Collection
  3. Nested

PropertiesWindow.png

Simple Properties

Simple properties are just a name and a value of a specific type (e.g., string, int, enum, etc.). To add a simple property/value you just need to AddProperty to Properties. If Properties is null then you need to instantiate it first. To add a name/value to it, you can simply use a code like this:

  1. // Creates a string property named 'id' with null default value that the display title is '(id)'  
  2. Properties.AddProperty("id", null, typeof(string)).DisplayName = "(id)";
  3.  
  4. // And similarly for description
  5. Properties.AddProperty("Description", string.Empty, typeof(string), "Any description related to this command").DisplayName = "(Description)";
  6. Properties["Description"].AllowEnterValue = true;

Collection of Properties

Now when you want a collection of some other properties, you add the property similarly. However, instead of setting a simple type for a name/property, you use

typeof(List<DynamicPropertyCollection>)

as the type. This makes the Properties window automatically launch a collection editor. For example, let's say we want a collection of strings that is called Messages

  1. var messagesProperty = Properties.AddProperty("Messages", null, typeof(List<DynamicPropertyCollection>));

That sets up the collection editor. In the collection editor, there will be a similar structure to edit messages. In order to set up that structure, you need use ListManager.Schema from Messages property (in this example).

  1. messagesProperty.ListManager.Schema.AddProperty("Message", null, typeof(string)).DisplayName = "Title";

What happens at this point is that when the collection editor is launched, it allows you to modify the list of objects that has one property called Message displayed as Title.

Nested Properties

For nested properties, you need to create an instance of DynamicPropertyCollection and add all of the properties to this collection, then pass that instance to a DynamicObject

  1. // Creates a collection to hold all of the nested properties
  2. var collection = new DynamicPropertyCollection();
  3. collection.AddProperty("uri", null, typeof(string));
  4.  
  5. // Create a dynamic object using the initialised properties
  6. var nestedProperty = new DynamicObject("", collection);
  7.  
  8. // And add the nestedProperty to the 'Properties' in the builder
  9. Properties.AddProperty("ProcSource", nestedProperty, typeof(DynamicObject)).DisplayName = "ProcSource";

Creating custom activity

A custom activity in a flow editor implements the ICustomActivity interface, which is very similar to ElementItemBuilderBase, but with one difference - it allows you to create an Printable object and return it to be displayed as a 'not' in the flow.

public CustomNode CreateNode(IDiagramNodeCreationInfo info, IElementItem element);

Please note that for easier implementation, you can drive a custom activity class from CustomActivityBase

Shapes

There are different ways to create a CustomNode's shape. In FlexRule Designer, we use Shape to build different shapes on a diagram. MindFusion.Diagramming.Shapes in assembly MindFusion.Diagramming will give you some useful predefined shapes. Check http://www.mindfusion.eu/onlinehelp/flowchartnet/Table_of_Predefined_Shapes.htm for a list of predefined shapes.

  1. public override CustomNode CreateNode(IDiagramNodeCreationInfo info, IElementItem element)
  2. {
  3.     return new CustomNode(Shapes.RoundRect)
  4.     {
  5.         FillColorFrom = Color.FromArgb(255, 224, 192),
  6.         FillColorTo = Color.FromArgb(255, 128, 0),
  7.         Size = new Size(10, 10),
  8.     };
  9. }

MindFusion.Diagramming.Shapes provides some pre-built shapes that can be used. Otherwise, you can implement your own by following the link below:

http://www.mindfusion.eu/onlinehelp/flowchartnet/T_MindFusion_Diagramming_Shape.htm

Registering custom activity

You need to register your custom activity in the settings file: FlexRule.Designer.Settings.config

In the Activities section of the settings, add your custom node:

  1.   <Activities>
  2.     <Validator assembly="FlexRule.Designer.ValidationEngine.dll" type="FlexRule.Designer.ValidationEngine.CustomActivity" />
  3.     <Notification assembly="FlexRule.Designer.Core.dll" type="FlexRule.Designer.Notifications.CustomActivity" />
  4.     <Notif assembly="FlexRule.Designer.Sample.FlowCustomActivity.dll" type="FlexRule.Designer.Sample.FlowCustomActivity.NotifCustomActivity" />
  5.   </Activities>

After registering the new activity, you can start dragging and dropping the new activity as well as connecting other nodes to it.

NewCustomActivity.png

Download the sample code

Download the following project: http://www.flexrule.com/wp-content/uploads/2015/03/FlexRule.Designer.Sample.FlowCustomActivity2.zip

Build the class library and put the assembly into the designer folder. When you create a new project, select this assembly as one of your builders in the properties window.


Migration from Old Designer

Assemblies and Types

  • Assembly FlexRule.Designer.Controls.Diagram has been replaced by MindFusion.Diagramming
  • Type ICustomActivity method signature has been changed from
Printable CreateNode(IFlowNodeCreationInfo info, IElementItem element);

to

 CustomNode CreateNode(IDiagramNodeCreationInfo info, IElementItem element);
  • Type Printble and LinkableCustomFigureContainer are replaced by CustomNode that contains a Shape

Re-defined Shapes and Custom Shapes

To use a predefined shape by its identifier, you can simply use

Shape.FromId(string identifier) 

in order to return the instance of a predefined shape by its identifier. For example, Shape.FromId("Display") returns a shape for a Display. Diagram Shape Display.png