RSS

Search Engine

Wednesday, June 30, 2010

Overview

The Eclipse Jobs API provides background capabilities to Eclipse with can also be used in Eclipse RCP.

The important parts of the Job API are:

  • JobManager - Schedules the jobs

  • Job - The individual task to perform

  • IProgresMonitor - UI for Job information

Using the Jobs API in Eclipse RCP is the same as in Eclipse plug-in development therefore I just give a small example. Check the appendix for a general introduction to the Eclipse Jobs API.

Example

The following assumes that you are familiar with Eclipse RCP development .

Create a new Eclipse RCP project "de.vogella.jobs.first" with the "Hello RCP" example. Create one command "de.vogella.jobs.first.runJob1" with the following default handler.

   
package de.vogella.jobs.first.handler;


import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.ui.handlers.HandlerUtil;

public class RunJob1 extends AbstractHandler {

@Override
public Object execute(final ExecutionEvent event) throws ExecutionException {
Job job = new Job("First Job"){
@Override
protected IStatus run(IProgressMonitor monitor) {

for (int i=0; i<10;>

Add the command to the menu.

Run your application to see the Job API in action.

Progress report

If you perform long running operations you should provide the user some information about the long running job. A good way of doing this is to show a progress indicator or a progress dialog.

Create a new project "de.vogella.rcp.intro.progress" with "Hello RCP" as a template. Create the command "de.vogella.rcp.intro.progress.showDialog" with the default handler "de.vogella.rcp.intro.progress.handler.ShowDialog". Create the handler class.

   
package de.vogella.rcp.intro.progress.handler;

import java.lang.reflect.InvocationTargetException;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.ui.handlers.HandlerUtil;

public class ShowDialog extends AbstractHandler {

@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
ProgressMonitorDialog dialog = new ProgressMonitorDialog(HandlerUtil
.getActiveShell(event).getShell());
try {
dialog.run(true, true, new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor) {
monitor
.beginTask("Doing something timeconsuming here",
100);
for (int i = 0; i < 10; i++) {
if (monitor.isCanceled())
return;
monitor.subTask("I'm doing something here " + i);
sleep(1000);
monitor.worked(i);
}
monitor.done();
}
});
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}

return null;
}

private void sleep(Integer waitTime) {
try {
Thread.sleep(waitTime);
} catch (Throwable t) {
System.out.println("Wait time interrupted");
}
}

}

Add this command to the menu.

Run the application. Your command should open a dialog which show the process.

Thank you

Thank you for practicing with this tutorial.

I maintain this tutorial in my private time. If you like the information please help me by donating or by recommending this tutorial to other people.

Tuesday, June 29, 2010

Eclipse Extensions

1.1. What are Extension Points and Extensions?

Eclipse provides the concept of "extension points" and "extensions" to facilitate that functionality can be contributed to plugins by other plugins. Plugins which define extension points open themself up for other plugins. A extension points defines a contact how other plugins can contribute. The plugin which defines the extension point is also responsible for evaluating the contributions. Therefore the plugin which defines an extension point both defines the extension point and has some coding to evaluate contributions of other plugins.

A plugin which defines an "extension" contributes to the defined "extension point". The contributed can be done by any plugin. Contributions can be code but also data contributions, e.g. help context. Extensions to an extension points are defined in the plugins via the file "plugin.xml" using XML.

The information of the available extension points and the provided extensions are stored in the Eclipse "Extension Registry". The resolution of the extension points and provided extension happen as a plugin is the lifecyle status "resolved". This lifecycle status is defined by the OSGI runtime, Equinox, which is used by Eclipse.

To summarize:
  • A plugin defines an Extension Point: this plugin allow other plugins to add functionality based on the defined contact by the extension point

  • A plugin provides an Extension: This plugin added a contribution to an existing extension point.

1.2. Analysis

The concept of extension points allows to contribute functionality to plugins without changing the existing code of the plugin. This is a powerful concept as it allows to developed functionality decoupled. The extension mechanism is declarative, therefore the dependencies can be evaluated without loading any code. This allows lasy loading of plugins and therefore scales very well with lots of plugins.

1.3. Process of creating an extension point

A plugin which defines an extension points must do the following.

  • Define the extension point in the MANIFEST.MF

  • Write code to load the extensions to this extension point

  • Load / Call the extensions when needed

Extension points are defined via an XML schema file which is referred to in the the file plugin.xml. This XML schema file defines the details and the contract of the extension point. For example the following shows the relation to the schema file in the file "plugin.xml".

    





1.4. Tooling

Eclipse PDE provides powerful tooling to work with extension points. The usage of the PDE tooling will be demonstrated in the following example.

Hello World extension point

2.1. Creating the extension provider

We will create an Eclipse RCP application which allows to be extended by other plugins via an extension point. The application will be a simple console application without UI.

Create a new project "de.vogella.extensionpoint.definition" (see Creating Eclipse RCP application for details). This example will not make contribution to the UI, select this setting in the wizard. Use the "Headless Hello RCP" as a template. The option of the activator should be active.

Select the file "MANIFEST.MF" and the tab "Extension Points". Press Add.

Use the ID "de.vogella.extensionpoint.definition.greeter" and the name "Greeter". The schema name will be generated for you.

You should now in the schema editor. Switch to the "Definition" Tab.

The definition for extension is already created. Adds attributes to the extension point. Click therefore on the "New Element" button. Give the new element the name "client".

Select the "client" element and press "New Attribute". Give the new element the name class and the type java. The interface should be "de.vogella.extensionpoint.definition.IGreeter".

Press on the hyperlink "Implements" to create this interface "IGreeter".

    
package de.vogella.extensionpoint.definition;

public interface IGreeter {
void greet();
}

Select the file "MANIFEST.MF" switch to the "Runtime" tab and export the package which contains IGreeter.

Go back to your extension point definition and add a choice to the extension point. This defines how often the extension "client" can be provided by contributing plugins. We will set no restrictions (unbound).

2.2. Evaluating the registered extensions

The plugin which defines an extension point is also responisble for reading and using the provided extensions. It is up to you where in your coding you do this. The following will do it directly in Application.java.

We are using ISafeRunnable. This interface allows to protect yourself from malfunction extensions. If the extension throws an exception it will be caught and the remaining extensions will still get executed.

    
package de.vogella.extensionpoint.definition;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;

/**
* This class controls all aspects of the application's execution
*/
public class Application implements IApplication {

// This is the ID from your extension point
private static final String IGREETER_ID = "de.vogella.extensionpoint.definition.greeter";

public Object start(IApplicationContext context) throws Exception {
System.out.println("Starting");
runGreeterExtension();

return IApplication.EXIT_OK;
}

public void stop() {
//nothing to do
}

private void runGreeterExtension() {
IConfigurationElement[] config = Platform.getExtensionRegistry()
.getConfigurationElementsFor(IGREETER_ID);
try {
for (IConfigurationElement e : config) {
System.out.println("Evaluating extension");
final Object o = e.createExecutableExtension("class");
if (o instanceof IGreeter) {
ISafeRunnable runnable = new ISafeRunnable() {
@Override
public void handleException(Throwable exception) {
System.out.println("Exception in client");
}

@Override
public void run() throws Exception {
((IGreeter) o).greet();
}
};
SafeRunner.run(runnable);
}
}
} catch (CoreException ex) {
System.out.println(ex.getMessage());
}
}

}

Have a look at the generated file "schema/de.vogella.extensionpoint.definition.greeter.exsd" and the reference to this file in "plugin.xml".

Run your application. It should write "Starting" to the console. You application is finished and it ready to get extended by other plugins!

2.3. Providing an extension

Create a new headless plugin "de.vogella.extensionpoint.contribution". This plugin should not be an RCP application. Use no template to create it.

Select "MANIFEST.MF" and the tab "Dependencies". Add "de.vogella.extensionpoint.definition" and "org.eclipse.core.runtime" as a dependency. Make sure your plugin has the singleton flag set.

Switch to the "Extensions" tab and select "Add". Select your extension point and press Finish.

Add a client to your extension point.

Create the class "de.vogella.extensionpoint.contribution.GreeterGerman" with the following coding.

    
package de.vogella.extensionpoint.contribution;

import de.vogella.extensionpoint.definition.IGreeter;

public class GreeterGerman implements IGreeter {

@Override
public void greet() {
System.out.println("Moin Jungs!");
}

}

2.4. Run the example

Create a launch configuration which contains both plugins and run your application. It should print the grettings from the contributing plugin to the console.

Extension Factories

If you define your class directly in the extension point you are limited to classes with have a default constructor. To avoid this restriction you can use extension factories to create the object which is then returned. To use a factory implement the interface "IExecutableExtensionsFactory" in the class attribute of your extension point definition. The factory receives the configuration elements and can construct the required object.

Thank you

Thank you for practicing with this tutorial.

I maintain this tutorial in my private time. If you like the information please help me by donating or by recommending this tutorial to other people.

Monday, June 28, 2010

Eclipse JFace viewers

1.1. Overview

Eclipse JFace viewers allow to display a domain model in a list, tree or table without converting the domain model beforehand.

These viewers provider adaptors which are used to provide the model to the viewer (these are called content provider) and adaptors to define how the model data is displayed in the viewer (these are called label providers).

The Eclipse JFace viewer concept is very flexible as it allows to use the same content provider / label provider in different viewers without changing the underlying data structure.

JFace also allows assign label provider directly to columns instead of one label provider for the whole table.

1.2. Viewer, content and label provider

In working with JFace viewers it is important to understand the concept of viewers, label and content providers. The following provides an overview and lists some typical classes in these areas.

1.3. Viewer

The user interface is represented by the viewer, e.g. in our example the table viewer.

Viewer: Typical viewers are

  • org.eclipse.jface.viewers.ListViewer - Display a simple list

  • org.eclipse.jface.viewers.TreeViewer - Displays a tree

  • org.eclipse.jface.viewers.TableViewer - Displays a table

1.4. Content provider

Responsible for providing objects to the view. It can simply return objects as-is.

Typical content provider are

  • IStructuredContentProvider: Used for lists and tables

  • ITreeContentProvider: Used for trees, has addional methods to determine the children and the parents of elements

1.5. Label provider

Defines how objects are displayed, e.g. which part of the object is used to return the text which is displayed.

Label provider follow usually the template approach; you have an interface which describes and contact and a class which the programmer can use which delivers already reasonable implementations for the interface and the programmer can then choose to override only the relevant methods.

Typical label providers are

Table 1. Label providers

InterfaceTemplate classDescription
ILabelProviderLabelProviderUsed for lists and trees, displays per element a icons and / or a text element.
ITableLabelProviderLabelProviderUsed for tables, displays per element and column a icons and / or a text element

Overview of the example

In this tutorial we will build a Eclipse RCP application which will display a domain model in a JFace table. The domain modelrepresents the data of persons (first name, last name, gender and if he/she is married). Each person is displayed in one row in the table.

Different editors and label provider will be used for different data types. The article demonstrate how to sort based on different table columns and how to use filters to display only selected content of the viewer. We will then learn how to use StyledCellLabelProvider to influence the display of data in the table. The usage of commands and how to hide and sort table columns is demonstrated.

The application will also be able to change the first name and last name like a text editor, the gender by a drop down list and the married status by a checkbox. You can edit the objects on two ways: an inline editor in the table and within a editor.

Via the menu it will be possible to add new persons to the table and to remove persons from the table. You will also be able to show/hide columns.

The final application will look like this.

Create the model

Create a package "de.vogella.jface.tableviewer.model".

Create the following class "Person". This class contains the first name, last name, gender and married status for a person and represents the data model for this example.

   
package de.vogella.jface.tableviewer.model;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class Person {
private String firstName;
private String lastName;
private boolean married;
private String gender;
private Integer age;
private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(
this);

public Person() {
}

public Person(String firstName, String lastName, String gender,
boolean married) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
this.married = married;
}

public void addPropertyChangeListener(String propertyName,
PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
}

public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}

public String getFirstName() {
return firstName;
}

public String getGender() {
return gender;
}

public String getLastName() {
return lastName;
}

public boolean isMarried() {
return married;
}

public void setFirstName(String firstName) {
propertyChangeSupport.firePropertyChange("firstName", this.firstName,
this.firstName = firstName);
}

public void setGender(String gender) {
propertyChangeSupport.firePropertyChange("gender", this.gender,
this.gender = gender);
}

public void setLastName(String lastName) {
propertyChangeSupport.firePropertyChange("lastName", this.lastName,
this.lastName = lastName);
}

public void setMarried(boolean isMarried) {
propertyChangeSupport.firePropertyChange("married", this.married,
this.married = isMarried);
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
propertyChangeSupport.firePropertyChange("age", this.age,
this.age = age);
}

@Override
public String toString() {
return firstName + " " + lastName;
}

}

