How To Customize the EMF Client Platform

Customize the Look of Model Elements

As you can see in the example application, model elements can be created and viewed in the model explorer of the EMF Client Platform. For every element, the model explorer shows an icon, text and its children. All this is enabled by the code generated by EMF or, more precisely, by the edit plugin. The edit plugin contains a so-called Item Provider for every element of the model. The item provider contains all necessary information for an element to render it in a view such as the model explorer. EMF generates one item provider per model element in the edit plugin.  For the “Make It Happen” model, there are three items providers:

  • TaskItemProvider

  • UserItemProvider

  • UserGroupItemProvider

The pre-generated item providers are capable of showing a generated icon and a default label. Additionally, the item provider allow you to create a tree view (model explorer), which follows the containment hierarchy of the underlying model.  In the “Make It Happen” example, subtasks are shown as children of tasks without any adaptation. However, the code generator cannot anticipate the final look for your custom model elements. Therefore, it usually makes sense to adapt this part. This tutorial describes how to:

  • Exchange the icon used for an element

  • Adapt the text shown beside the icon

  • Adapt which children are shown for a certain element

Exchange Icons

The most urgent adaptation is usually exchanging the generated icons of model elements for custom icons. The generated icons provide a first visualization of model elements, however, they all look quite similar and do not really allow you to distinguish the type of element. In the “Make It Happen” example, we want to introduce icons for all three elements. The icon for Task should be dependent on the state of the task (open or complete).  Icons are from this page.

  • User: image18 How To Customize the EMF Client Platform

  • UserGroup: image16 How To Customize the EMF Client Platform

  • Open Task: image14 How To Customize the EMF Client Platform

  • Completed Task: image02 How To Customize the EMF Client Platform

Like most attributes related to a certain model element, the icon is specified in the relevant item provider of an element. The item provider can be found in the edit plugin generated by EMF.  In the “Make It Happen” example, the class UserItemProvider is generated by EMF for the model element User. Every item provider implements a method getImage(Object object). In the pre-generated item providers, this method returns one of the generated images named after the element type (e.g., “User.gif”) found in the icons folder of the edit plugin. The easiest way to replace an icon that is a gif file is simply to replace the gif file in the icons folder. If the custom icon has another format, or if the displayed icon should indicate a state of the model element (e.g., if a task is open or closed), the code of the generated item provider needs to be adapted.

Before adapting the code, you should add the “@generate NOT” tag to the method to prevent it from being overriden during the next generation of the edit plugin. The following code example shows the necessary adaptations on the UserItemProvider to replace the icons with a custom file “User.png”

/**
* This returns User.gif.
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated NOT
 */
@Override
public Object getImage(Object object) {
		return overlayImage(object, getResourceLocator().getImage("full/obj16/User.png"));
}

You will see the new icons in the model explorer view just by restarting the example application:

image25 How To Customize the EMF Client Platform<

If you override the existing generated gif image, EMF will not override it. If you use another image format and delete the generated gif file, EMF will recreate it. Therefore you should set the “Image” attribute in the genmodel to false for the respective model element:

image05 How To Customize the EMF Client Platform

The item provider of the model element task needs to return two different icons depending on the state of the task. Therefore, we process the object that is given in the getImage method as a parameter. It is the model element for which the icon is retrieved. As the TaskItemProvider is only responsible for tasks, we can safely cast the object, check the state of the task and return the corresponding icon:

/**
 * This returns Task.gif.
 * <!-- begin-user-doc -->
 * <!-- end-user-doc -->
 * @generated NOT
 */
 @Override
public Object getImage(Object object) {
	Task task = (Task) object;
	if(task.isDone()){
 	    return overlayImage(object, getResourceLocator().getImage("full/obj16/bullet_green.png"));
	}
	else{
            return overlayImage(object, getResourceLocator().getImage("full/obj16/bullet_red.png"));
	}
}

Again after a restart, the UI will show the new icons. Even more significantly, it will update the icon if the state of a task changes. So test this: open a task in the editor (double click) and check the “done” attribute:

image03 How To Customize the EMF Client Platform

Adapt Label

As you might have noticed, there is already a label being displayed beside the icon for every model element. This label displays the type of every model element as well as the name of Tasks and UserGroups and the first name of Users. This default has been pre-generated by EMF and is reused by the EMF Client Platform. In fact, EMF will assume the first string attribute of any model element is the label and add the class name before the value of this attribute. If you are happy with this scheme but just want to change the attribute being displayed as a label, you can change the attribute in the genmodel. Click on the class whose label you want to adapt and change the value for the label feature in the properties view. After regenerating the edit plugin, the changes are directly reflected in the UI.

image09 How To Customize the EMF Client Platform

However, in many cases, the label needs to be adapted even more. In the “Make It Happen” example, we want to do two adaptations. First, we want to remove the class name from the label. Since we added nice icons earlier, allowing the user to identify the elements, we don’t want to display the class name anymore. Additionally, the label of a User should show a combination of the first name and the last name.

As before for the icons, these adaptations are done within the item provider of an element. To adapt the label override the method getText, as before the method getImage. In the following code example, a string is concatenated from the first and the last name of the user.

/**
 * This returns the label text for the adapted class.
 * <!-- begin-user-doc -->
 * <!-- end-user-doc -->
 * @generated NOT
 */
@Override
public String getText(Object object) {
	User user = (User) object;
	String firstName = user.getFirstName();
	String lastName = user.getLastName();
	String ret = "";
	if(firstName!=null&&(!firstName.equals(""))){
		ret=ret+firstName;
	}
	if(lastName!=null&&(!lastName.equals(""))){
		if(!ret.equals("")){
			ret=ret+" ";
		}
		ret=ret+lastName;
	}
	return ret;
}

After restarting the application, the EMF Client Platform displays the new labels in the explorer and the editor. Please note, that the labels and the icons are even updated on any change of a model element without any further effort.

image08 How To Customize the EMF Client Platform

Adapt Visible Children

The item provider is also responsible for creating the hierarchy of model elements shown in the model explorer. Every item provider is capable of returning a list of children for a certain model element. The pre-generated item provider follows the containment hierarchy specified in the Ecore model. That is the reason why users can already be shown as children of UserGroups and why Tasks can have subtasks within the explorer. In most cases, the containment hierarchy of the model defines the right structure for the tree in the explorer. However, the hierarchy is adaptable and children can be added or removed from the tree. This adaptation is again done within the genmodel. Open the genmodel and navigate to any reference. In the properties view, you can configure whether the reference is shown as children within the tree of the explorer (Property “Children”). With the property “Create Child”, you can influence whether the explorer offers the possibility of creating elements as children with a right click.

image01 How To Customize the EMF Client Platform

 

To try this feature in the “Make It Happen” example model, you could set the Children property of the reference “tasks” of the element user to true. If you do this, all tasks of a user will be displayed within the tree of the explorer.

image12 How To Customize the EMF Client Platform

However, from an application point of view, this customization does not really make sense. A user probably has a large number of tasks, therefore, not all of them should be displayed in the tree. As stated before, for most applications, the given containment hierarchy does already define the desired hierarchy of the tree. For the “Make It Happen” example application, we conduct an alternative customization. The children customization is not only available for references but also for multi attributes. The “Make It Happen” example defines a multi string attribute “eMails” for the model element user. If you set the children property in the genmodel to true for this attribute, the explorer will create a child node for every email address of a user:

image06 How To Customize the EMF Client Platform

We will use these nodes in a later tutorial to enable sending emails to these addresses just by double clicking on them.

Define Model Validation Constraints

Model Validation Constraints allow you to specify rules for your model that can be validated. For example, you can specify that a certain attribute may not be empty. The EMF Client Platform is capable of showing the result of model validations in two places. First, the model explorer displays a warning on elements failing the validation. Second, the editor highlights attributes that cause the validation to fail.

EMF allows you to specify certain validation constraints within a model. For example, the multiplicity of a reference can be specified. The EMF Client Platform will use these constraints wherever possible. In the “Make It Happen” example, the model specifies that every user has to have at least one email address by setting the lower bound of this attribute to 1. Therefore, the editor and the model explorer will mark any user without an email address in the model explorer and the editor. The editor will also mark all parent elements, which allows a user to find constraint violation in the model:


image091 How To Customize the EMF Client Platform

The editor will mark the attribute or reference causing the violation allowing the user to fix the problem:


image00 How To Customize the EMF Client Platform

However, some constraints cannot be added directly to the model and need to be specified programmatically. In the “MakeItHappen” example, tasks need to have a non-empty name, but this cannot be expressed directly in Ecore. Therefore, this tutorial describes how to add a custom validation rule. These custom constraints will also be processed by the EMF Client Platform and therefore displayed in the UI.

To add custom validation constraints, EMF Client Platform facilitates EMF’s built-in validation mechanism. To add a constraint, an operation must first be added to a model element.  In our example, we add “Task” to the model element. This is done in the Ecore model by right clicking on the element “Task” => “New Child” => “EOperation”. The operation should be assigned a meaningful name; in our example we use “hasName”. To be recognized by EMF validation, the new operation has to return an EBoolean, specified as the EType of the EOperation in the property view. Additionally, it has to have two parameters.  You can add these in the Ecore editor by right clicking on the EOperation you just created. After creating the validation operation, it should look like this:

image081 How To Customize the EMF Client Platform

The model now needs to be regenerated. After that, a new method is available in the class TaskImpl. This new methods needs to be adapted to implement the custom validation constraint:

  1. The method must be marked with @generated NOT so that is isn’t overriden on the next generation

  2. The “false” in the if statement needs to be replaced with the actual condition to be checked. In the example, we check whether the name of the task is empty or null.

  3. The fourth argument of the constructor of BasicDiagnostic should be replaced with a meaningful error message. In a real example, the message should be internationalized; in the code example it is just a string.

  4. If the editor of the EMF Client Platform shall be capable of marking the attribute or reference causing the violation, the corresponding feature must be passed as a second argument within the object array. The object array is the last parameter of the BasicDiagnostic.

/**
* <!-- begin-user-doc -->
 * <!-- end-user-doc -->
 * @generated NOT
 */
public boolean hasName(DiagnosticChain chain, Map<?, ?> context) {
	if (getName()==null||getName().equals("")) {
		if (chain != null) {
			chain.add
				(new BasicDiagnostic
					(Diagnostic.ERROR,
					 TaskValidator.DIAGNOSTIC_SOURCE,
					 TaskValidator.TASK__HAS_NAME,
					 "Task needs to have a name",
					 new Object [] { this, TaskPackage.eINSTANCE.getTask_Name() }));
		}
		return false;
	}
	return true;
}

After restarting the application, the EMF Client Platform will validate the custom constraint. The navigator will mark all elements violating it, e.g., all tasks without a name. The editor will mark the attribute causing the violation:

image26 How To Customize the EMF Client Platform

 

How To Replace the Editor

In running your EMF Client Platform application, which contains your custom model or the example model, you can add new model elements within the model explorer and double click on them to open the default editor (provided by EMF Forms). This editor reflectively renders all attributes and references of the opened EObject. It is possible to completely adapt the layout of this editor and extend it by new controls. All these features are provided by EMF Forms.

However, even if EMF Fomrs offers the flexibility to create any kind of form-based UI, you might want to replace the default editor with your own view that you have customized manually for your application and model. More likely, you might even execute a completely different action by double clicking on an element. The EMF Client Platform offers an extension point for this, which allows you to define a custom action to be executed with a double click. This way, you can iteratively customize your UI since the default editor is still in place for all other elements.

The extension point “org.eclipse.emf.ecp.ui.modelElementOpener” allows the registration of a “ModelElementOpener”. A ModelElementOpener also needs to know for which Objects it is applicable. For this, it needs to have a tester, either a staticTester or a dynamicTester. Each tester returns a priority defined as a positive integer value. The ModelElementOpener tester that returns the highest value will be used.

The ModelElementOpener you implement looks the same regardless of the tester you use.

Here is an example:

public class MyModelElementOpener implements ECPModelElementOpener{
 
           @Override
           public void openModelElement(Object objectToBeOpened, ECPProject ecpProject) {
                       //Open your view in here or execute any action    
           }
}

The openModelElement method should implement the behavior to open the Object. Note that you can replace the behavior with anything you want, e.g., by opening a view, another editor, a dialog or something completely different.

StaticTester

If you simply want to open a specific editor for a specific object type, you can add a staticTester. A staticTester is configured declaratively. It defines the EObject that the ModelElementOpener can handle and a priority for this case. Several ModelElementOpeners can be responsible for the same EObject, so priorities are assigned. The one with the highest priority will be applied.

<extension
        point="org.eclipse.emf.ecp.ui.modelElementOpener">
     <ModelElementOpener
           class="org.eclipse.example.myextension.ModelElementOpener"
           name="org.eclipse.example.myextension.ModelElementOpener">
        <staticTester
              modelElement="org.eclipse.example.mymodel.TheObjectIWantToOpen"
              priority="1">
        </staticTester>
     </ModelElementOpener>
  </extension>

DynamicTester

If the staticTester is not sufficient for you, you can define a dynamicTester. This allows you to programmatically check any kind of condition. A dynamicTester must provide a class implementing the org.eclipse.emf.ecp.ui.util.ECPModelElementOpenTester interface.

The provided ModelElementOpener looks like this then:

<extension
        point="org.eclipse.emf.ecp.ui.modelElementOpener">
