Nebula NatTable - GlazedLists Tutorial
This tutorial is based on Nebula NatTable 1.4 and explains the usage of NatTable together with GlazedLists.
1. Overview
How to create GlazedLists can be found here in the GlazedLists tutorial.
2. Prerequisite
This tutorial requires knowledge about GlazedLists and NatTable.
3. Exercise - Tracking changes to the list of elements
When elements are added, removed or set on a list, which is shown in a NatTable this should be reflected immediately.
So the one who works with the list of elements doesn’t have to know anything about the NatTable API, but can just change the underlying list, which basically is a java.util.List
.
3.1. GlazedListsEventLayer and EventList
In order to achieve this target the base list of the data provider should an EventList
and the GlazedListsEventLayer
should be used.
package com.vogella.nattable.parts.glazedlists;
import java.util.List;
import jakarta.annotation.PostConstruct;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
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.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import com.vogella.model.person.Person;
import com.vogella.model.person.PersonService;
import com.vogella.nattable.data.PersonDataProvider;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
public class EventListPart {
private int personId = 1000;
@PostConstruct
public void postConstruct(Composite parent, PersonService personService) {
List<Person> persons = personService.getPersons(10);
// create an EventList, which can track changes to the list of persons
EventList<Person> personEventList = GlazedLists.eventList(persons);
PersonDataProvider personDataProvider = new PersonDataProvider(personEventList);
DataLayer bodyDataLayer = new DataLayer(personDataProvider);
// add a GlazedListsEventLayer event layer that is responsible for
// updating the grid on list changes
GlazedListsEventLayer<Person> glazedListsEventLayer = new GlazedListsEventLayer<>(bodyDataLayer,
personEventList);
SelectionLayer selectionLayer = new SelectionLayer(glazedListsEventLayer);
ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
Button addPersonBtn = new Button(parent, SWT.PUSH);
addPersonBtn.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
addPersonBtn.setText("Add new Person");
addPersonBtn.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
Person newPerson = personService.createPerson(personId++);
personEventList.add(newPerson);
}
});
Button removePersonBtn = new Button(parent, SWT.PUSH);
removePersonBtn.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
removePersonBtn.setText("Remove first Person");
removePersonBtn.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (!personEventList.isEmpty()) {
personEventList.remove(0);
}
}
});
NatTable natTable = new NatTable(parent, viewportLayer);
GridDataFactory.fillDefaults().grab(true, true).span(2, 1).applyTo(natTable);
GridLayoutFactory.fillDefaults().numColumns(2).applyTo(parent);
}
}
3.2. Validate
Run the application, press the Add new Person or Remove first Person button and see the NatTable being updated directly.
4. Exercise - Observing elements within an EventList
4.1. Target
Changes to the elements within the list should also be reflected in the NatTable.
So when changing a Person
object’s first name, it should be shown in the table immediately.
Therefore the usage of an ObservableElementList
and the GlazedListsEventLayer
is required.
4.2. GlazedListsEventLayer and ObservableElementList
The code from the previous exercise can be reused with these additional lines of code:
// Make use of the ObservableElementList and it's bean connector for a person bean
ObservableElementList<Person> observableElementList = new ObservableElementList<>(personEventList,
GlazedLists.beanConnector(Person.class));
// ... creation of a data layer ...
// Add the GlazedListsEventLayer to the layer hierarchy
GlazedListsEventLayer<Person> glazedListsEventLayer = new GlazedListsEventLayer<>(bodyDataLayer,
observableElementList);
// ... add other layers, e.g., selection layer and viewport layer ...
// Add a button, which modifies a person object
Button modifyPersonBtn = new Button(parent, SWT.PUSH);
modifyPersonBtn.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
modifyPersonBtn.setText("Modify first Person");
modifyPersonBtn.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (!observableElementList.isEmpty()) {
Person person = observableElementList.get(0);
person.setFirstName("Simon");
}
}
});
The complete code would look like this:
package com.vogella.nattable.parts.glazedlists;
import java.util.List;
import jakarta.annotation.PostConstruct;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
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.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import com.vogella.model.person.Person;
import com.vogella.model.person.PersonService;
import com.vogella.nattable.data.PersonDataProvider;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.ObservableElementList;
public class BeanObservableEventListPart {
private int personId = 1000;
@PostConstruct
public void postConstruct(Composite parent, PersonService personService) {
List<Person> persons = personService.getPersons(10);
EventList<Person> personEventList = GlazedLists.eventList(persons);
// create an ObservableElementList, which can track changes to the person objects with a bean connector
ObservableElementList<Person> observableElementList = new ObservableElementList<>(personEventList,
GlazedLists.beanConnector(Person.class));
PersonDataProvider personDataProvider = new PersonDataProvider(observableElementList);
DataLayer bodyDataLayer = new DataLayer(personDataProvider);
// add a GlazedListsEventLayer event layer that is responsible for
// updating the grid on list changes
GlazedListsEventLayer<Person> glazedListsEventLayer = new GlazedListsEventLayer<>(bodyDataLayer,
observableElementList);
SelectionLayer selectionLayer = new SelectionLayer(glazedListsEventLayer);
ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
Button addPersonBtn = new Button(parent, SWT.PUSH);
addPersonBtn.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
addPersonBtn.setText("Add new Person");
addPersonBtn.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
Person newPerson = personService.createPerson(personId++);
observableElementList.add(newPerson);
}
});
Button removePersonBtn = new Button(parent, SWT.PUSH);
removePersonBtn.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
removePersonBtn.setText("Remove first Person");
removePersonBtn.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (!observableElementList.isEmpty()) {
observableElementList.remove(0);
}
}
});
Button modifyPersonBtn = new Button(parent, SWT.PUSH);
modifyPersonBtn.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
modifyPersonBtn.setText("Modify first Person");
modifyPersonBtn.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (!observableElementList.isEmpty()) {
Person person = observableElementList.get(0);
person.setFirstName("Simon");
}
}
});
NatTable natTable = new NatTable(parent, viewportLayer);
GridDataFactory.fillDefaults().grab(true, true).span(3, 1).applyTo(natTable);
GridLayoutFactory.fillDefaults().numColumns(3).applyTo(parent);
}
}
4.3. Validate
Run the application, press the Modify first Person button and see that the change of the first name is reflected in the NatTable.
5. Exercise: NatTable Tree Grid
This exercise shows how to create a simple tree in NatTable.
A new part will be added to the Eclipse RCP application like done in a previous exercise.
The new Part is supposed to show hierarchical Todo
objects.
5.1. Add a new part to the application model
Create a new Part
in the PartStack
.
Enter NatTable Tree Grid Example in the Label field and create a new Part
implementation by clicking on the Class URI lnik.
Set the value com.vogella.nattable.parts.tree
into the _Package field and the value SimpleTreePart
into the Name field.
By clicking on Finish the class SimpleTreePart
will be created in the com.vogella.nattable.parts.tree
package.
5.2. Getting data from a TreeList
An EventList
instance can be decorated by a TreeList
, which also needs a ca.odell.glazedlists.TreeList.Format<E>
instance and an ca.odell.glazedlists.TreeList.ExpansionModel<E>
instance to be created.
The TreeList.Format<E>
interface specifies 3 methods, which are used to built the tree structure from a list.
package com.vogella.nattable.parts.tree;
import java.util.Comparator;
import java.util.List;
import com.vogella.model.Todo;
import com.vogella.model.TreeItem;
import ca.odell.glazedlists.TreeList;
class TreeItemFormat implements TreeList.Format<TreeItem<Todo>> {
@Override
public void getPath(List<TreeItem<Todo>> path, TreeItem<Todo> element) {
if (element.getParent() != null) {
path.add(element.getParent());
}
path.add(element);
}
@Override
public boolean allowsChildren(TreeItem<Todo> element) {
return element.hasChildren();
}
@Override
public Comparator<? super TreeItem<Todo>> getComparator(int depth) {
return (o1, o2) -> o1.getItem().getSummary().compareTo(o2.getItem().getSummary());
}
}
The TreeList.Format<E>
methods will be called by NatTable to determine the tree path of the list items.
The TreeList
comes with two default TreeList.ExpansionModel<E>
implementations.
-
ca.odell.glazedlists.TreeList.nodesStartExpanded()
-
ca.odell.glazedlists.TreeList.nodesStartCollapsed()
The creation of the TreeList
can look like this:
TreeList<TreeItem<Todo>> treeList = new TreeList<TreeItem<Todo>>(eventList, new TreeItemFormat(), TreeList.nodesStartExpanded());
5.3. Providing data for a TreeLayer
For showing a tree in the Body Region of a NatTable instance the TreeLayer
is usually used.
For the creation of a TreeLayer
it expects it’s underlying Layer and an ITreeRowModel
as parameters.
To keep it simple only a the DataLayer
will be used as base layer for the TreeLayer
.
The DataLayer
instance will be based on a ListDataProvider, which handles the TreeList
from the Getting data from a TreeList chapter.
// Properties of the Todo items inside the TreeItems
String[] propertyNames = { "item.summary", "item.description" };
IColumnPropertyAccessor<TreeItem<Todo>> columnPropertyAccessor = new ExtendedReflectiveColumnPropertyAccessor<TreeItem<Todo>>(propertyNames);
EventList<TreeItem<Todo>> eventList = GlazedLists.eventList(todoService.getSampleTodoTreeItems());
TreeList<TreeItem<Todo>> treeList = new TreeList<TreeItem<Todo>>(eventList, new TreeItemFormat(), TreeList.nodesStartExpanded());
ListDataProvider<TreeItem<Todo>> dataProvider = new ListDataProvider<>(treeList, columnPropertyAccessor);
DataLayer dataLayer = new DataLayer(dataProvider);
Now that there is a DataLayer
based on a TreeList
, which is ready to be passed to a TreeLayer
, the only part what’s missing is the ITreeRowModel
as second parameter of the TreeLayer
.
EventList<TreeItem<Todo>> eventList = GlazedLists.eventList(todoService.getSampleTodoTreeItems());
TreeList<TreeItem<Todo>> treeList = new TreeList<TreeItem<Todo>>(eventList, new TreeItemFormat(), TreeList.nodesStartExpanded());
GlazedListTreeData<TreeItem<Todo>> glazedListTreeData = new GlazedListTreeData<>(treeList);
GlazedListTreeRowModel<TreeItem<Todo>> glazedListTreeRowModel = new GlazedListTreeRowModel<>(glazedListTreeData);
TreeLayer treeLayer = new TreeLayer(dataLayer, glazedListTreeRowModel);
So based on the TreeList
instance a ITreeRowModel
for the TreeLayer
can be created.
5.4. Create the NatTable instance
The complete implementation of the SimpleTreePart
would look like this:
package com.vogella.nattable.parts.tree;
import jakarta.annotation.PostConstruct;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.data.ExtendedReflectiveColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeData;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeRowModel;
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
import org.eclipse.swt.widgets.Composite;
import com.vogella.model.Todo;
import com.vogella.model.TodoService;
import com.vogella.model.TreeItem;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.TreeList;
public class SimpleTreePart {
@PostConstruct
public void createComposite(Composite parent, TodoService todoService) {
// Properties of the Todo items inside the TreeItems
String[] propertyNames = { "item.summary", "item.description" };
IColumnPropertyAccessor<TreeItem<Todo>> columnPropertyAccessor = new ExtendedReflectiveColumnPropertyAccessor<TreeItem<Todo>>(
propertyNames);
EventList<TreeItem<Todo>> eventList = GlazedLists.eventList(todoService.getSampleTodoTreeItems());
TreeList<TreeItem<Todo>> treeList = new TreeList<TreeItem<Todo>>(eventList, new TreeItemFormat(),
TreeList.nodesStartExpanded());
ListDataProvider<TreeItem<Todo>> dataProvider = new ListDataProvider<>(treeList, columnPropertyAccessor);
DataLayer dataLayer = new DataLayer(dataProvider);
setColumWidthPercentage(dataLayer);
GlazedListTreeData<TreeItem<Todo>> glazedListTreeData = new GlazedListTreeData<>(treeList);
GlazedListTreeRowModel<TreeItem<Todo>> glazedListTreeRowModel = new GlazedListTreeRowModel<>(
glazedListTreeData);
TreeLayer treeLayer = new TreeLayer(dataLayer, glazedListTreeRowModel);
treeLayer.setRegionName(GridRegion.BODY);
new NatTable(parent, treeLayer, true);
GridLayoutFactory.fillDefaults().generateLayout(parent);
}
private void setColumWidthPercentage(DataLayer dataLayer) {
dataLayer.setColumnPercentageSizing(true);
dataLayer.setColumnWidthPercentageByPosition(0, 50);
dataLayer.setColumnWidthPercentageByPosition(1, 50);
}
}
6. Links and Literature
Nothing listed.
6.1. vogella Java example code
If you need more assistance we offer Online Training and Onsite training as well as consulting