Tip

This class has also propertyChange support, which is not necessary for this example. Feel free to ignore this and implement your own POJO's with only the getter and setters.

Now create a class, called "ModelProvider". This class will be providing the collection of persons. This class is defined as a Singleton (see The Singleton Pattern for details).

   
package de.vogella.jface.tableviewer.model;

import java.util.ArrayList;
import java.util.List;

public class ModelProvider {

private static ModelProvider content;
private List persons;

private ModelProvider() {
persons = new ArrayList();
// Image here some fancy database access to read the persons and to
// put them into the model
Person person;
person = new Person("Rainer", "Zufall", "male", true);
persons.add(person);
person = new Person("Rainer", "Babbel", "male", true);
persons.add(person);
person = new Person("Marie", "Darms", "female", false);
persons.add(person);
person = new Person("Holger", "Adams", "male", true);
persons.add(person);
person = new Person("Juliane", "Adams", "female", true);
persons.add(person);

}

public static synchronized ModelProvider getInstance() {
if (content != null) {
return content;
}
content = new ModelProvider();
return content;
}

public List getPersons() {
return persons;
}

}

Content and LabelProvider

The application you have created already contains a pre-defined view. This is because we used the template "RCP application with a view". In this view a content provider and a label provider are given and assigned to a table viewer. Open now the class View.java and review and the method "createPartControl".

The following will replace these default providers with our own providers.

5.1. Build your ContentProvider

Create your own Content Provider. First create the package "de.vogella.jface.tableviewer.providers". Create the class "PersonContentProvider" which implement the Interface "org.eclipse.jface.viewers.IStructuredContentProvider". The class "PersonContentProvider" reads the data model and to return the data as an array.

Tip

It is important to note that within the content provider no assumption is made how and which data of the data model is displayed by the view. This is the responsibility of the label provider.

    
package de.vogella.jface.tableviewer.providers;

import java.util.List;

import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.Viewer;

import de.vogella.jface.tableviewer.model.Person;

public class PersonContentProvider implements IStructuredContentProvider {

@Override
public Object[] getElements(Object inputElement) {
@SuppressWarnings("unchecked")
List persons = (List) inputElement;
return persons.toArray();
}

@Override
public void dispose() {
}

@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}

}

The method getElements() will be called by the viewer. The viewer has method setInput(). This input will be provided to the contentProvider as the parameter in method getElements().

5.2. Build your LabelProvider

We will use icons for the married Status. If the folder "icons" does not exists in your project create it. Download the icons from the link provided at the end of this article and place the two icons in the folder icons.

