Home Tutorials Training Consulting Books Company Contact us


Get more...

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);
    }
}

5.5. Run

When running the application the Simple Tree Part should look similar to this:

simpletreegrid