<ModelElementOpener
           class="org.eclipse.example.myextension.ModelElementOpener2"
           name="org.eclipse.example.myextension.ModelElementOpener2">
        <dynamicTester
              tester="org.eclipse.example.myextension.ModelElementOpenTester">
        </dynamicTester>
     </ModelElementOpener>
</extension>

The provided tester class would look like this:

public class ModelElementOpenTester implements ECPModelElementOpenTester {
 
           public int isApplicable(Object object) {
                       // your check here
                       return 1;
           }
}

In the isApplicable(Eobject) method, you can implement the checks you need to identify the EObject your ModelElementOpener can open.

The returned integer value is also the priority. If the tester can’t handle the EObject at hand, it should return the predefined value NOT_APPLICABLE. It is statically accessible from the ECPModelElementOpenTester.

We also want to implement a customized opener for the “Make It Happen” example application. As there is no entity for which we want to replace the default editor, we will do something different to demonstrate the power of the model element opener.

In a previous part of this tutorial, we added a new node type to the explorer, which represents the mail addresses of a user:

image06 How To Customize the EMF Client Platform

Since these nodes are not EObjects, the editor is not capable of rendering them. Therefore, currently nothing happens on a double click on these nodes. Therefore, we want to register a model element opener that is able to process the email nodes and trigger a custom action. In our case, we don’t want to open just any view, but we do want to send an email to the email address that has been double clicked on.

To prepare, we need to add a new plugin that will contain the custom model element opener “com.eclipsesource.makeithappen.ui”. The plugin needs two additional dependencies:

  • com.eclipsesource.makeithappen.model : To process elements of the model

  • org.eclipse.emf.ecp.ui : To use the model element opener extension point

After setting up the plugin, you can add an extension to the extension point “org.eclipse.emf.ecp.ui.modelelementopener”. For our example, we will use a dynamic tester, therefore the extension point looks like this:

<extension
        point="org.eclipse.emf.ecp.ui.modelElementOpener">
     <modelElementOpener
           class="com.eclipsesource.makeithappen.ui.MailOpener">
        <dynamicTester
              tester="com.eclipsesource.makeithappen.ui.MailOpenTester">
        </dynamicTester>
     </modelElementOpener>
  </extension>

The extension links to two classes we need to implement: the MailOpenTester and the MailOpener. First, we implement the dynamic tester. It checks for the type of the object to be opened. For the mail nodes, the type is AttributeValueWrapperItemProvider. From this object we can retrieve the EFM feature. If the feature is the emails reference of the model element type User, the tester returns “1” and “NOT_APPLICABLE” in any other case. Thus our model element opener will be triggered only on a double click on the mail nodes in the tree.

public class MailOpenTester implements ECPModelElementOpenTester {
 
    public int isApplicable(Object objectToBeOpened) {
        if(objectToBeOpened instanceof AttributeValueWrapperItemProvider){
            EStructuralFeature feature = ((AttributeValueWrapperItemProvider) objectToBeOpened).getFeature();
            if(feature.equals(TaskPackage.eINSTANCE.getUser_EMails())){
                return 2;
            }
        }
        return NOT_APPLICABLE;
    }
}

As a second step, we implement the model element opener. It retrieves the value of the email node, which is the email address displayed in the tree, and opens the client’s email client to send an email:

public class MailOpener implements ECPModelElementOpener {
 
    public void openModelElement(Object object, ECPProject arg1) {
        AttributeValueWrapperItemProvider wrapper=(AttributeValueWrapperItemProvider)object;
        String value = (String) wrapper.getValue();
        Program.launch("mailto:"+value);
    }
}

Customize Editor Controls

The default editor of the EMF Client Platform is capable of rendering any EObject without any additional manual coding or code generation. The editor is provided by a framework called EMF Forms (a subcomponent of ECP).

EMF Forms is capable of reflectively rendering all attributes and references of an EObject. This pattern is very robust against model evolution. Any new or changed model element can be displayed automatically by the editor without any further adaptation. EMF Forms uses a factory pattern to retrieve the controls to be used for every EMF attribute or reference. For example, if an EObject with a string attribute is opened, the editor will retrieve a text control and use it to render the attribute. This architecture allows you to extend the editor by custom controls that are used for specific attributes or references. This allows for an incremental adaptation of the editor. All attributes will still be rendered with the default controls and only the one you have added as a custom control will be adapted. The pattern also enables you to adapt only the attributes or references that are special in your application without re-implementing a full editor. This tutorial describes how to add a custom control to the editor for a certain attribute or reference.

You can find more documentation and information about EMF Client Platform here: http://eclipse.org/emfclient

Share it

Twitter0
Google+0
LinkedIn
Facebook