Tip

You can also return null in the method getColumnImage() of the label provider in case you don't want to use the icons.

The class "View" already contains a Label Provider from the example. Currently the toString() method of the object Person is used in this label provider. We will replace this default label provider with our own label providers.

Create class "PersonLabelProvider" implementing the interface org.eclipse.jface.viewers.ITableLabelProvider.

    
package de.vogella.jface.tableviewer.providers;

import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.graphics.Image;
import de.vogella.jface.tableviewer.Activator;

import de.vogella.jface.tableviewer.model.Person;

public class PersonLabelProvider extends LabelProvider implements
ITableLabelProvider {
// We use icons
// We use icons
private static final Image CHECKED = Activator.getImageDescriptor(
"icons/checked.gif").createImage();
private static final Image UNCHECKED = Activator.getImageDescriptor(
"icons/unchecked.gif").createImage();

@Override
public Image getColumnImage(Object element, int columnIndex) {
// In case you don't like image just return null here
if (columnIndex == 3) {
if (((Person) element).isMarried()) {
return CHECKED;
} else {
return UNCHECKED;
}
}
return null;
}

@Override
public String getColumnText(Object element, int columnIndex) {
Person person = (Person) element;
switch (columnIndex) {
case 0:
return person.getFirstName();
case 1:
return person.getLastName();
case 2:
return person.getGender();
case 3:
return String.valueOf(person.isMarried());
default:
throw new RuntimeException("Should not happen");
}

}

}

Important thing to note:

  • getColumnText: will return the value for a certain element, e.g. in our example row and column. May not be null.

  • getColumnImage: will return the image for a certain element and a certain column. May be null

5.3. Use the providers

We have to change the class "View.java" to use our content and label providers. We will also delete the internal class ViewLabelProvider and ViewContentProvider which the tempate generated.

    
package de.vogella.jface.tableviewer;

import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.ui.part.ViewPart;

import de.vogella.jface.tableviewer.model.ModelProvider;
import de.vogella.jface.tableviewer.providers.PersonContentProvider;
import de.vogella.jface.tableviewer.providers.PersonLabelProvider;

public class View extends ViewPart {
public static final String ID = "de.vogella.jface.tableviewer.view";

private TableViewer viewer;

public void createPartControl(Composite parent) {
createViewer(parent);
// Get the content for the viewer, setInput will call getElements in the
// contentProvider
viewer.setInput(ModelProvider.getInstance().getPersons());
}

private void createViewer(Composite parent) {
viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
| SWT.V_SCROLL | SWT.FULL_SELECTION);
createColumns(viewer);
viewer.setContentProvider(new PersonContentProvider());
viewer.setLabelProvider(new PersonLabelProvider());
}

// This will create the columns for the table
private void createColumns(TableViewer viewer) {

String[] titles = { "First name", "Last name", "Gender", "Married" };
int[] bounds = { 100, 100, 100, 100 };

for (int i = 0; i < titles.length; i++) {
TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
column.getColumn().setText(titles[i]);
column.getColumn().setWidth(bounds[i]);
column.getColumn().setResizable(true);
column.getColumn().setMoveable(true);
}
Table table = viewer.getTable();
table.setHeaderVisible(true);
table.setLinesVisible(true);
}

/**
* Passing the focus request to the viewer's control.
*/
public void setFocus() {
viewer.getControl().setFocus();
}
}

The method createColumns create the table columns, headers, sets the size of the columns, makes the columns resizable and show the lines in the table.

5.4. Run

Run the example. The application should look like this.

Cell Editors

A cell editor defines how the user can edit certain cells of a table.

We will define the editors for our table. The "First name" and "Last name" column will be editable by a textfield. The "Gende"’ column will be editable by a drop down list and the "Married" column will be editable by a checkbox.

Create a new class with the name "PersonEditingSupport" in package de.vogella.jface.tableviewer.providers.

   
package de.vogella.jface.tableviewer.providers;

import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckboxCellEditor;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.ComboBoxCellEditor;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;

import de.vogella.jface.tableviewer.model.Person;

public class PersonEditingSupport extends EditingSupport {
private CellEditor editor;
private int column;

public PersonEditingSupport(ColumnViewer viewer, int column) {
super(viewer);

String[] gender = new String[2];
gender[0] = "male";
gender[1] = "female";

// Create the correct editor based on the column index
switch (column) {
case 2:
editor = new ComboBoxCellEditor(((TableViewer) viewer).getTable(),
gender);
break;
case 3:
editor = new CheckboxCellEditor(null, SWT.CHECK | SWT.READ_ONLY);
break;
default:
editor = new TextCellEditor(((TableViewer) viewer).getTable());
}
this.column = column;
}

@Override
protected boolean canEdit(Object element) {
return true;
}

@Override
protected CellEditor getCellEditor(Object element) {
return editor;
}

@Override
protected Object getValue(Object element) {
Person person = (Person) element;

switch (this.column) {
case 0:
return person.getFirstName();
case 1:
return person.getLastName();
case 2:
if (person.getGender().equals("male")) {
return 0;
}
return 1;
case 3:
return person.isMarried();
default:
break;
}
return null;
}

@Override
protected void setValue(Object element, Object value) {
Person pers = (Person) element;

switch (this.column) {
case 0:
pers.setFirstName(String.valueOf(value));
break;
case 1:
pers.setLastName(String.valueOf(value));
break;
case 2:
if (((Integer) value) == 0) {
pers.setGender("male");
} else {
pers.setGender("female");
}
break;
case 3:
pers.setMarried((Boolean) value);
break;
default:
break;
}

getViewer().update(element, null);
}

}

Explanation of the the main methods of "PersonEditingSupport":

  • ‘getCellEditor’ : In this method you return the celleditor you want to use (e.g. Texteditor). I work with a ‘switch-case-command’ to split my columns and give every column its own celleditor.

  • ‘setValue’: Receives the new value the user gives. You have to set the new value to the object which is given, too.

  • ‘getValue’: Receives the object which was changed and returns the value for the table. You have to return the new value of the object.

Now assign the editors to your columns. We do this in View.java in the method createColumns().

   
// This will create the columns for the table
private void createColumns(TableViewer viewer) {

String[] titles = { "First name", "Last name", "Gender", "Married" };
int[] bounds = { 100, 100, 100, 100 };

for (int i = 0; i < titles.length; i++) {
TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
column.getColumn().setText(titles[i]);
column.getColumn().setWidth(bounds[i]);
column.getColumn().setResizable(true);
column.getColumn().setMoveable(true);
// enable editing support
column.setEditingSupport(new PersonEditingSupport(viewer, i));
}
Table table = viewer.getTable();
table.setHeaderVisible(true);
table.setLinesVisible(true);
}

Run now your application. You should now be able to modify the content of the JFace table.

Sort Columns

This chapter explains how to to sort the columns of the table ascending/descending.

Create a new Class "de.vogella.jface.tableviewer.sorter.TableSorter.java"

   
package de.vogella.jface.tableviewer.sorter;

import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;

import de.vogella.jface.tableviewer.model.Person;

public class TableSorter extends ViewerSorter {
private int propertyIndex;
// private static final int ASCENDING = 0;
private static final int DESCENDING = 1;

private int direction = DESCENDING;

public TableSorter() {
this.propertyIndex = 0;
direction = DESCENDING;
}

public void setColumn(int column) {
if (column == this.propertyIndex) {
// Same column as last sort; toggle the direction
direction = 1 - direction;
} else {
// New column; do an ascending sort
this.propertyIndex = column;
direction = DESCENDING;
}
}

@Override
public int compare(Viewer viewer, Object e1, Object e2) {
Person p1 = (Person) e1;
Person p2 = (Person) e2;
int rc = 0;
switch (propertyIndex) {
case 0:
rc = p1.getFirstName().compareTo(p2.getFirstName());
break;
case 1:
rc = p1.getLastName().compareTo(p2.getLastName());
break;
case 2:
rc = p1.getGender().compareTo(p2.getGender());
break;
case 3:
if (p1.isMarried() == p2.isMarried()) {
rc = 0;
} else
rc = (p1.isMarried() ? 1 : -1);
break;
default:
rc = 0;
}
// If descending order, flip the direction
if (direction == DESCENDING) {
rc = -rc;
}
return rc;
}
}

Add a listener to the view for setting the information which column should be sorted. Change View.java to the following-

   
package de.vogella.jface.tableviewer;

import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.part.ViewPart;

import de.vogella.jface.tableviewer.model.ModelProvider;
import de.vogella.jface.tableviewer.providers.PersonContentProvider;
import de.vogella.jface.tableviewer.providers.PersonEditingSupport;
import de.vogella.jface.tableviewer.providers.PersonLabelProvider;
import de.vogella.jface.tableviewer.sorter.TableSorter;

public class View extends ViewPart {
public static final String ID = "de.vogella.jface.tableviewer.view";

private TableViewer viewer;

private TableSorter tableSorter;

public void createPartControl(Composite parent) {
createViewer(parent);
// Get the content for the viewer, setInput will call getElements in the
// contentProvider
viewer.setInput(ModelProvider.getInstance().getPersons());
// Make the selection available
getSite().setSelectionProvider(viewer);
// Set the sorter for the table
tableSorter = new TableSorter();
viewer.setSorter(tableSorter);

}

private void createViewer(Composite parent) {
viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
| SWT.V_SCROLL | SWT.FULL_SELECTION);
createColumns(viewer);
viewer.setContentProvider(new PersonContentProvider());
viewer.setLabelProvider(new PersonLabelProvider());
}

public TableViewer getViewer() {
return viewer;
}

// This will create the columns for the table
private void createColumns(final TableViewer viewer) {
Table table = viewer.getTable();
String[] titles = { "First name", "Last name", "Gender", "Married" };
int[] bounds = { 100, 100, 100, 100 };

for (int i = 0; i < titles.length; i++) {
final int index = i;
final TableViewerColumn viewerColumn = new TableViewerColumn(
viewer, SWT.NONE);
final TableColumn column = viewerColumn.getColumn();
column.setText(titles[i]);
column.setWidth(bounds[i]);
column.setResizable(true);
column.setMoveable(true);
// Setting the right sorter
column.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
tableSorter.setColumn(index);
int dir = viewer.getTable().getSortDirection();
if (viewer.getTable().getSortColumn() == column) {
dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
} else {

dir = SWT.DOWN;
}
viewer.getTable().setSortDirection(dir);
viewer.getTable().setSortColumn(column);
viewer.refresh();
}
});
viewerColumn.setEditingSupport(new PersonEditingSupport(viewer, i));
}
table.setHeaderVisible(true);
table.setLinesVisible(true);
}

/**
* Passing the focus request to the viewer's control.
*/
public void setFocus() {
viewer.getControl().setFocus();
}
}

Run the example, click on a column, there will appear a sort-direction signifier and your items are ordered ascending or descending.

Filter

Now we would like to add a filter to the table. The user should have a text field in which we can enter a first- or lastname. Only the names which applies to this filter should get displayed.

Adding a filter to a view is simple, you use method addFilter() on the viewer, which expects a ViewFilter as argument. Each ViewFilter is checked the input on the viewer is changed of whenever the viewer.refresh();

Create a new Class "de.vogella.jface.tableviewer.filter.PersonFilter.java"

   
package de.vogella.jface.tableviewer.filter;

import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;

import de.vogella.jface.tableviewer.model.Person;

public class PersonFilter extends ViewerFilter {

private String searchString;

public void setSearchText(String s) {
// Search must be a substring of the existing value
this.searchString = ".*" + s + ".*";
}

@Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
if (searchString == null || searchString.length() == 0) {
return true;
}
Person p = (Person) element;
if (p.getFirstName().matches(searchString)) {
return true;
}
if (p.getLastName().matches(searchString)) {
return true;
}

return false;
}
}

We will create a new text field in which the user can search. This text field will have a keyListener which updates the filter and the viewer. Change the View.java to the following .

   
package de.vogella.jface.tableviewer;

import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.part.ViewPart;

import de.vogella.jface.tableviewer.filter.PersonFilter;
import de.vogella.jface.tableviewer.model.ModelProvider;
import de.vogella.jface.tableviewer.providers.PersonContentProvider;
import de.vogella.jface.tableviewer.providers.PersonEditingSupport;
import de.vogella.jface.tableviewer.providers.PersonLabelProvider;
import de.vogella.jface.tableviewer.sorter.TableSorter;

public class View extends ViewPart {
public static final String ID = "de.vogella.jface.tableviewer.view";

private TableViewer viewer;

private TableSorter tableSorter;

private PersonFilter filter;

public void createPartControl(Composite parent) {
GridLayout layout = new GridLayout(2, false);
parent.setLayout(layout);
Label searchLabel = new Label(parent, SWT.NONE);
searchLabel.setText("Search: ");
final Text searchText = new Text(parent, SWT.BORDER | SWT.SEARCH);
searchText.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL
| GridData.HORIZONTAL_ALIGN_FILL));
searchText.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent ke) {
filter.setSearchText(searchText.getText());
viewer.refresh();
}

});
viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
| SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
createColumns(viewer);
viewer.setContentProvider(new PersonContentProvider());
viewer.setLabelProvider(new PersonLabelProvider());
// Get the content for the viewer, setInput will call getElements in the
// contentProvider
viewer.setInput(ModelProvider.getInstance().getPersons());
// Make the selection available
getSite().setSelectionProvider(viewer);
// Set the sorter for the table
tableSorter = new TableSorter();
viewer.setSorter(tableSorter);
filter = new PersonFilter();
viewer.addFilter(filter);

// Layout the viewer
GridData gridData = new GridData();
gridData.verticalAlignment = GridData.FILL;
gridData.horizontalSpan = 2;
gridData.grabExcessHorizontalSpace = true;
gridData.grabExcessVerticalSpace = true;
gridData.horizontalAlignment = GridData.FILL;
viewer.getControl().setLayoutData(gridData);
}

public TableViewer getViewer() {
return viewer;
}

// This will create the columns for the table
private void createColumns(final TableViewer viewer) {
Table table = viewer.getTable();
String[] titles = { "First name", "Last name", "Gender", "Married" };
int[] bounds = { 100, 100, 100, 100 };

for (int i = 0; i < titles.length; i++) {
final int index = i;
final TableViewerColumn viewerColumn = new TableViewerColumn(
viewer, SWT.NONE);
final TableColumn column = viewerColumn.getColumn();
column.setText(titles[i]);
column.setWidth(bounds[i]);
column.setResizable(true);
column.setMoveable(true);
// Setting the right sorter
column.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
tableSorter.setColumn(index);
int dir = viewer.getTable().getSortDirection();
if (viewer.getTable().getSortColumn() == column) {
dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
} else {

dir = SWT.DOWN;
}
viewer.getTable().setSortDirection(dir);
viewer.getTable().setSortColumn(column);
viewer.refresh();
}
});
viewerColumn.setEditingSupport(new PersonEditingSupport(viewer, i));
}
table.setHeaderVisible(true);
table.setLinesVisible(true);
}

/**
* Passing the focus request to the viewer's control.
*/
public void setFocus() {
viewer.getControl().setFocus();
}
}

Run the example, filtering should work.

Highlighting elements - StyledCellLabelProvider

It is also possible to use a StyleCellLabelProvider to highlight the search result. StyleCellLabelProvider can do much more but we will look at the simple case.

First create the following helper class which will determine which occurrence of the search string is in the column.

   
package de.vogella.jface.tableviewer.util;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.widgets.Display;

public class SearchUtil {
public static int[] getSearchTermOccurrences(String searchTerm,
String content) {
List styleRange;
List ranges;
Display disp = Display.getCurrent();
StyleRange myStyleRange = new StyleRange(0, 0, null, disp
.getSystemColor(SWT.COLOR_YELLOW));

styleRange = new ArrayList(); // reset the StyleRange-Array
// for each new field
ranges = new ArrayList(); // reset the ranges-array
// empty search term ==> return an empty StyleRange array
if (searchTerm.equals("")) {
return new int[] {};
}

// determine all occurrences of the searchText and write the beginning
// and length of each occurrence into an array
for (int i = 0; i < content.length(); i++) {
if (i + searchTerm.length() <= content.length()
&& content.substring(i, i + searchTerm.length())
.equalsIgnoreCase(searchTerm)) {
// ranges format: n->start of the range, n+1->length of the
// range
ranges.add(i);
ranges.add(searchTerm.length());
}
}
// convert the list into an int[] and make sure that overlapping
// search term occurrences are are merged
int[] intRanges = new int[ranges.size()];
int arrayIndexCounter = 0;
for (int listIndexCounter = 0; listIndexCounter < ranges.size(); listIndexCounter++) {
if (listIndexCounter % 2 == 0) {
if (searchTerm.length() > 1
&& listIndexCounter != 0
&& ranges.get(listIndexCounter - 2)
+ ranges.get(listIndexCounter - 1) >= ranges
.get(listIndexCounter)) {
intRanges[arrayIndexCounter - 1] = 0
- ranges.get(listIndexCounter - 2)
+ ranges.get(listIndexCounter)
+ ranges.get(++listIndexCounter);
} else {
intRanges[arrayIndexCounter++] = ranges
.get(listIndexCounter);
}
} else {
intRanges[arrayIndexCounter++] = ranges.get(listIndexCounter);
styleRange.add(myStyleRange);
}
}
// if there have been any overlappings we need to reduce the size of
// the array to avoid conflicts in the setStyleRanges method
int[] intRangesCorrectSize = new int[arrayIndexCounter];
System.arraycopy(intRanges, 0, intRangesCorrectSize, 0,
arrayIndexCounter);

return intRangesCorrectSize;
}
}

Change your PersonLabelProvider to the following.

   
package de.vogella.jface.tableviewer.providers;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import de.vogella.jface.tableviewer.Activator;

import de.vogella.jface.tableviewer.model.Person;
import de.vogella.jface.tableviewer.util.SearchUtil;

public class PersonLabelProvider extends StyledCellLabelProvider {
// We use icons
private static final Image CHECKED = Activator.getImageDescriptor(
"icons/checked.gif").createImage();
private static final Image UNCHECKED = Activator.getImageDescriptor(
"icons/unchecked.gif").createImage();
private String searchText;
private Color systemColor;

public PersonLabelProvider() {
systemColor = Display.getCurrent().getSystemColor(SWT.COLOR_YELLOW);
}

public void setSearchText(String searchText) {
this.searchText = searchText;

}

@Override
public void update(ViewerCell cell) {
Person element = (Person) cell.getElement();
int index = cell.getColumnIndex();
String columnText = getColumnText(element, index);
cell.setText(columnText);
cell.setImage(getColumnImage(element, index));
if (searchText != null && searchText.length() > 0) {
int intRangesCorrectSize[] = SearchUtil.getSearchTermOccurrences(
searchText, columnText);
List styleRange = new ArrayList();
for (int i = 0; i < intRangesCorrectSize.length / 2; i++) {
StyleRange myStyleRange = new StyleRange(0, 0, null,
systemColor);
myStyleRange.start = intRangesCorrectSize[i];
myStyleRange.length = intRangesCorrectSize[++i];
styleRange.add(myStyleRange);
}
cell.setStyleRanges(styleRange.toArray(new StyleRange[styleRange
.size()]));
} else {
cell.setStyleRanges(null);
}

super.update(cell);

}

private String getColumnText(Object element, int columnIndex) {
Person person = (Person) element;
switch (columnIndex) {
case 0:
return person.getFirstName();
case 1:
return person.getLastName();
case 2:
return person.getGender();
case 3:
return String.valueOf(person.isMarried());
default:
throw new RuntimeException("Should not happen");
}
}

private Image getColumnImage(Object element, int columnIndex) {
// In case you don't like image just return null here
if (columnIndex == 3) {
if (((Person) element).isMarried()) {
return CHECKED;
}
return UNCHECKED;
}
return null;
}

}

Make the following changes to the View. .

   
package de.vogella.jface.tableviewer;

import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.part.ViewPart;

import de.vogella.jface.tableviewer.filter.PersonFilter;
import de.vogella.jface.tableviewer.model.ModelProvider;
import de.vogella.jface.tableviewer.providers.PersonContentProvider;
import de.vogella.jface.tableviewer.providers.PersonEditingSupport;
import de.vogella.jface.tableviewer.providers.PersonLabelProvider;
import de.vogella.jface.tableviewer.sorter.TableSorter;

public class View extends ViewPart {
public static final String ID = "de.vogella.jface.tableviewer.view";

private TableViewer viewer;

private TableSorter tableSorter;

private PersonFilter filter;

private PersonLabelProvider labelProvider;

public void createPartControl(Composite parent) {
GridLayout layout = new GridLayout(2, false);
parent.setLayout(layout);
Label searchLabel = new Label(parent, SWT.NONE);
searchLabel.setText("Search: ");
final Text searchText = new Text(parent, SWT.BORDER | SWT.SEARCH);
searchText.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL
| GridData.HORIZONTAL_ALIGN_FILL));
searchText.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent ke) {
filter.setSearchText(searchText.getText());
labelProvider.setSearchText(searchText.getText());
viewer.refresh();
}
});

createViewer(parent);


// Get the content for the viewer, setInput will call getElements in the
// contentProvider
viewer.setInput(ModelProvider.getInstance().getPersons());
// Make the selection available
getSite().setSelectionProvider(viewer);
// Set the sorter for the table
tableSorter = new TableSorter();
viewer.setSorter(tableSorter);
filter = new PersonFilter();
viewer.addFilter(filter);

// Layout the viewer
GridData gridData = new GridData();
gridData.verticalAlignment = GridData.FILL;
gridData.horizontalSpan = 2;
gridData.grabExcessHorizontalSpace = true;
gridData.grabExcessVerticalSpace = true;
gridData.horizontalAlignment = GridData.FILL;
viewer.getControl().setLayoutData(gridData);
}
private void createViewer(Composite parent) {
viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
| SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
createColumns(viewer);
viewer.setContentProvider(new PersonContentProvider());
labelProvider = new PersonLabelProvider();
viewer.setLabelProvider(labelProvider);
}

public TableViewer getViewer() {
return viewer;
}

// This will create the columns for the table
private void createColumns(final TableViewer viewer) {
Table table = viewer.getTable();
String[] titles = { "First name", "Last name", "Gender", "Married" };
int[] bounds = { 100, 100, 100, 100 };

for (int i = 0; i < titles.length; i++) {
final int index = i;
final TableViewerColumn viewerColumn = new TableViewerColumn(
viewer, SWT.NONE);
final TableColumn column = viewerColumn.getColumn();
column.setText(titles[i]);
column.setWidth(bounds[i]);
column.setResizable(true);
column.setMoveable(true);
// Setting the right sorter
column.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
tableSorter.setColumn(index);
int dir = viewer.getTable().getSortDirection();
if (viewer.getTable().getSortColumn() == column) {
dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
} else {

dir = SWT.DOWN;
}
viewer.getTable().setSortDirection(dir);
viewer.getTable().setSortColumn(column);
viewer.refresh();
}
});
viewerColumn.setEditingSupport(new PersonEditingSupport(viewer, i));
}
table.setHeaderVisible(true);
table.setLinesVisible(true);
}

/**
* Passing the focus request to the viewer's control.
*/
public void setFocus() {
viewer.getControl().setFocus();
}
}

Run the example, if you search now the selected content should get highlighted should work.

Show/Hide Columns

This chapter shows how to implement a menu on the table which allows to show and hide columns of the table. Please note that Eclipse 3.5 introduced the possibility to add menus to columns headers.

Tip

This example is based on SWT Snippet 311

Change your coding in View.java to the following.

   
package de.vogella.jface.tableviewer;

import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.part.ViewPart;

import de.vogella.jface.tableviewer.filter.PersonFilter;
import de.vogella.jface.tableviewer.model.ModelProvider;
import de.vogella.jface.tableviewer.providers.PersonContentProvider;
import de.vogella.jface.tableviewer.providers.PersonEditingSupport;
import de.vogella.jface.tableviewer.providers.PersonLabelProvider;
import de.vogella.jface.tableviewer.sorter.TableSorter;

public class View extends ViewPart {
public static final String ID = "de.vogella.jface.tableviewer.view";

private TableViewer viewer;

private TableSorter tableSorter;

private PersonFilter filter;

private PersonLabelProvider labelProvider;

public void createPartControl(Composite parent) {
GridLayout layout = new GridLayout(2, false);
parent.setLayout(layout);
Label searchLabel = new Label(parent, SWT.NONE);
searchLabel.setText("Search: ");
final Text searchText = new Text(parent, SWT.BORDER | SWT.SEARCH);
searchText.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL
| GridData.HORIZONTAL_ALIGN_FILL));
searchText.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent ke) {
filter.setSearchText(searchText.getText());
labelProvider.setSearchText(searchText.getText());
viewer.refresh();
}
});
createViewer(parent);
}

private void createViewer(Composite parent) {
viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
| SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);

createColumns(parent, viewer);
viewer.setContentProvider(new PersonContentProvider());
labelProvider = new PersonLabelProvider();
viewer.setLabelProvider(labelProvider);
// Get the content for the viewer, setInput will call getElements in the
// contentProvider
viewer.setInput(ModelProvider.getInstance().getPersons());
// Make the selection available
getSite().setSelectionProvider(viewer);
// Set the sorter for the table
tableSorter = new TableSorter();
viewer.setSorter(tableSorter);
filter = new PersonFilter();
viewer.addFilter(filter);

// Layout the viewer
GridData gridData = new GridData();
gridData.verticalAlignment = GridData.FILL;
gridData.horizontalSpan = 2;
gridData.grabExcessHorizontalSpace = true;
gridData.grabExcessVerticalSpace = true;
gridData.horizontalAlignment = GridData.FILL;
viewer.getControl().setLayoutData(gridData);
}

public TableViewer getViewer() {
return viewer;
}

// This will create the columns for the table
private void createColumns(final Composite parent, final TableViewer viewer) {
final Menu headerMenu = new Menu(parent);
String[] titles = { "First name", "Last name", "Gender", "Married" };
int[] bounds = { 100, 100, 100, 100 };

for (int i = 0; i < titles.length; i++) {
final int index = i;
final TableViewerColumn viewerColumn = new TableViewerColumn(
viewer, SWT.NONE);
final TableColumn column = viewerColumn.getColumn();
column.setText(titles[i]);
column.setWidth(bounds[i]);
column.setResizable(true);
createMenuItem(headerMenu, column); // Create the menu item for this
// column
column.setMoveable(true);

// Setting the right sorter
column.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
tableSorter.setColumn(index);
int dir = viewer.getTable().getSortDirection();
if (viewer.getTable().getSortColumn() == column) {
dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
} else {

dir = SWT.DOWN;
}
viewer.getTable().setSortDirection(dir);
viewer.getTable().setSortColumn(column);
viewer.refresh();
}
});
viewerColumn.setEditingSupport(new PersonEditingSupport(viewer, i));
}
final Table table = viewer.getTable();
table.setHeaderVisible(true);
table.setLinesVisible(true);

table.addListener(SWT.MenuDetect, new Listener() {
public void handleEvent(Event event) {
table.setMenu(headerMenu);
}
});

}

private void createMenuItem(Menu parent, final TableColumn column) {
final MenuItem itemName = new MenuItem(parent, SWT.CHECK);
itemName.setText(column.getText());
itemName.setSelection(column.getResizable());
itemName.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
if (itemName.getSelection()) {
column.setWidth(150);
column.setResizable(true);
} else {
column.setWidth(0);
column.setResizable(false);
}
}
});

}

/**
* Passing the focus request to the viewer's control.
*/
public void setFocus() {
viewer.getControl().setFocus();
}
}

If you run your application you are able to hide and show columns using the right mouse click on your table.

Commands

The following will demonstrate the usage of commands. Feel free to skip with chapter.

11.1. Print the model content

This chapter add the functionality to print the content of the domain model to the console. This way it is possible to verify that the changes on the JFace table are updating the model.

Create the command "de.vogella.jface.tableviewer.commands.Print" with the default handler "de.vogella.jface.tableviewer.commands.Print" (see Defining commands for details).

Implement the following coding:

    
package de.vogella.jface.tableviewer.commands;

import java.util.List;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;

import de.vogella.jface.tableviewer.model.ModelProvider;
import de.vogella.jface.tableviewer.model.Person;

public class Print extends AbstractHandler {
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
List personList = ModelProvider.getInstance().getPersons();
for (Person p : personList) {
System.out.println(p);
}
return null;
}
}

Add your command to the menu.(see Defining commands for details).

After finishing this you will be able to print the current state of your domain model to the console via the menu. Validate that the changes you are doing in the UI are reflected the model.

11.2. Add and delete a person to and from the model

This chapter shows how to add and delete Persons to and from the table.

You have to make the selection of the viewer available and will also provide an access method to the viewer of the view.

    
public void createPartControl(Composite parent) {
viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
| SWT.V_SCROLL | SWT.FULL_SELECTION);
createColumns(viewer);
viewer.setContentProvider(new PersonContentProvider());
viewer.setLabelProvider(new PersonLabelProvider());
// Get the content for the viewer, setInput will call getElements in the
// contentProvider
viewer.setInput(ModelProvider.getInstance().getPersons());
// Make the selection available
getSite().setSelectionProvider(viewer);

}

public TableViewer getViewer() {
return viewer;
}

Create the command "de.vogella.jface.tableviewer.commands.AddPerson" with the default handler "de.vogella.jface.tableviewer.commands.AddPerson". Add the command to your menu. Again see Defining commands for details.

Create a dialog to maintain the data for the additional person. Create package "de.vogella.jface.tableviewer.dialogs" and the following class "AddPersonDialog".

    
package de.vogella.jface.tableviewer.dialogs;

import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import de.vogella.jface.tableviewer.model.Person;

public class AddPersonDialog extends TitleAreaDialog {

private Text text1;
private Text text2;
private Person person;
private Button button1;
private Combo combo1;

public Person getPerson() {
return person;
}

public AddPersonDialog(Shell parentShell) {
super(parentShell);
}

@Override
protected Control createContents(Composite parent) {
Control contents = super.createContents(parent);
setTitle("Add a new Person");
setMessage("Please enter the data of the new person",
IMessageProvider.INFORMATION);
return contents;
}

@Override
protected Control createDialogArea(Composite parent) {
GridLayout layout = new GridLayout();
layout.numColumns = 2;
parent.setLayout(layout);
Label label1 = new Label(parent, SWT.NONE);
label1.setText("First Name");
text1 = new Text(parent, SWT.BORDER);
Label label2 = new Label(parent, SWT.NONE);
label2.setText("Last Name");
text2 = new Text(parent, SWT.BORDER);
Label label3 = new Label(parent, SWT.NONE);
label3.setText("Gender");
GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_END);
gd.horizontalSpan = 2;
combo1 = new Combo(parent, SWT.READ_ONLY);
combo1.add("male");
combo1.add("female");
button1 = new Button(parent, SWT.CHECK);
button1.setText("Is married?");
button1.setLayoutData(gd);
return parent;

}

@Override
protected void createButtonsForButtonBar(Composite parent) {
((GridLayout) parent.getLayout()).numColumns++;

Button button = new Button(parent, SWT.PUSH);
button.setText("OK");
button.setFont(JFaceResources.getDialogFont());
button.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
if (text1.getText().length() != 0
&& text2.getText().length() != 0
&& combo1.getItem(combo1.getSelectionIndex()).length() != 0) {
person = new Person(text1.getText(), text2.getText(),
combo1.getItem(combo1.getSelectionIndex()), button1
.getSelection());
close();

} else {
setErrorMessage("Please enter all data");
}
}
});
}
}

Implement the following code for the class "de.vogella.jface.tableviewer.commands.AddPerson":

    
package de.vogella.jface.tableviewer.commands;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.handlers.HandlerUtil;

import de.vogella.jface.tableviewer.View;
import de.vogella.jface.tableviewer.dialogs.AddPersonDialog;
import de.vogella.jface.tableviewer.model.ModelProvider;

public class AddPerson extends AbstractHandler {

@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindow(event);
ModelProvider persons = ModelProvider.getInstance();
AddPersonDialog dialog = new AddPersonDialog(window.getShell());
dialog.open();
if (dialog.getPerson() != null) {
persons.getPersons().add(dialog.getPerson());
// Updating the display in the view
IWorkbenchPage page = window.getActivePage();
View view = (View) page.findView(View.ID);
view.getViewer().refresh();
}
return null;
}
}

When you finished this successful you are able to add new persons in your application.

We will implement that a person can be deleted from the list after someone selected a person in the table, e.g. via a double click.

Create the command "de.vogella.jface.tableviewer.commands.DeletePerson" with the default handler "de.vogella.jface.tableviewer.commands.DeletePerson". Add the command to your menu.

Implement class "de.vogella.jface.tableviewer.commands.DeletePerson".

    
package de.vogella.jface.tableviewer.commands;

import java.util.Iterator;
import java.util.List;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.handlers.HandlerUtil;

import de.vogella.jface.tableviewer.View;
import de.vogella.jface.tableviewer.model.ModelProvider;
import de.vogella.jface.tableviewer.model.Person;

public class DeletePerson extends AbstractHandler {
@SuppressWarnings("unchecked")
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindow(event);
IWorkbenchPage page = window.getActivePage();
View view = (View) page.findView(View.ID);
ISelection selection = view.getSite().getSelectionProvider()
.getSelection();

if (selection != null && selection instanceof IStructuredSelection) {
List persons = ModelProvider.getInstance().getPersons();
IStructuredSelection sel = (IStructuredSelection) selection;

for (Iterator iterator = sel.iterator(); iterator.hasNext();) {
Person person = iterator.next();
persons.remove(person);
}
view.getViewer().refresh();
}
return null;
}
}

Please try to delete entries of the table by selection entries and executing "Delete Action".

11.3. Copy data to the system clipboard

This chapter shows how to copy the table data to the system clipboard via a command.

Create the command "de.vogella.jface.tableviewer.commands.CopyPersonClipboard" with the default handler "de.vogella.jface.tableviewer.commands.CopyPersonClipboard". Add the command to the menu.

    
package de.vogella.jface.tableviewer.commands;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;

import de.vogella.jface.tableviewer.View;
import de.vogella.jface.tableviewer.model.Person;

public class CopyPersonClipboard extends AbstractHandler {

@Override
public Object execute(ExecutionEvent event) throws ExecutionException {

IWorkbenchWindow window = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow();
IWorkbenchPage page = window.getActivePage();
IViewPart view = page.findView(View.ID);
Clipboard cb = new Clipboard(Display.getDefault());
ISelection selection = view.getSite().getSelectionProvider()
.getSelection();
List personList = new ArrayList();
if (selection != null && selection instanceof IStructuredSelection) {
IStructuredSelection sel = (IStructuredSelection) selection;
for (Iterator iterator = sel.iterator(); iterator.hasNext();) {
Person person = iterator.next();
personList.add(person);
}
}
StringBuilder sb = new StringBuilder();
for (Person person : personList) {
sb.append(personToString(person));
}
TextTransfer textTransfer = TextTransfer.getInstance();
cb.setContents(new Object[] { sb.toString() },
new Transfer[] { textTransfer });

return null;
}

private String personToString(Person person) {
return person.getFirstName() + "\t" + person.getLastName() + "\t"
+ person.getGender() + "\t" + person.isMarried()
+ System.getProperty("line.separator");
}

}

Run your application, select a few persons, run your command and paste the result in a text editor, e.g. notepad.

Thank you

Thank you for practicing with this tutorial.

I maintain this tutorial in my private time. If you like the information please help me by donating or by recommending this tutorial to other people.

Wednesday, June 23, 2010

Eclipse Data Binding - Overview

Databinding provides a flexible way of connecting a domain model with the User Interface.

The Eclipse Data Binding connects your domain model and the user interface. Changes in both will be automatically synchronized. You can use default or customized data validators to check the data and can use converters to convert the data between the UI and the domain model.

We will build an application which displays the data of a person and lets you change the person in the UI. Can you also trigger a direct change in the model via a button. Changes in the UI and the model will be synchronized with each other.

Domain Model

2.1. Overview

We are going to re-use the same data model for several examples. Therefore we will create the data model in a separate plugin and re-use it.

2.2. Create Plugin

Create a plugin project "de.vogella.databinding.person.model". Do not create an activator, do not select "This plug-in will make contributions to the UI" and select "No" for the question "Do you want to create a rich client application".

Press "Finish".

To use the databinding between the UI and the domain model in both directions the domain model needs to implement PropertyChangeSupport so that the Eclipse databinding can listen to changes in the model.

Create the package "de.vogella.databinding.person.model" and the following classes.

    
package de.vogella.databinding.person.model;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class Person implements PropertyChangeListener {
private String firstName;
private String lastName;
private boolean married;
private String gender;
private Integer age;
private Address address;
private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(
this);

public Person() {
}

public void addPropertyChangeListener(String propertyName,
PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
}

public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}

public String getFirstName() {
return firstName;
}

public String getGender() {
return gender;
}

public String getLastName() {
return lastName;
}

public boolean isMarried() {
return married;
}

public void setFirstName(String firstName) {
propertyChangeSupport.firePropertyChange("firstName", this.firstName,
this.firstName = firstName);
}

public void setGender(String gender) {
propertyChangeSupport.firePropertyChange("gender", this.gender,
this.gender = gender);
}

public void setLastName(String lastName) {
propertyChangeSupport.firePropertyChange("lastName", this.lastName,
this.lastName = lastName);
}

public void setMarried(boolean isMarried) {
propertyChangeSupport.firePropertyChange("married", this.married,
this.married = isMarried);
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
propertyChangeSupport.firePropertyChange("age", this.age,
this.age = age);
}

public Address getAddress() {
return address;
}

public void setAddress(Address address) {
address.addPropertyChangeListener("country", this);
propertyChangeSupport.firePropertyChange("address", this.address,
this.address = address);
}

@Override
public String toString() {
return firstName + " " + lastName;
}

@Override
public void propertyChange(PropertyChangeEvent event) {
propertyChangeSupport.firePropertyChange("address", null, address);
}

}

    
package de.vogella.databinding.person.model;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class Address {

private String street;
private String number;
private String postalCode;
private String city;
private String country;
private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(
this);

public void addPropertyChangeListener(String propertyName,
PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
}

public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}

public Address() {
}

public Address(String postalCode, String city, String country) {
this.postalCode = postalCode;
this.city = city;
this.country = country;
}

public String getStreet() {
return street;
}

public void setStreet(String street) {
propertyChangeSupport.firePropertyChange("street", this.street,
this.street = street);
}

public String getNumber() {
return number;
}

public void setNumber(String number) {
propertyChangeSupport.firePropertyChange("number", this.number,
this.number = number);
}

public String getPostalCode() {
return postalCode;
}

public void setPostalCode(String postalCode) {
propertyChangeSupport.firePropertyChange("postalCode", this.postalCode,
this.postalCode = postalCode);
}

public String getCity() {
return city;
}

public void setCity(String city) {
propertyChangeSupport.firePropertyChange("city", this.city,
this.city = city);
this.city = city;
}

public String getCountry() {
return country;
}

public void setCountry(String country) {
propertyChangeSupport.firePropertyChange("country", this.country,
this.country = country);
}

public String toString() {
String s = "";
s += street != null ? street + " " : "";
s += number != null ? number + " " : "";
s += postalCode != null ? postalCode + " " : "";
s += city != null ? city + " " : "";
s += country != null ? country + " " : "";

return s;
}

}

Select the file MANIFEST.MF in the directory "META-INF". Select the tab "Runtime". Press add and add the package "de.vogella.databinding.person.model". This will enable other plugin to use the classes from this package.


Data Binding with SWT controls

3.1. RCP project

Create a new Eclipse RCP project "de.vogella.databinding.person.swt" use "Hello RCP" as a template (see Create your first Eclipse RCP application for details).

3.2. Model dependencies

Add the plugin "de.vogella.databinding.person.model" as a dependency to your plugin.

3.3. Eclipse Databinding dependencies

Add the plugins "org.eclipse.core.databinding", "org.eclipse.core.databinding.beans", "org.eclipse.jface.databinding" and "org.eclipse.core.databinding.property" as a dependency to your plugin.

3.4. UserInterface

Add via extension a view to the RCP application and create the class "de.vogella.databinding.person.PersonView" for this view.

Add the view to the perspective.

See Eclipse RCP Tutorial for details). Use the "Hello RCP" as a template.

Then implement the following code.

    
package de.vogella.databinding.person.swt;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.beans.BeansObservables;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.part.ViewPart;

import de.vogella.databinding.person.model.Address;
import de.vogella.databinding.person.model.Person;

public class PersonView extends ViewPart {
public static final String ID = "de.vogella.databinding.person.swt.View";
private Person person;

private Text firstName;
private Text ageText;
private Button marriedButton;
private Combo genderCombo;
private Text countryText;

public PersonView() {
}

@Override
public void createPartControl(Composite parent) {

person = new Person();
Address address = new Address();
address.setCountry("Deutschland");
person.setAddress(address);
person.setFirstName("John");
person.setLastName("Doo");
person.setGender("Male");
person.setAge(12);
person.setMarried(true);
// Lets put thing to order
Layout layout = new GridLayout(2, false);
parent.setLayout(layout);

Label firstLabel = new Label(parent, SWT.NONE);
firstLabel.setText("Firstname: ");
firstName = new Text(parent, SWT.BORDER);

GridData gridData = new GridData();
gridData.horizontalAlignment = SWT.FILL;
gridData.grabExcessHorizontalSpace = true;
firstName.setLayoutData(gridData);

Label ageLabel = new Label(parent, SWT.NONE);
ageLabel.setText("Age: ");
ageText = new Text(parent, SWT.BORDER);

gridData = new GridData();
gridData.horizontalAlignment = SWT.FILL;
gridData.grabExcessHorizontalSpace = true;
ageText.setLayoutData(gridData);

Label marriedLabel = new Label(parent, SWT.NONE);
marriedLabel.setText("Married: ");
marriedButton = new Button(parent, SWT.CHECK);

Label genderLabel = new Label(parent, SWT.NONE);
genderLabel.setText("Gender: ");
genderCombo = new Combo(parent, SWT.NONE);
genderCombo.add("Male");
genderCombo.add("Female");

Label countryLabel = new Label(parent, SWT.NONE);
countryLabel.setText("Country");
countryText = new Text(parent, SWT.BORDER);

Button button1 = new Button(parent, SWT.PUSH);
button1.setText("Write model");
button1.addSelectionListener(new SelectionAdapter() {

@Override
public void widgetSelected(SelectionEvent e) {
System.out.println("Firstname: " + person.getFirstName());
System.out.println("Age " + person.getAge());
System.out.println("Married: " + person.isMarried());
System.out.println("Gender: " + person.getGender());
System.out.println("Country: "
+ person.getAddress().getCountry());
}
});

Button button2 = new Button(parent, SWT.PUSH);
button2.setText("Change model");
button2.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
person.setFirstName("Lars");
person.setAge(person.getAge() + 1);
person.setMarried(!person.isMarried());
if (person.getGender().equals("Male")) {

} else {
person.setGender("Male");
}
if (person.getAddress().getCountry().equals("Deutschland")) {
person.getAddress().setCountry("USA");
} else {
person.getAddress().setCountry("Deutschland");
}
}
});

// Now lets do the binding
bindValues();
}

@Override
public void setFocus() {
}

private void bindValues() {
// The DataBindingContext object will manage the databindings
DataBindingContext bindingContext = new DataBindingContext();
IObservableValue uiElement;
IObservableValue modelElement;
// Lets bind it
uiElement = SWTObservables.observeText(firstName, SWT.Modify);
modelElement = BeansObservables.observeValue(person, "firstName");
// The bindValue method call binds the text element with the model
bindingContext.bindValue(uiElement, modelElement, null, null);

uiElement = SWTObservables.observeText(ageText, SWT.Modify);
modelElement = BeansObservables.observeValue(person, "age");
// Remember the binding so that we can listen to validator problems
// See below for usage
bindingContext.bindValue(uiElement, modelElement, null, null);

uiElement = SWTObservables.observeSelection(marriedButton);
modelElement = BeansObservables.observeValue(person, "married");

bindingContext.bindValue(uiElement, modelElement, null, null);

uiElement = SWTObservables.observeSelection(genderCombo);
modelElement = BeansObservables.observeValue(person, "gender");

bindingContext.bindValue(uiElement, modelElement, null, null);

// Address field is bound to the Ui
uiElement = SWTObservables.observeText(countryText, SWT.Modify);
modelElement = BeanProperties.value("address.country").observe(person);
bindingContext.bindValue(uiElement, modelElement, null, null);

}

}

The DataBindingContext object will manage the databindings. The bindValue method call binds the text element with the model. The first parameter is the UIElement with an update strategy (which defines then the update should take place) wrapped into a IObservableValue object, the second one does the same for the model object and the last parameter can be used to pass validators and converters to the binding. By passing null you get the default ones.

3.5. Run

Run the example and test it. Each time you change the UI element then model changes automatically. If you change the model then the UI will also update.

The coding also demonstrate that Eclipse Databinding converts per default already the from the typical values and that it performs in standard already nice validation.