Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

BPMN2-Modeler/DeveloperTutorials/CreateCustomShape

Versions

This Tutorial was developed with Eclipse 4.4 (Luna) and BPMN2-Plugin version 1.1.1.

Changing the shape for a Custom Task

This section explains how to change the shape and visual appearance of a custom model element. This tutorial assumes that you already have extended a Task element. See the DeveloperTutorials/CreateCustomTask for details how to extend a custom task element.

The CustomShapeFeatureContainer

To change the visual appearance of a model object, the base class CustomShapeFeatureContainer can be extended. This class is typically already extended for a Custom Task implementation. To adapt the visual representation the method createFeatureContainer() need to be overwritten. This method creates a FeatueContainer which is used to display the visual representation of a model object.

 @Override
 protected TaskFeatureContainer createFeatureContainer(IFeatureProvider fp) {
	return new TaskFeatureContainer() {
        ....
 }

It is important to return the corresponding FeatureContainer type as defined in the plugin extension point in plugin.xml

 <customTask
           featureContainer="org.imixs.bpmn.ImixsTaskFeatureContainer"
           id="org.imixs.workflow.bpmn.ProcessEntityTask"
           name="Imixs Task"
           runtimeId="org.imixs.workflow.bpmn.runtime"
           type="Task"
	....
	>
  </customTask>


For example, if you are extending a Task object (like in the plugin.xml above), you need to return the corresponding class "org.eclipse.bpmn2.modeler.ui.features.activity.task.TaskFeatureContainer". If you extend another model type e.g. IntermediateCatchEvent you need to return an instance of 'org.eclipse.bpmn2.modeler.ui.features.event.IntermediateCatchEventFeatureContainer'.


Overwriting getAddFeature()

In your custom implementation of the FeatureContainer you next need to overwrite the method addFeature.


@Override
public IAddFeature getAddFeature(IFeatureProvider fp) {
	 return new AddTaskFeature(fp) { 			
 		@Override
 		protected void decorateShape(IAddContext context, 
			ContainerShape containerShape, Task businessObject)   {
 		.....
		}
	};
} 

Also here take care about the returned ClassType which should match your object model type. (e.g. for a IntermediateCatchEvent this would be the class 'org.eclipse.bpmn2.modeler.ui.features.event.AddIntermediateCatchEventFeature').

To change the shape design you now overwrite the method decorateShape. In this method changes of the figure can be made. See the following example which changes the size and background color of a custom task element:


@Override
public IAddFeature getAddFeature(IFeatureProvider fp) {
	return new AddTaskFeature(fp) {
	@Override
	protected void decorateShape(IAddContext context, 
 	 	 	ContainerShape containerShape, Task businessObject) {
		super.decorateShape(context, containerShape, businessObject);
		Rectangle selectionRect = (Rectangle)containerShape.getGraphicsAlgorithm();
		int width = 160;
		int height = 90;
		selectionRect.setWidth(width);
		selectionRect.setHeight(height);
			
		Task ta = BusinessObjectUtil.getFirstElementOfType(containerShape, Task.class);
		if (ta!=null) {
			Shape shape = containerShape.getChildren().get(0);
			ShapeStyle ss = new ShapeStyle();
			ss.setDefaultColors(new ColorConstant(144, 176, 224));
			StyleUtil.applyStyle(shape.getGraphicsAlgorithm(), ta, ss);
		}						
	}
	};
}

Additional you can also overwrite the getCreateFeature() which can be use to add extension attributes to your business object (bpmn2 element) - see also the BPMN2 examples code for more details. You will also want to provide your own images for the tool palette by overriding getCreateImageId() and getCreateLargeImageId() here.

BPMN2-Modeler-bpmn tutorial customshape 01.png

Updating a CustomShapeFeatureContainer dynamically

Another feature of the CustomShapeFeatureContainer is the possibility to update the shape and visual appearance of a custom model element dynamically. For example you may want to change a icon or the color of an element depending on the properties assigned to the business object. In this case you can overwrite the method getUpdateFeature(). Similar to the getAddFeature() method, this method returns an instance of an IUpdateFeature instance. The implementation of the updateFeature need a little bit more care to control whether the CusstomShape need to be rendered or not. So the important method to be implemented in the IUpdateFeature implementation is the method updateNeeded(). This method indicates the graphiti framework if the layout need to be refreshed. The Update itself can be implemented by overwriting the method update() which is similar to the decorateShape() method of the IAddFeature implementation. The important part of the update method is to indicate the framework when the update was completed because the updateNeeded() method can be called several times during an layout cycle. The trick here is to store the state of the layout process in a PropertyValue inside the corresponding PictogrammmElement.

So the typical implementation of the IUpdateFeature can look like illustrated in the following code snippet:

 @Override
   public IUpdateFeature getUpdateFeature(IFeatureProvider fp) {
 		MultiUpdateFeature multiUpdate = (MultiUpdateFeature) super.getUpdateFeature(fp);
 		// add a new updateFeature to the exiting list of features....
 		multiUpdate.addFeature(new  AbstractUpdateBaseElementFeature<Task>(fp) {
 			
 			/*
 			 * method to layout the shape
 			 */
 			@Override
 			public boolean update(IUpdateContext context) {		
 				// update layout of CustomShapeFeatureContainer
 				// .... do some graphic stuff... 
 				
 				// finally update pictorgrammElement state 
 				FeatureSupport.setPropertyValue((ContainerShape)context.getPictogramElement(), "evaluate.property", myState);
 				return true;
 			}
 		
 			/*
 			 * method to signal the graphiti framework if the shape need to be updated
 			 */
 			@Override
 			public IReason updateNeeded(IUpdateContext context) {
 				IReason reason = super.updateNeeded(context);
 				if (reason.toBoolean())
 					return reason;
 				
 				
 				// test if we already have stored the update state in the pictorgramElement...
 				PictogramElement pe = context.getPictogramElement();
 				String myState = FeatureSupport.getPropertyValue(pe, "evaluate.property");
 				
 				//....
 				
 				// compare the pictorgram state with current BusinessObject
 				BaseElement ta = (BaseElement) getBusinessObjectForPictogramElement(pe);
 				// ...
 				newState=........ // custom business logic to determine a custom property from the business object..
 				
 				// update is needed if the property has changed....
 				if (Boolean.parseBoolean(myState) != newState){
 					// indicate a reason to update the shape
 					return Reason.createTrueReason("evaluate.property changed");
 				}
 
 				// otherwise no reason to update the element now.
 				return Reason.createFalseReason("");
 			     }
 			});
 		
 		return multiUpdate;
 	}

If you do not take care of the correct implementation of the updateNeeded() method, you will see a red dashed line, indicating that Graphiti has not finished the update cycle. In those cases check if your update-reason is cleared correctly at the end of the update() method.

Trigger a shape refresh on a property change event

If you want to trigger an update of your custom shape element depending on an update of a model value, you can implement an Adapter Class with the notifyChange() method to call the update() method on your IUpdateFeature.

See the following code example:

 // get corresponding EObject object from my BaseElement and add an adapter...
 myModelValue.eAdapters().add(new AdapterImpl() {
 	public void notifyChanged(Notification notification) {
 		int type = notification.getEventType();
 
 		if (type == Notification.SET) {
 			Object newValue = notification.getNewValue();
 			if (newValue != null) {
 				// get the PictogrammElement form the current selection...
 				PictogramElement pe = null;
 				PictogramElement[] pictogramElementList = getDiagramEditor().getSelectedPictogramElements();
 				if (pictogramElementList != null && pictogramElementList.length > 0) {
 					pe = pictogramElementList[0];
 					// get the updateContext object
 					UpdateContext updateContext = new UpdateContext(pe);
 					// find the IUpdateFeature and request an update...
 					IUpdateFeature iUpdateFeature = getDiagramEditor().getDiagramTypeProvider()
 							.getFeatureProvider().getUpdateFeature(updateContext);
 					if (iUpdateFeature.canUpdate(updateContext))
 						iUpdateFeature.update(updateContext);
 				}
 
 			}
 		}
 	}
 });


Updating a CustomShapeFeatureContainer using Adapters

Another - more elegant - way to change the shape of an Element, is to use an Adapter class. See the Adapters Tutorial for more information. An Adapter class offers more control to handle changes of properties. But the Adapter classes have also the disadvantage that the order how Adapters are applied to an BPMN element can not be foreseen. So in some cases you have a more predictable result when using the method decorateShape as described in the beginning of this article.

Copyright © Eclipse Foundation, Inc. All Rights Reserved.