How To Customize EMF Elements
11 min ReadCustomize the Look of Elements in trees
Model elements can be created and viewed in the model explorer of the EMF Forms Editors. 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
UserGroup
Open Task
Completed Task
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.
*
*
* @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:
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:
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.
*
*
* @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:
⇒ Find out more about Developer Support or contact us.
⇒ Further Documentation for EMF Forms
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 EMF Forms. 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.
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.
*
*
* @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, EMF Forms 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.
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.
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.
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 single string attribute “eMails” for the model element user. As users could have several mail addresses, change this attribute to multiple. To do so, open the Ecore model, browse to the “eMails” attribute, set the upper bound to “-1” and regenerate the model and the edit bundle. If you now 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:
We will use these nodes in a later tutorial to enable sending emails to these addresses just by double clicking on them.
⇒ Find out more about Developer Support or contact us.
⇒ Further Documentation for EMF Forms
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. EMF Forms 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. EMF Forms 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:
The editor will mark the attribute or reference causing the violation allowing the user to fix the problem:
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 EMF Forms and therefore displayed in the UI.
To add custom validation constraints, EMF Forms 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:
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:
The method must be marked with @generated NOT so that is isn’t overridden on the next generation
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.
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.
If EMF Forms 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.
/**
*
*
* @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, EMF Forms will validate the custom constraint. The editor will mark all elements violating it, e.g., all tasks without a name. The editor will mark the attribute causing the violation: