Home Tutorials Training Consulting Products Books Company Donate Contact us









NOW Hiring

Quick links

Share

This tutorial shows how to get started with Nebula NatTable. It shows how to get NatTable installed into the IDE and explains the basic concepts. It is also explained how to put data in a NatTable instance and how to create a NatTable with various compositions.

This tutorial is based on Nebula NatTable 1.5.0.

1. Introduction to NatTable

The following chapters give an introduction into NatTable. It explains how it can be installed into the Eclipse IDE and where to get further information and examples.

1.1. What is NatTable?

A table, grid or tree is used to visualize data in a structured way. NatTable is a framework to create table, grid and tree controls. It has a rich feature set and is designed to handle very large data sets. It comes with a lot of features users expect from a table implementation nowadays, like for example sorting, filtering, grouping and fixed/frozen columns and rows. Currently it is available for SWT but there are plans to port it to other UI frameworks like for example JavaFX.

To be able to show dynamic updating stock exchange data in a table, the NatTable project was started as a project for the banking sector. Unlike the SWT Table control, NatTable doesn’t use the native table control of the operating system. It extends the SWT Canvas class and paints the visible part of the table using the Canvas API.

Some characteristics of NatTable which distinguishes it from other table implementations are:

  • it can handle huge data sets with a lot of columns

  • full styling capabilities, e.g., styling the column headers

  • support for different row heights in a table

  • simple API for advanced table features like sorting, filtering, styling, grouping and dynamic updates

Why is it called NatTable?

The original implementation was dedicated to the girlfriend of the creator whose name was Natalie. A lot has changed since then and the original implementation was mainly refactored. But the name stayed. Today we think of Nat as an acronym for Not a(nother) table, because it is a lot more than a simple table control. This acronym was finally approved by Lars Vogel. When we met him at a conference, introducing NatTable to him, his first reaction was to shout out Oh No! Not Another Table widget. As said at the beginning, he was right with that statement, since it is a framework to create rich table, grid or tree controls and not just another table widget.

1.2. Version history

NatTable was created on SourceForge and developed there until version 2.3.2. In 2012 the project moved to the Eclipse Foundation as a subproject of the Nebula project. Because of the move, the project had a version reset to 0.9, indicating that the project is in incubation. This means the project needs to establish a fully-functional open-source project before releasing a major version. Since then the project got several improvements and stabilization and is out of incubation. It is therefore recommended to not using the SourceForge version anymore and instead use the latest Eclipse version.

1.3. Bundles and dependencies

The NatTable project is split into two categories, NatTable Core and NatTable Extensions.

1.3.1. NatTable Core

NatTable Core contains the core functionality with all the interface definitions and common implementations. It has the following dependencies:

  • SWT - rendering and interaction (key, mouse)

  • JFace - resource handling and dialogs

  • org.eclipse.equinox.common & org.eclipse.core.commands - JFace dependencies

  • Apache Commons Logging - internal logging

1.3.2. NatTable Extensions

The NatTable Extensions category contains special implementations of core functionality using third-party libraries. Currently there are four extensions available:

  1. The Eclipse 4 extension contains CSS styling capabilities using the Eclipse 4 CSS engine. It also contains e4 specific implementations like for example the E4SelectionListener to support selection handling the e4 way. The Eclipse 4 extension requires at least Eclipse Neon (4.6) and therefore requires Java 1.8 as minimum.

  2. The GlazedLists extension contains implementations for sorting, filtering and trees using the GlazedLists library. It also contains the GroupBy feature which is currently not available in NatTable Core. The GlazedLists Extension has an additional dependency to Apache Commons Codec, which is needed for serialization purposes.

  3. The Nebula extension contains implementations for integrating widgets from the Nebula project. Currently it only contains an ICellEditor and an ICellPainter that uses the RichText control. The Nebula extension requires at least Eclipse Luna (4.4) and therefore requires Java 1.7 as minimum.

  4. The Apache POI extension contains implementations for Excel exports using the Apache POI library. It adds support for advanced export features.

1.4. Available update sites

You can add NatTable to the Eclipse IDE via the project update site. The available update sites are listed on the Nebula NatTable download page. There is a separate update site for every released version. For example, the update site for the 1.5.0 release is the following:

http://download.eclipse.org/nattable/releases/1.5.0/repository/

Since NatTable 1.4.0, the NatTable features also include the required third-party-libraries. This makes it it easier to install and to consume the NatTable features in a feature based application.

In NatTable versions before 1.4.0 you need to ensure that the necessary third-party dependencies are available in order to install the NatTable extensions. The necessary third-party libraries GlazedLists and Apache POI can be retrieved via Eclipse Orbit. Eclipse Orbit is a project that provides a repository that contains bundled versions of third-party libraries that are approved for use in one or more Eclipse projects.

2. NatTable example application

2.1. Exploring the NatTable capabilities with the example application

The NatTable project provides an application that shows various examples for the usage of NatTable. There you can find examples for almost every feature.

As you can see in the following screenshot, the application is split into two sections, a tree on the left that lists all available examples and a right section where the examples are opened in tabs.

nattable examples

Via the View Source link at the bottom of the example, you are able to view the source code of the example.

The tree currently contains three main sections if you download and start the Eclipse RCP version:

  • Tutorial Examples
    The examples that are referred to in upcoming tutorials.

  • Classic Examples
    The examples that exist for a long time.

  • E4 Examples
    Examples to show the use of E4 mechanisms like CSS styling or the E4 selection mechanism.

The E4 Examples are not available in the plain SWT version of the NatTable examples application.

2.2. Starting the example application

To start the NatTable examples application you have several options. First you need to download the application from the NatTable website. It can be reached by clicking on the Try it button on the start page.

nattable website

The easiest way to download the Eclipse RCP version for your operating system. Unzip the downloaded archive and start the application via the executable.

The second option to run the examples application is to download the JAR file for the desired version (currently NatTableExamples-1.5.0.jar) and start it via command line. The JAR file is also available via the NatTable Examples Application Download Page. The JAR file does not contain the platform specific SWT libraries. Therefore you need to ensure that the SWT jar file for your platform is added to the classpath when starting the examples application.

For starting the NatTable examples application on a 32bit Windows machine you would need to copy the org.eclipse.swt.win32.win32.x86_.jar to the same place where the NatTableExamples JAR is located and call

java -cp org.eclipse.swt.win32.win32.x86_.jar;NatTableExamples.jar org.eclipse.nebula.widgets.nattable.examples.NatTableExamples

The third option is to run the examples application from the sources. Checked out the sources from Git as explained in the Contribution Guide. Afterwards start the org.eclipse.nebula.widgets.nattable.examples.NatTableExamples main class in the org.eclipse.nebula.widgets.nattable.examples project.

3. Exercise: Install NatTable

3.1. Install into the IDE

Open the Eclipse Installation Manager via Help ▸ Install New Software…​. Insert http://download.eclipse.org/nattable/releases/1.5.0/repository/ in the Work with: field. The result should look similar to the following screenshot.

nattable update site

Select all items in all categories. This also installs the corresponding source features, which allows you to debug into the NatTable code if necessary.

Click the Next button to verify your selection. Press the Next button again and accept the license information. Finally click on Finish to start the installation procedure.

After the installation procedure is finished, restart the Eclipse IDE to have a clean state.

3.2. Install NatTable with a target definition

Create a new General Project called com.vogella.nattable.target and a new target definition called com.vogella.nattable.target.target inside this project.

target platform project

Now open the com.vogella.nattable.target.target file in the target definition editor and add all necessary update sites and dependencies.

Table 1. Update sites for the target definition
Name URL Content

Eclipse release update site

http://download.eclipse.org/releases/neon/

Eclipse SDK and Eclipse Platform Launcher Executables

NatTable update site

http://download.eclipse.org/nattable/releases/1.5.0/repository/

All

vogella sample data model

https://dl.bintray.com/vogellacompany/codeexamples-javadatamodel/

All

The following features of the update sites above should be added to the target definition file.

target platform

Ensure you activate this target definition by clicking on the Set as Target Platform link in the target definition editor.

4. Architecture

The architecture of NatTable is quite special to support flexible composition and configuration. The following chapters covers the architecture basics, explaining layers, configurations, command and event handling and the label concept used for conditional configuration.

4.1. Basics and Limitations

There are two general facts about NatTable you need to be aware of:

  • NatTable is a so called virtual table. This means it only processes the current visible part of the table. This is one of the reasons why it can handle huge data sets as the drawing is very efficient.

  • NatTable is currently only a framework to create the control that visualizes data. The default implementations are operating on data that resides in memory. To process huge data sets, typically the size of the memory is the limiting factor. To handle such data sets, you need to provide your custom implementations.

Currently NatTable can handle max int rows * max int columns, because the implementation uses Integer based indices. Technically it could support even greater ranges if the default implementation is updated to use Long for example. This is something considered for a future release.

4.1.1. Comparison to other table widgets

The SWT table implementation, and therefore also the JFace implementation, is based on the native table control of the operating system. NatTable is completely different, i.e., it is a custom painted control. Therefore, NatTable does not have the limitations of the native controls, like the missing ability to show different row heights in one table (an issue on Windows) or that it is not possible to style column headers.

Comparing how the widgets get created, you also notice a difference. To create a SWT Table instance you first need to create the Table as a container and then create a TableColumn for each column you want to add to the table. So you are attaching columns side by side in order to create a table, as depicted in the following diagram.

jface table design

The following code demonstrates the creation of a table with JFace.

// create the observable list to be able to enable databinding
IObservable list = new WritableList(personService.getPersons(10), Person.class);

// create and configure the TableViewer as container
TableViewer viewer = new TableViewer(parent,
    SWT.MULTI|SWT.BORDER|SWT.H_SCROLL|SWT.V_SCROLL|SWT.FULL_SELECTION);
ObservableListContentProvider cp = new ObservableListContentProvider();
viewer.setContentProvider(cp);
viewer.getTable().setHeaderVisible(true);
viewer.getTable().setLinesVisible(true);

// create a new column for the TableViewer
TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
column.getColumn().setText("Firstname");
column.getColumn().setWidth(100);
column.setLabelProvider(new FirstNameLabelProvider());
column.setEditingSupport(new FirstNameEditingSupport(viewer));

// create a new column for the TableViewer
column = new TableViewerColumn(viewer, SWT.NONE);
column.getColumn().setText("Lastname");
column.getColumn().setWidth(100);
column.setLabelProvider(new LastNameLabelProvider());
column.setEditingSupport(new LastNameEditingSupport(viewer));

// set the input to the table
viewer.setInput(list);

For additional configurations like styling or editing you need to create further objects per column, e.g. LabelProvider, EditingSupport. So the number of objects that need to be created in order to build and configure a table is relatively high.

This is one of the reasons why tables based on native table controls like the SWT Table do not scale well in case a lot of columns are involved. For a huge amount of rows, it is possible to create a virtual SWT Table using the SWT.VIRTUAL style bit, which scales well today.

In NatTable you are specifying a data provider that returns values based on column and row index. To add features you stack up layers, and to configure the features and everything else you create configurations. It is not created column by column.

4.1.2. No JFace databinding support

JFace databinding allows to bind data model attributes to user interface components with support for automated conversion and validation.

The request for databinding support in NatTable is raised sometimes, but fortunately it is not necessary. NatTable already directly operates on the data model as the data providers are connected to it. And it also supports data conversion and validation mechanisms that fit better into the concepts of NatTable.

For completeness, there is also a technical reason for not supporting JFace databinding in NatTable. Using JFace databinding you connect a Control to a model property. This is possible when using a SWT Table, because every column is a Control itself. As NatTable is a custom painted control that is not created using TableColumn controls, there is no possibility to create a connection between control and model via JFace databinding. It might be possible to create some kind of wrapper to add such support, but it wouldn’t match the NatTable architecture and could possibly have negative impact on the performance.

4.2. Layers

A NatTable instance is typically build out of several layers. A layer is a rectangular region of cells and has methods to access columns, rows, width and height. All layers implement the ILayer interface. Every basic feature is implemented in a layer and can be added to a NatTable instance by adding the layer to a layer stack or a layer composition. For example, to add the ability to select cells in a table you need to add the SelectionLayer.

NatTable Architecture

The above diagram shows the stacking and composing of a NatTable grid.

4.2.1. Layer stacks

Layers can be stacked to combine multiple features in a NatTable instance. To do this almost every layer is created on top of another layer at creation time. The only exception to this is the DataLayer.

The DataLayer needs to be the bottom most layer in a layer stack and is mandatory.

4.2.2. Layer compositions

Additionally to stacking layers, it is also possible to create a layer composition where several layers or layer stacks are positioned side by side. A grid, for example, is a common composition that consists of four regions: corner, column header, row header and body, where every region contains separate layer stacks.

4.2.3. Position-Index transformation

By stacking a layer on top of another layer it is possible to expose a transformed view of its underlying layer cell structure. A transformed view for example can be used to hide or reorder columns.

Columns and rows in a layer can be referenced either by position or by index.

  • The position of a column/row in a layer corresponds to the location of the column/row in the CURRENT layer. Positions always start from 0 and increase sequentially.

  • The index of a column/row in a layer corresponds to the location of the column/row in the LOWEST level layer in the layer stack. Indexes are not necessarily ordered, and in some cases may not even be unique within a layer above the lowest level layer.

These concepts are illustrated by the following example. The layer stack shows a ColumnHideShowLayer that is stacked on top of a ColumnReorderLayer which is in turn stacked on a DataLayer. The positions in the DataLayer are the same as its indexes, because it is the lowest level layer in the stack. The ColumnReorderLayer reorders column 0 of its underlying layer after column 2 of its underlying layer. The ColumnHideShowLayer hides the first column of its underlying layer.

ColumnHideShowLayer
0 1 2 3 4 <- column positions
2 0 3 4 5 <- column indexes


ColumnReorderLayer
0 1 2 3 4 5 <- column positions
1 2 0 3 4 5 <- column indexes


DataLayer
0 1 2 3 4 5 <- column positions
0 1 2 3 4 5 <- column indexes
Usually, the framework does the necessary position-index transformations. So typically, it is not necessary to perform that transformation manually. In the few cases where it might be necessary, you can have a look at the LayerUtil helper class, which provides some static methods for that purpose.

4.3. Configuration

Additionally to the concept of layers, NatTable has a configuration concept to be able to customize styling and behavior of layers as well as the table as a whole. Every layer typically introduces configurations for the features they add.

Configurations are registered via registries. There are two configuration registries for each NatTable instance:

  • IConfigRegistry: Contains different configuration attributes, like styling, editing or layer specific configurations.

  • IUIBindingRegistry: Contains UI bindings for mouse and keyboard interactions.

For initial configuration it is recommended to create separate configuration classes. This ensures to setup a NatTable instance correctly and betters the encapsulation. It is also possible to put configurations directly into a registry which is mostly used for dynamic behavior changes at runtime. There are four types of configuration classes:

  • AbstractRegistryConfiguration: Used to register configuration values to the IConfigRegistry.

  • AbstractUiBindingConfiguration: Used to bind actions to UI interactions via IUIBindingRegistry.

  • AbstractLayerConfiguration: Used to configure an ILayer. This typically means to register ILayerCommandHandler and ILayerEventHandler on a layer.

  • AggregateConfiguration: Used to combine several configurations in one. Several default configurations delivered with NatTable are implementations of AggregateConfiguration. Using this only one configuration instance needs to be registered to add a configuration for styling and ui binding at once.

The following simplified UML diagram shows the relationship between configurations, the NatTable and layers.

NatTable Konfiguration UML vereinfacht

Editing support is one of the advanced features that is solely specified via configuration. There is no layer that needs to be added to the layer stack in order to enable editing.

When creating a NatTable instance with auto configuration turned on, internally the DefaultNatTableStyleConfiguration will be added and NatTable#configure() will be called automatically to start building up the registries. In such a case configuration instances can only be added to layers before the NatTable instance is created. Adding configurations to layers afterwards will not have an effect. Also trying to add configuration instances to the NatTable instance will result in an IllegalStateException.

The auto configuration can be turned off at creation time by setting the constructor parameter autoconfigure to false. Doing this allows to add configuration instances to layers and the NatTable instance itself, until the registries are build up executing NatTable#configure(). After the registries are build up it is not possible to add further configuration instances. This is because configuration instances will only get interpreted once for initially building up the registries.

It is still possible to modify, add, remove or exchange configuration values at runtime by directly operating on the registries itself.

In case the auto configuration of NatTable is disabled, you need to ensure that at least the DefaultNatTableStyleConfiguration or a equivalent one is added. Otherwise the rendering won’t work correctly because of missing configuration attributes.

The following listing demonstrates the creation of a NatTable instance with auto configuration turned off.

// create a NatTable using a GridLayer and configure to not using the default configurations
NatTable natTable = new NatTable(parent, gridLayer, false);

// add the default style configuration
natTable.addConfiguration(new DefaultNatTableStyleConfiguration());

// add additional custom configurations
...

// call NatTable#configure() to start the configuration process
natTable.configure();

Some layers need the reference to the IConfigRegistry as constructor parameter. This is because in the current architecture there is nothing like a global context, and the layers itself are well encapsulated to not having any direct reference to the NatTable instance they are connected to.

The following listing demonstrates the pattern for creating a NatTable instance that uses an externally created IConfigRegistry.

// create a ConfigRegistry that can be used to create advanced layers
ConfigRegistry configRegistry = new ConfigRegistry();

// create some layers
...

// create a NatTable using a GridLayer and configure to not using the default configurations
NatTable natTable = new NatTable(parent, gridLayer, false);

// set the ConfigRegistry to the NatTable instance BEFORE additional configuration are added
natTable.setConfigRegistry(configRegistry);

// add the default style configuration
natTable.addConfiguration(new DefaultNatTableStyleConfiguration());

// add additional custom configurations
...

// call NatTable#configure() to start the configuration process
natTable.configure();

4.4. Cell display mode

In NatTable a cell can have several modes in which they are displayed. These modes are called DisplayMode. This allows to specify different configurations for the same cell in different modes. The following table lists the currently supported display modes.

Table 2. NatTable DisplayModes
DisplayMode Description

DisplayMode#NORMAL

The normal state a cell is in if no other state applies.

DisplayMode#SELECT

The state that shows that a cell is currently selected.

DisplayMode#EDIT

The state that shows that a cell is currently edited.

This DisplayMode is never applied to a cell and is only used for configuration purposes!

DisplayMode#HOVER

The state that shows that currently the mouse hovers over the cell.

DisplayMode#SELECT_HOVER

The state that shows that currently the mouse hovers over the cell that is currently selected.

4.5. Configuration by labels

NatTable supports conditional configuration via a label mechanism. This means that it is possible to add labels to a cell and register configurations for labels.

Every cell in a NatTable has a so called LabelStack, which is a collection of Strings. Via labels it is possible to tie configurations to specific cells, for example styling a cell with a special error style or configure a checkbox editor for a cell that contains boolean values.

The following listing shows an example that registers an ImagePainter for the label myImageLabel. Further details on registering configuration attribute values are described in Configuration attributes.

configRegistry.registerConfigAttribute(
    CellConfigAttributes.CELL_PAINTER,
    new ImagePainter(),
    DisplayMode.NORMAL,
    "myImageLabel");

How labels are added to the LabelStack of a cell is described in the following sections.

When using a CompositeLayer it is possible to set a region label per composition region. You can think of the region label as a default label that is applied to every cell in the corresponding region, e.g. every cell in the body region of a grid has the label GridRegion.BODY in its LabelStack.

Every layer is able to add labels to a cells label stack in the ILayer#getConfigLabelsByPosition(int, int) method. This way layer specific stylings are added, e.g. a sort indicator in the column header cells in case the SortHeaderLayer is involved. Additionally custom labels can be added by using an IConfigLabelAccumulator. Each layer in the layer composition can take an IConfigLabelAccumulator which can be set by calling AbstractLayer#setConfigLabelAccumulator(IConfigLabelAccumulator).

4.5.1. Add custom labels by standard criteria

NatTable ships with several default implementations that can be used to register custom labels. They implement AbstractOverrider and allow registering of labels for special criteria.

Table 3. AbstractOverrider implementations
AbstractOverrider implementation Description

CellOverrideLabelAccumulator

Registers a label that is applied in case the specified value is shown in the specified column. registerOverride(Object cellValue, int col, String configLabel)

ColumnOverrideLabelAccumulator

Register labels that will be added to the label stack of cells in the column of the specified index. registerColumnOverrides(int columnIndex, String…​ configLabels) registerColumnOverridesOnTop(int columnIndex, String…​ configLabels)

RowOverrideLabelAccumulator

Register labels that will be added to the label stack of cells in the row of the specified index. registerOverrides(int rowIndex, String…​configLabels)

4.5.2. Add column position based default labels

The ColumnLabelAccumulator is a simple implementation that adds column based labels to the label stack. The labels follow the pattern COLUMN_ + <column position>. Via the constant ColumnLabelAccumulator#COLUMN_LABEL_PREFIX concatenated with the column position, it is possible to register custom configurations for those default labels.

4.5.3. Add custom labels by custom criteria

As there are several use cases where the default implementations doesn’t match, it is of course possible to create custom IConfigLabelAccumulator. By implementing IConfigLabelAccumulator#accumulateConfigLabels(LabelStack, int, int) it is possible to add additional labels to the given LabelStack. You can simply add a custom label by calling LabelStack#addLabel(String) which means it will be added to the end of the stack. To ensure the newly added label gets a higher priority the LabelStack#addLabelOnTop(String) method can be called, which will add the new label at the first position of the label stack.

Setting a label at the top of a label stack doesn’t ensure that no other layer or accumulator adds another label on top afterwards.

The following code snippet is an example for an IConfigLabelAccumulator that uses the IDataProvider of the body to be able to retrieve the object which is currently processed.

// add labels to provide conditional styling
bodyDataLayer.setConfigLabelAccumulator(new IConfigLabelAccumulator() {
    @Override
    public void accumulateConfigLabels(
        LabelStack configLabels, int columnPosition, int rowPosition) {

        Person p = bodyDataProvider.getRowObject(rowPosition);
        if (p != null) {
            configLabels.addLabel(
                p.getGender().equals(Gender.FEMALE) ? FEMALE_LABEL : MALE_LABEL);
        }
    }
});

Best practice is to set an IConfigLabelAccumulator to the DataLayer as there you don’t need to take care about the index-position transformation. This is because the DataLayer is the bottom-most layer in a layer composition, which means the index == position.

4.5.4. Add multiple labels

A layer only takes one IConfigLabelAccumulator. To use multiple IConfigLabelAccumulator instances, there needs to be a way to combine them. To achieve this the AggregateConfigLabelAccumulator can be used which is itself an IConfigLabelAccumulator that is able to combine multiple instances.

4.6. Configuration attributes

As explained before, the IConfigRegistry is the instance global registry for various typed configuration attributes. Despite UI bindings and special layer configurations, almost every NatTable customization that can be configured is done by registering a value for a configuration attribute. This includes for example styling, editing, layer specific configurations (e.g. configuration of a summary row) and non-layer specific configurations (e.g. excel export).

Configuration attributes are defined via the ConfigAttribute<T> interface, where the generic specifies the value type.

The following code specifies for example a configuration attribute for the value type org.eclipse.swt.graphics.Color.

ConfigAttribute<Color> GRID_LINE_COLOR = new ConfigAttribute<Color>();

The supported default configuration attributes are defined as constants at several places, e.g. CellConfigAttributes or EditConfigAttributes.

4.6.1. Register configuration attributes

Configuration attribute values can be registered by calling one of the available registerConfigAttribute() methods on IConfigRegistry. Since ConfigAttribute<T> is a generic interface, the registration of configuration attribute values is type safe.

The registerConfigAttribute() methods can be called within IConfiguration instances for initial configuration, as well as at runtime to support dynamic configuration changes. Note that changes to the IConfigRegistry outside an IConfiguration need to be performed after NatTable#configure() is called. Otherwise the manually registered values might be overriden by an IConfiguration. The reason for this is that the initial configuration attribute values are added to the IConfigRegistry via IConfiguration instances on calling NatTable#configure().

To register a value for a configuration attribute, you need to specify the ConfigAttribute<T> and the value to set. Additionally you can specify the DisplayMode and a label for which the configuration attribute should be registered. See Cell display mode and Configuration by labels for further information on that.

Without specifying the DisplayMode, the configuration attribute is registered for DisplayMode#NORMAL.

The following snippet shows some examples for registering configuration attribute values.

// register a global default style
configRegistry.registerConfigAttribute(
    CellConfigAttributes.CELL_STYLE,
    cellStyle);

// register a global style for selected cells
configRegistry.registerConfigAttribute(
    CellConfigAttributes.CELL_STYLE,
    selectionCellStyle,
    DisplayMode.SELECT);

// register a conditional style a custom label
configRegistry.registerConfigAttribute(
    CellConfigAttributes.CELL_STYLE,
    customCellStyle,
    DisplayMode.NORMAL,
    "myLabel");

4.6.2. Unregister configuration attributes

Configuration attribute values can be unregistered by calling one of the available unregisterConfigAttribute() methods on IConfigRegistry.

Unregistering configuration attribute values is typically used to change a NatTable configuration dynamically at runtime. But it can also be done when extending and modifying a default NatTable configuration.

Unregistering configuration attribute values outside an IConfiguration need to be performed after NatTable#configure() is called. Otherwise the values to unregister might not have been registered yet and will be added afterwards.

4.6.3. Determine the configuration attribute value

As there can be various values registered for the same configuration attribute by specifying DisplayMode and label, there is a search pattern to determine the value that should be used.

  • Determine the IDisplayModeOrdering for the current DisplayMode.

  • Iterate over the DisplayMode`s specified by the found `IDisplayModeOrdering.

  • For every DisplayMode, iterate over the labels in a LabelStack and search for a configuration attribute value. If no value is found for a DisplayMode and label combination, check for a value that is registered only for the DisplayMode.

The first value that is found for a DisplayMode and label combination will be used.

The following gives an example on the determination of a configuration attribute value. The value (e.g. a style) is searched for a cell that is selected and has the LabelStack [COLUMN_3, myLabel].

The IDisplayModeOrdering for DisplayMode#SELECT is [SELECT, NORMAL]. Therefore the search order is as follows:

  • DisplayMode#SELECT && label == COLUMN_3

  • DisplayMode#SELECT && label == myLabel

  • DisplayMode#SELECT

  • DisplayMode#NORMAL && label == COLUMN_3

  • DisplayMode#NORMAL && label == myLabel

  • DisplayMode#NORMAL

4.7. Commands & Events

Interactions in NatTable are implemented using a special command and event pattern.

NatTable Commands Events

Every UI interaction triggers a command that is transported down the layer stack until a handler takes the command and performs several corresponding actions. Typically the command gets consumed when it is handled which means it is not passed further down the layer stack. There are a few exceptions to that rule for some internal commands that need to get handled in several places, e.g. ClientAreaResizeCommand.

After the command is executed, an event is fired that is transported up the layer stack, giving every layer and every listener the ability to react on possible structural or visual state changes that are the result of the command processing. Events are never consumed and will always be transported to the top of the layer stack.

NatTable is executing commands on user interactions by registering actions to UI interactions. If actions on a NatTable instance should be performed programmatically by pressing a button outside the NatTable instance, e.g. hiding several columns or triggering a visual refresh, the NatTable commands can also be created and processed programmatically.

natTable.doCommand(new VisualRefreshCommand());

5. Data Access

The first step on creating a NatTable instance is to configure how the data is provided. For the layer architecture of NatTable this means, you need to use the DataLayer as lower most layer in a layer stack.

5.1. Providing data to the DataLayer

To create a DataLayer you need to provide a IDataProvider.

The IDataProvider is the connection between the model and NatTable. It provides information about the number of columns and rows and grants access to the model via column and row index. Below you can see the interface definition.

public interface IDataProvider {

    Object getDataValue(int columnIndex, int rowIndex);

    void setDataValue(int columnIndex, int rowIndex, Object newValue);

    int getColumnCount();

    int getRowCount();
}

The implementation of an IDataProvider to access data in a custom data structure is straight forward. You could for example show data that is stored in a two-dimensional array as shown below.

class TwoDimensionalArrayDataProvider implements IDataProvider {

    private String[][] data;

    public TwoDimensionalArrayDataProvider(String[][] data) {
        this.data = data;
    }

    @Override
    public Object getDataValue(int columnIndex, int rowIndex) {
        return this.data[columnIndex][rowIndex];
    }

    @Override
    public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
        this.data[columnIndex][rowIndex] = newValue != null ? newValue.toString() : null;
    }

    @Override
    public int getColumnCount() {
        return this.data.length;
    }

    @Override
    public int getRowCount() {
        return this.data[0] != null ? this.data[0].length : 0;
    }
}

The above example is based on the NatTable Examples Application Tutorial Examples ▸ Data ▸ CustomDataProviderExample

5.2. Providing data stored in a list

In most of the cases the data shown in a NatTable is stored in a collection. Typically a list will be used because of the need for ordering and index based access. For such cases NatTable provides the ListDataProvider which implements IRowDataProvider to access data row-based via index.

While the ListDataProvider automatically uses the index of an element in a list as the row index, you need to specify which model property should be shown per column. This is done using an instance of the IColumnAccessor class. To create a ListDataProvider instance you need to specify the list that contains the data and an IColumnAccessor.

List<Person> persons = ...;

IColumnPropertyAccessor<Person> columnPropertyAccessor = ...;

IDataProvider bodyDataProvider = new ListDataProvider<Person>(
        persons, columnPropertyAccessor);

5.3. Mapping properties to columns

When using a ListDataProvider to visualize objects in a NatTable, it is necessary specify how to access the object properties of the model elements and how they are mapped to column positions. This task is taken by the IColumnAccessor.

The IColumnAccessor is similar to the IDataProvider. While the IDataProvider operates on row and column index, the IColumnAccessor operates on an Object and a column index. You can see the interface definition below.

public interface IColumnAccessor<T> {

    Object getDataValue(T rowObject, int columnIndex);

    void setDataValue(T rowObject, int columnIndex, Object newValue);

    int getColumnCount();
}

Similar to implementing an IDataProvider implementing a custom IColumnAccessor is straight forward. Below you see an example that will show the first name of a Person object at column index 0 and the last name of the Person object at column index 1 in a NatTable that shows two columns.

class PersonColumnAccessor implements IColumnAccessor<Person> {

    @Override
    public Object getDataValue(Person rowObject, int columnIndex) {
        switch (columnIndex) {
            case 0:
                return rowObject.getFirstName();
            case 1:
                return rowObject.getLastName();
        }
        return "";
    }

    @Override
    public void setDataValue(Person rowObject, int columnIndex, Object newValue) {
        switch (columnIndex) {
            case 0:
                rowObject.setFirstName((String)newValue);
                break;
            case 1:
                rowObject.setLastName((String)newValue);
                break;
        }
    }

    @Override
    public int getColumnCount() {
        return 2;
    }
}

5.4. Mapping property names to columns

Additionally to the IColumnAccessor there is a IColumnPropertyResolver which is used to map between the property name in the backing bean and its corresponding column index in the NatTable instance. The IColumnPropertyResolver is used internally for several framework functionalities like sorting in combination with GlazedLists.

The following example shows the method signatures that are added by the IColumnPropertyResolver class.

public interface IColumnPropertyResolver {

    String getColumnProperty(int columnIndex);

    int getColumnIndex(String propertyName);
}

Typically you don’t need to implement a IColumnPropertyResolver directly. Instead of creating just an IColumnAccessor instance you will usually create an instance of the IColumnPropertyAccessor class which is a combination of both, IColumnAccessor and IColumnPropertyResolver.

To extend the PersonColumnAccessor class from above to implement IColumnPropertyAccessor you need to add the necessary methods like shown in the example below.

public static final String[] PERSONWITHADDRESS_PROPERTY_NAMES = {
    FIRSTNAME_PROPERTYNAME,
    LASTNAME_PROPERTYNAME
};

@Override
public String getColumnProperty(int columnIndex) {
    return PERSONWITHADDRESS_PROPERTY_NAMES[columnIndex];
}

@Override
public int getColumnIndex(String propertyName) {
    return Arrays.asList(PERSONWITHADDRESS_PROPERTY_NAMES).indexOf(propertyName);
}

NatTable comes with two generic IColumnPropertyAccessor implementations, that are using reflection to access the properties in a model object. The two implementations ReflectiveColumnPropertyAccessor and ExtendedReflectiveColumnPropertyAccessor are explained below.

5.5. Access properties via reflection

The ReflectiveColumnPropertyAccessor accesses the properties in a model object via reflection. To specify which columns should be shown in which order, it takes a String array as constructor parameter.

String[] propertyNames = {"firstName", "lastName", "gender", "married", "birthday"};

IColumnPropertyAccessor<Person> columnPropertyAccessor =
    new ReflectiveColumnPropertyAccessor<Person>(propertyNames);

The ReflectiveColumnPropertyAccessor is searching for attributes via the Java property specification. That means, you need to create a getter and a setter for the attribute you want to access by following the general naming conventions [attribute = firstName, getter = getFirstName(), setter = setFirstName()].

5.6. Extended access to properties via reflection

The ReflectiveColumnPropertyAccessor is not able to access nested objects. It only operates on the direct properties of a class. To support also nested properties the ExtendedReflectiveColumnPropertyAccessor was introduced. It is created the same way as the ReflectiveColumnPropertyAccessor. The only difference is that you are able to specify nested properties via dot separated attribute concatenation.

String[] propertyNames = {"firstName", "lastName", "address.street", "address.city"};

IColumnPropertyAccessor<PersonWithAddress> columnPropertyAccessor =
    new ExtendedReflectiveColumnPropertyAccessor<PersonWithAddress>(propertyNames);

5.7. Calculated values

For most use cases the provided default implementations of IDataProvider and IColumnPropertyAccessor should be sufficient. But there are of course use cases where you need to create a custom implementation. One case for example is to add calculated values to a grid. Of course you could also extend the model to contain the calculated value itself, but for a clean separation the calculated value is usually not part of it.

To add a column with calculated values, you can for example create a custom IColumnAccessor that calculates the value in the getDataValue() method.

class CalculatingDataProvider implements IColumnAccessor<NumberValues> {

    @Override
    public Object getDataValue(NumberValues rowObject, int columnIndex) {
        switch(columnIndex) {
            case 0: return rowObject.getColumnOneNumber();
            case 1: return rowObject.getColumnTwoNumber();
            case 2: return rowObject.getColumnThreeNumber();
            case 3: //calculate the sum
                    return rowObject.getColumnTwoNumber() + rowObject.getColumnThreeNumber();
            case 4: //calculate the percentage
                    return new Double(rowObject.getColumnTwoNumber() + rowObject.getColumnThreeNumber())
                        / rowObject.getColumnOneNumber();
        }
        return null;
    }

    @Override
    public void setDataValue(NumberValues rowObject, int columnIndex, Object newValue) {
        switch(columnIndex) {
            case 0: rowObject.setColumnOneNumber((Integer)newValue);
                    break;
            case 1: rowObject.setColumnTwoNumber((Integer)newValue);
                    break;
            case 2: rowObject.setColumnThreeNumber((Integer)newValue);
                    break;
            //as column 3 and 4 are calculated we don't need to update the model
        }
    }

    @Override
    public int getColumnCount() {
        //this example will show exactly 5 columns
        return 5;
    }
}

The full example can be found in the NatTable Examples Application Tutorial Examples ▸ Data ▸ CalculatedDataExample

The example above will calculate the value everytime the data value is requested. As this can cause serious performance issues in some cases the CalculatedValueCache was introduced. An example on how to use it can be found in the NatTable Examples Application Tutorial Examples ▸ Integration ▸ CachedCalculatingGridExample

5.8. Dynamic columns

Typically the number of columns is fixed because of the number of members a class specifies. For use cases where dynamic column creation or removal is necessary, you also need to provide a custom implementation of an IDataProvider or IColumnPropertyAccessor. A custom implementation will need to implement a mechanism to access column properties dynamically, similar to the mechanism the ListDataProvider uses to access rows dynamically. This can be done for example if the model contains a collection of values as a property.

You can find an example for dynamic columns in the NatTable Examples Application Tutorial Examples ▸ Data ▸ DynamicColumnExample

5.9. Exercise: NatTable data providing

5.9.1. Overview of the example

This exercise shows the basic setup of a NatTable. In this exercise you build an Eclipse RCP application which displays data of persons in a NatTable. Each person is displayed in one individual row.

5.9.2. Prerequisite

This exercise requires that you configured the target platform as described in Install NatTable with a target definition.

5.9.3. Create the Eclipse 4 RCP project

Create a new Eclipse RCP application via the File ▸ New ▸ Other ▸ Plug-in Development ▸ Plug-in Project menu entry.

Set the project name to com.vogella.nattable. Click Next and select that you want to create a rich client application. Select the Eclipse 4 RCP application template and click Next. Select the Create sample content and press the Finish button.

5.9.4. Configure the dependencies

Open the MANIFEST.MF file and add the following dependencies to the Required Plug-ins in the Dependencies section.

  • com.vogella.model

  • com.vogella.model.service

  • org.eclipse.nebula.widgets.nattable.core

Open the product definition file com.vogella.nattable.product and enter com.vogella.nattable.product into the ID field.

exercise product overview

Also add the manifest dependencies to the Dependencies section of the product definition.

exercise product dependencies

5.9.5. Add or modify your part in the application model

You want to use NatTable in part of your application. If you have created the RCP application with sample content as suggested earlier you can use this part.

Otherwise, add a PartStack to the application model using the Application ▸ Windows and Dialogs ▸ Trimmed Window ▸ Controls menu entry in the Eclipse 4 model editor. Create a new Part in this PartStack.

Ensure that your part is labeled NatTable Data Example via the Label field. Create the NatTableDataExamplePart class in the com.vogella.nattable.part package and point to it the Class URI field in the part using the Find button. Set the value com.vogella.nattable.part into the Package field and the value NatTableDataExamplePart into the Name field.

After this step the application model should look similar to the following screenshot.

nattable data example application model

5.9.6. Create the NatTable instance

Change the generated NatTableDataExamplePart class similar to the following listing.

package com.vogella.nattable.part;

import javax.annotation.PostConstruct;

import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ReflectiveColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;

import com.vogella.model.person.Person;
import com.vogella.model.person.PersonService;

public class NatTableDataExamplePart {

    @PostConstruct
    public void postConstruct(Composite parent, PersonService personService) {
        parent.setLayout(new GridLayout());

        // property names of the Person class
        String[] propertyNames = { "firstName", "lastName", "gender", "married", "birthday" };

        // create the data provider
        IColumnPropertyAccessor<Person> columnPropertyAccessor =
                new ReflectiveColumnPropertyAccessor<Person>(propertyNames);
        IDataProvider bodyDataProvider =
                new ListDataProvider<Person>(
                        personService.getPersons(10), columnPropertyAccessor);

        final DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);

        // use different style bits to avoid rendering of inactive scrollbars for small table
        // Note: The enabling/disabling and showing of the scrollbars is handled by the
        //       ViewportLayer. Without the ViewportLayer the scrollbars will always be
        //       visible with the default style bits of NatTable.
        final NatTable natTable = new NatTable(
                parent,
                SWT.NO_REDRAW_RESIZE | SWT.DOUBLE_BUFFERED | SWT.BORDER,
                bodyDataLayer);

        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
    }
}

The example uses the ReflectiveColumnPropertyAccessor that is created using a String array. It is used to create a ListDataProvider together with a list of Person`s that are retrieved by the `PersonService.

The layer composition in this example consists only of the DataLayer. It is therefore directly used to create the NatTable instance.

As there is no other layer involved, the created table does not support scrolling. Therefore this example uses style bits to avoid rendering scrollbars that never get active.

5.9.7. Run

Start the example application by opening the product definition file and clicking Launch an Eclipse application. The application should display a table showing the generated example data, similar to the following screenshot.

nattable data example

If you have problems starting your application, check the dependencies section of your product. It should contain the org.eclipse.nebula.widgets.nattable.core plug-in. Click the Add Required Plug-ins button if it is not included.

5.10. Exercise: NatTable data providing without reflection

5.10.1. Overview of the example

In the previous exercise Exercise: NatTable data providing a ReflectiveColumnPropertyAccessor is used to access the Person objects properties.

Concluding from the name reflection is used to access these Person objects properties, which results in a slightly less performance due to the relection overhead. In order to avoid this performance impact a custom IColumnPropertyAccessor<R> can be implemented for a Person object.

5.10.2. Implement a custom IColumnPropertyAccessor for a Person

The PersonColumnPropertyAccessor should be placed in a com.vogella.nattable.data package.

package com.vogella.nattable.data;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;

import com.vogella.model.person.Person;
import com.vogella.model.person.Person.Gender;

public class PersonColumnPropertyAccessor
    implements IColumnPropertyAccessor<Person> {

    private static final List<String> propertyNames =
        Arrays.asList("firstName", "lastName", "gender", "married", "birthday");

    @Override
    public Object getDataValue(Person person, int columnIndex) {
        switch (columnIndex) {
            case 0:
                return person.getFirstName();
            case 1:
                return person.getLastName();
            case 2:
                return person.getGender();
            case 3:
                return person.isMarried();
            case 4:
                return person.getBirthday();
            }
        return person;
    }

    @Override
    public void setDataValue(Person person, int columnIndex, Object newValue) {
        switch (columnIndex) {
            case 0:
                String firstName = String.valueOf(newValue);
                person.setFirstName(firstName);
                break;
            case 1:
                String lastName = String.valueOf(newValue);
                person.setLastName(lastName);
                break;
            case 2:
                person.setGender((Gender) newValue);
                break;
            case 3:
                person.setMarried((boolean) newValue);
                break;
            case 4:
                person.setBirthday((Date) newValue);
                break;
        }
    }

    @Override
    public int getColumnCount() {
        return 5;
    }

    @Override
    public String getColumnProperty(int columnIndex) {
        return propertyNames.get(columnIndex);
    }

    @Override
    public int getColumnIndex(String propertyName) {
        return propertyNames.indexOf(propertyName);
    }

}

Now instead of passing a ReflectiveColumnPropertyAccessor to the ListDataProvider of the NatTableDataExamplePart from Exercise: NatTable data providing the PersonColumnPropertyAccessor can be used.

5.10.3. Run

When running the application the NatTableDataExamplePart should look completely equal to the one before from Exercise: NatTable data providing, but without using reflection.

nattable data example

6. Layer

As explained in the architecture chapter Layers, a NatTable is created by using layers that can be stacked and composed. Every layer has its own functionality and encapsulates cell transformations if necessary.

The following is a list of layers that are shipped with NatTable.

Table 4. NatTable Layers

Layer

Description

DataLayer

Provides the data that is shown in a layer stack.

ViewportLayer

Responsible for adding and handling the virtual nature to a NatTable instance.

SelectionLayer

Adds the ability to perform selection actions.

HoverLayer

Adds the ability to change styling on hover.

ColumnHideShowLayer

Adds the ability to hide columns.

RowHideShowLayer

Adds the ability to hide rows.

GlazedListsRowHideShowLayer

Adds the ability to hide rows when GlazedLists are used to wrap the data model collection.

ColumnReorderLayer

Adds the ability to reorder columns.

RowReorderLayer

Adds the ability to reorder rows.

SortHeaderLayer

Adds the ability to sort columns by clicking on the column header.

FilterRowHeaderComposite

Adds a filter row between the column header and the body of a NatTable composition.

ColumnGroupHeaderLayer

Adds a second level column header to group columns.

ColumnGroupGroupHeaderLayer

Adds a third level column header to group columns and column groups.

ColumnGroupReorderLayer

Adds the ability to reorder column groups.

ColumnGroupExpandCollapseLayer

Adds the ability to expand and collapse column groups.

RowGroupHeaderLayer

Adds a second level row header to group rows.

SummaryRowLayer

Adds a summary row at the end of a NatTable instance.

FreezeLayer

Handles and stores states dependent on freezing columns and rows.

CompositeFreezeLayer

Composition to add freeze functionality.

TreeLayer

Transformation layer that shows the underlying data model collection in a tree representation.

BlinkLayer

Adds the ability to dynamically react on data model changes in the background.

GlazedListsEventLayer

Transforms list change events sent by GlazedLists into NatTable events to refresh rendering on list changes. GlazedLists events are conflated in a 100ms interval to reduce the amount of NatTable refresh events.

DetailGlazedListsEventLayer

Transforms list change events sent by GlazedLists into NatTable events to refresh rendering on list changes. Every GlazedLists event is transformed and fired to the NatTable layer stack, transporting the detail information about the list change, e.g. which rows have been deleted.

GroupByHeaderLayer

Adds a section on top were columns can be dragged to in order to dynamically group the data model elements.

GroupByDataLayer

Specialized DataLayer that needs to be used in order to make the groupBy feature work.

Additionally there are several layers that are used to create composites, like the CompositeLayer, GridLayer, RowHeaderLayer, ColumnHeaderLayer and the CornerLayer to name the most important ones. They will be explained in the following chapters.

6.1. Exercise: NatTable Layer Stack

6.1.1. Overview of the example

In this exercise you add a new part to the Eclipse RCP application you created in Exercise: NatTable data providing. It will display persons in a scrollable NatTable where cells can be selected. This exercise shows how to setup a basic layer stack in NatTable.

6.1.2. Add a new part to the application model

Create a new Part in the PartStack that was created in Exercise: NatTable data providing. Enter NatTable LayerStack Example in the Label field and create a new Part implementation by clicking on Class URI. Set the value com.vogella.nattable.part into the Package field and the value NatTableLayerStackExamplePart into the Name field. By clicking on Finish the class NatTableLayerStackExamplePart is created in the com.vogella.nattable.part package.

6.1.3. Create the NatTable instance

Change the NatTableLayerStackExamplePart class so it creates an IDataProvider that is able to handle a collection of Person objects. For this create a ListDataProvider using a ReflectiveColumnPropertyAccessor and a list of Person objects that is retrieved by the PersonService similar to Create the NatTable instance.

Build up a layer stack that consists of a DataLayer, a SelectionLayer and a ViewportLayer. You are doing this by creating the layer instances one by one, using the prior layer as underlyingLayer constructor parameter as shown in the code snippet below.

DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
SelectionLayer selectionLayer = new SelectionLayer(bodyDataLayer);
ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);

The default UI bindings for selection actions are bound to the GridRegion.BODY region label. As we have no grid composition in this exercise, we need to manually set the region label in order to make selections work correctly.

viewportLayer.setRegionName(GridRegion.BODY);

Create a NatTable instance for the created layer stack that uses the default configurations.

It is not necessary to set custom style bits, as now a ViewportLayer is used, which handles the scrollbars automatically.

package com.vogella.nattable.part;

import javax.annotation.PostConstruct;

import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ReflectiveColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
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.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;

import com.vogella.model.person.Person;
import com.vogella.model.person.PersonService;

public class NatTableLayerStackExamplePart {

    @PostConstruct
    public void postConstruct(
            Composite parent, PersonService personService) {

        parent.setLayout(new GridLayout());

        // property names of the Person class
        String[] propertyNames = {
                "firstName",
                "lastName",
                "gender",
                "married",
                "birthday" };

        // create the data provider
        IColumnPropertyAccessor<Person> columnPropertyAccessor =
                new ReflectiveColumnPropertyAccessor<Person>(propertyNames);
        IDataProvider bodyDataProvider =
                new ListDataProvider<Person>(
                        personService.getPersons(50),
                        columnPropertyAccessor);

        // build up a layer stack consisting of DataLayer, SelectionLayer and
        // ViewportLayer
        DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
        SelectionLayer selectionLayer = new SelectionLayer(bodyDataLayer);
        ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);

        // as the selection mouse bindings are registered for the region label
        // GridRegion.BODY
        // we need to set that region label to the viewport so the selection via mouse
        // is working correctly
        viewportLayer.setRegionName(GridRegion.BODY);

        NatTable natTable = new NatTable(parent, viewportLayer);

        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
    }
}

6.1.4. Run

Start the example application by opening the product definition file and clicking Launch an Eclipse application. The application should display two parts similar to the following screenshot. One part is showing the data example of the previous example, the other one is showing the layer stack example which is scrollable and supports cell selection.

nattable layerstack example

6.2. Layer compositions

Additionally to stacking up layers, it is possible to create compositions and arrange layer stacks side by side. Following the design principles in NatTable, this is achieved by using a CompositeLayer. A CompositeLayer is itself column and row based, where every part is called a region. To create a CompositeLayer you need to specify the number of columns and rows of regions. Afterwards the layers/layer stacks can be put in the CompositeLayer by calling CompositeLayer#setChildLayer(String, ILayer, int, int), where the first parameter specifies the region label.

// create a composition with one column and two rows
CompositeLayer compositeLayer = new CompositeLayer(1, 2);
// set the column header layer in the first row of the first column
compositeLayer.setChildLayer(GridRegion.COLUMN_HEADER, columnHeaderLayer, 0, 0);
// set the viewport layer in the second row of the first column
compositeLayer.setChildLayer(GridRegion.BODY, viewportLayer, 0, 1);

6.2.1. Dimensional dependencies

In a layer composition typically one region is leading in terms of cell boundaries. For example, in a composition that contains a body and a column header, the body is typically scrollable, so the column header cell boundaries need to follow the cell boundaries of the body cells.

Layers whose cell boundaries are attached to the cell boundaries of another layer are called dimensionally dependent. Every dimensionally dependent layer extends DimensionallyDependentLayer and needs to know the layer it depends on at creation time.

Layers can either be horizontally dependent (e.g. ColumnHeaderLayer), vertically dependent (e.g. RowHeaderLayer) or both horizontally and vertically dependent (e.g. CornerLayer).

6.2.2. Creating a grid composition

The GridLayer is a specialized CompositeLayer that has two rows and two columns. It therefore has four regions:

  • Corner (upper left region)

  • Column Header (upper right region)

  • Row Header (bottom left region)

  • Body (bottom right region)

6.2.3. GridLayer default configuration

The GridLayer contains several default configurations to specify behavior in a grid. These configuration are aggregated in DefaultGridLayerConfiguration.

Table 5. Default configurations
Configuration class Description

DefaultExportBindings

Adds the key binding for exporting a NatTable and configuration regarding the default export settings.

DefaultPrintBindings

Adds the key binding for printing a NatTable.

DefaultEditBindings

Adds the UI bindings for keys and mouse interactions regarding the editing behavior.

DefaultEditConfiguration

Registers the command handlers necessary for editing and adds default configurations like the default cell editor, the editable rule and the default data validator.

DefaultRowStyleConfiguration

Configures the styling for alternating row colors.

If you are building up a custom layer composition and notice that some functions are not working as intended, it is always a good idea to check if the necessary default configurations are added to the custom CompositeLayer.

6.2.4. GridLayer commands

The following section lists and describes the commands that are handled by the GridLayer.

Table 6. Commands
Command CommandHandler Description

ClientAreaResizeCommand

GridLayer

Performs additional transformations to correctly support percentage sizing.

PrintCommand

PrintCommandHandler

Prints the NatTable.

ExportCommand

ExportCommandHandler

Exports the NatTable to a Excel compatible format.

AutoResizeColumnsCommand

AutoResizeColumnCommandHandler

Performs an auto resize for a specified column.

AutoResizeRowsCommand

AutoResizeRowCommandHandler

Performs an auto resize for a specified row.

6.2.5. Layer stack of the body region

The body layer stack is the main part of a NatTable composition. Typically it handles selection and the virtual nature via viewport. Also additional layers for handling e.g. column reordering or column hide/show are added to this stack.

// build the body layer stack
IDataProvider bodyDataProvider
    = new ListDataProvider<Person>(personService.getPersons(50), columnPropertyAccessor);
DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
SelectionLayer selectionLayer = new SelectionLayer(bodyDataLayer);
ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);

6.2.6. Layer stack of the column header region

The column header layer stack is used to render the column header. It has a horizontal dimensional dependency to the body layer stack. This dependency is resolved using the ColumnHeaderLayer.

To create a ColumnHeaderLayer you need to specify the underlying base layer, which is typically the DataLayer for the column header region. The layer to resolve the dimensional dependency, is typically the ViewportLayer or a custom body layer stack that was encapsulated in a separate class, and the SelectionLayer which is necessary to be able to visualize cell selections in the column header.

To add additional functionality to the column header layer, it is of course possible to add further layers to the layer stack. For example, adding the SortHeaderLayer adds the ability to sort the content via clicking the column header cells. Using the ColumnGroupHeaderLayer adds a additional row on top of the column header for grouping columns.

The IDataProvider that is used to provide data to the column header layer stack needs to handle the same amount of columns as the IDataProvider of the body layer stack. Otherwise there will be runtime issues when trying to setup the horizontal dependency.

// build the column header layer stack
IDataProvider columnHeaderDataProvider =
    new DefaultColumnHeaderDataProvider(propertyNames, propertyToLabelMap);
DataLayer columnHeaderDataLayer = new DataLayer(columnHeaderDataProvider);
ILayer columnHeaderLayer = new ColumnHeaderLayer(
    columnHeaderDataLayer,
    viewportLayer,
    selectionLayer);

6.2.7. Layer stack of the row header region

The row header layer stack is used to render the row header. It has a vertical dimensional dependency to the body layer stack. This dependency is resolved using the RowHeaderLayer.

To create a RowHeaderLayer you need to specify the underlying base layer, which is typically the DataLayer for the row header region, the layer to resolve the dimensional dependency, typically the ViewportLayer or a custom body layer stack that was encapsulated in a separate class, and the SelectionLayer which is necessary to be able to visualize cell selections in the column header.

Using the DefaultRowHeaderDataProvider ties the number of rows to the body layer stack. It simply returns (row index+1) as content for cells in the row header. To change what information should be showed in the row header, use a different IDataProvider.

The IDataProvider that is used to provide data to the row header layer stack needs to handle the same amount of rows as the IDataProvider of the body layer stack. Otherwise there will be runtime issues when trying to setup the vertical dependency.

To add additional functionality to the row header layer, it is of course possible to add further layers to the layer stack. For example, using the RowGroupHeaderLayer adds a additional column to the left of the row header for grouping rows.

// build the row header layer stack
IDataProvider rowHeaderDataProvider = DefaultRowHeaderDataProvider(bodyDataProvider);
DataLayer rowHeaderDataLayer = new DataLayer(rowHeaderDataProvider, 40, 20);
ILayer rowHeaderLayer = new RowHeaderLayer(
    rowHeaderDataLayer,
    viewportLayer,
    selectionLayer);

6.2.8. Layer stack of the corner region

The corner layer stack is typically non functional. It needs to be there to fill the fourth remaining region of the grid. It is dimensionally dependent to both, the column header region and the row header region.

// build the corner layer stack
IDataProvider cornerDataProvider =
    new DefaultCornerDataProvider(
        columnHeaderDataProvider,
        rowHeaderDataProvider);
DataLayer cornerDataLayer = new DataLayer(cornerDataProvider);
ILayer cornerLayer = new CornerLayer(
    cornerDataLayer,
    rowHeaderLayer,
    columnHeaderLayer)

As the corner region is also built with a layer stack it is of course possible to add content or other layers to the stack.

6.3. Exercise: NatTable Layer Composition

6.3.1. Overview of the example

This exercise shows how to setup a layer composition with column header and body in a NatTable. You add a new part to the Eclipse RCP application you created in Exercise: NatTable data providing. This part displays persons in a scrollable NatTable where cells can be selected.

6.3.2. Add a new part to the application model

Create a new Part in the PartStack that was created in Exercise: NatTable data providing. Enter NatTable Composition Example in the Label field and create a new Part implementation by clicking on Class URI. Set the value com.vogella.nattable.part into the Package field and the value NatTableCompositionExamplePart into the Name field. By clicking on Finish the class NatTableCompositionExamplePart is created in the com.vogella.nattable.part package.

6.3.3. Create the NatTable instance

Change the NatTableCompositionExamplePart class so it creates an IDataProvider that is able to handle a collection of Person objects. For this create a ListDataProvider using a ReflectiveColumnPropertyAccessor and a list of Person objects that is retrieved by the PersonService similar to Create the NatTable instance.

Build up a layer stack that consists of a DataLayer, a SelectionLayer and a ViewportLayer. You are doing this by creating the layer instances one by one, using the prior layer as underlyingLayer constructor parameter as shown in the code snippet below.

DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
SelectionLayer selectionLayer = new SelectionLayer(bodyDataLayer);
ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);

Create an IDataProvider that is used for the column header region. Either create your own or use a existing default implementation like DefaultColumnHeaderDataProvider.

There are two ways to create a DefaultColumnHeaderDataProvider. Either specify a String array with the same size as the column specification, where every entry is the label for the column at the specified index. Or create a Map that contains a mapping from property name to label and provide it as parameter together with the String array of property names that was already used to create the ReflectiveColumnPropertyAccessor.

Build up a column header layer stack that consists of a DataLayer and a ColumnHeaderLayer. This should look similar to the following code snippet.

// build the column header layer stack
IDataProvider columnHeaderDataProvider =
    new DefaultColumnHeaderDataProvider(propertyNames, propertyToLabelMap);
DataLayer columnHeaderDataLayer = new DataLayer(columnHeaderDataProvider);
ILayer columnHeaderLayer = new ColumnHeaderLayer(
    columnHeaderDataLayer,
    viewportLayer,
    selectionLayer);

Create a CompositeLayer and set the column header layer stack and the body layer stack to the appropriate positions. The following code snippet gives an example on how this should look like.

// create a composition with one column and two rows
CompositeLayer compositeLayer = new CompositeLayer(1, 2);
// set the column header layer in the first row of the first column
compositeLayer.setChildLayer(GridRegion.COLUMN_HEADER, columnHeaderLayer, 0, 0);
// set the viewport layer in the second row of the first column
compositeLayer.setChildLayer(GridRegion.BODY, viewportLayer, 0, 1);

Create a NatTable instance for the created CompositeLayer that uses the default configurations.

package com.vogella.nattable.part;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ReflectiveColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;

import com.vogella.model.person.Person;
import com.vogella.model.person.PersonService;

public class NatTableCompositionExamplePart {

    @PostConstruct
    public void postConstruct(Composite parent, PersonService personService) {
        parent.setLayout(new GridLayout());

        // property names of the Person class
        String[] propertyNames = {
                "firstName",
                "lastName",
                "gender",
                "married",
                "birthday" };

        // mapping from property to label, needed for column header labels
        Map<String, String> propertyToLabelMap = new HashMap<String, String>();
        propertyToLabelMap.put("firstName", "Firstname");
        propertyToLabelMap.put("lastName", "Lastname");
        propertyToLabelMap.put("gender", "Gender");
        propertyToLabelMap.put("married", "Married");
        propertyToLabelMap.put("birthday", "Birthday");

        IColumnPropertyAccessor<Person> columnPropertyAccessor =
                new ReflectiveColumnPropertyAccessor<Person>(propertyNames);

        // build the body layer stack
        IDataProvider bodyDataProvider =
                new ListDataProvider<Person>(
                        personService.getPersons(50),
                        columnPropertyAccessor);
        DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
        SelectionLayer selectionLayer = new SelectionLayer(bodyDataLayer);
        ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);

        // build the column header layer stack
        IDataProvider headerDataProvider =
                new DefaultColumnHeaderDataProvider(propertyNames, propertyToLabelMap);
        DataLayer headerDataLayer =
                new DataLayer(headerDataProvider);
        ILayer columnHeaderLayer =
                new ColumnHeaderLayer(headerDataLayer, viewportLayer, selectionLayer);

        // create the composition
        // set the region labels to make default configurations work, e.g. selection
        CompositeLayer compositeLayer = new CompositeLayer(1, 2);
        compositeLayer.setChildLayer(GridRegion.COLUMN_HEADER, columnHeaderLayer, 0, 0);
        compositeLayer.setChildLayer(GridRegion.BODY, viewportLayer, 0, 1);

        NatTable natTable = new NatTable(parent, compositeLayer);

        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
    }
}

6.3.4. Run

Start the example application by opening the product definition file and clicking Launch an Eclipse application. The application should display three parts similar to the following screenshot. One showing the data example, one showing the layer stack example which is scrollable and one that shows the scrollable composition of column header and body.

nattable composition example

6.4. Exercise: NatTable Layer Composition

6.4.1. Overview of the example

This exercise shows how to setup a grid layer composition in NatTable. You add a new part to the Eclipse RCP application you created in Exercise: NatTable data providing. The new part displays persons in a typical grid composition.

6.4.2. Add a new part to the application model

Create a new Part in the PartStack that was created in Exercise: NatTable data providing. Enter NatTable Grid Example in the Label field and create a new Part implementation by clicking on Class URI. Set the value com.vogella.nattable.part into the Package field and the value NatTableGridExamplePart into the Name field. By clicking on Finish the class NatTableGridExamplePart will be created in the com.vogella.nattable.part package.

6.4.3. Create the NatTable instance

Change the NatTableGridExamplePart to add a NatTable grid composition that is able to handle a collection of Person objects. For this create a ListDataProvider using a ReflectiveColumnPropertyAccessor and a list of Person instances that is retrieved by the PersonService similar to Create the NatTable instance.

Build up a layer stack that consists of a DataLayer, a SelectionLayer and a ViewportLayer. You are doing this by creating the layer instances one by one, using the prior layer as underlyingLayer constructor parameter as shown in the below.

DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
SelectionLayer selectionLayer = new SelectionLayer(bodyDataLayer);
ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);

Build up a column header layer stack that consists of a DataLayer and a ColumnHeaderLayer. This should look similar to the following code snippet.

// build the column header layer stack
IDataProvider columnHeaderDataProvider =
    new DefaultColumnHeaderDataProvider(propertyNames, propertyToLabelMap);
DataLayer columnHeaderDataLayer = new DataLayer(columnHeaderDataProvider);
ILayer columnHeaderLayer = new ColumnHeaderLayer(
    columnHeaderDataLayer,
    viewportLayer,
    selectionLayer);

Build up a row header layer stack that consists of DataLayer and RowHeaderLayer. This should look similar to the following code snippet.

// build the row header layer stack
IDataProvider rowHeaderDataProvider = DefaultRowHeaderDataProvider(bodyDataProvider);
DataLayer rowHeaderDataLayer = new DataLayer(rowHeaderDataProvider, 40, 20);
ILayer rowHeaderLayer = new RowHeaderLayer(
    rowHeaderDataLayer,
    viewportLayer,
    selectionLayer);

Build up a corner layer stack that consists of DataLayer and CornerLayer. This should look similar to the following code snippet.

// build the corner layer stack
IDataProvider cornerDataProvider =
    new DefaultCornerDataProvider(
        columnHeaderDataProvider,
        rowHeaderDataProvider);
DataLayer cornerDataLayer = new DataLayer(cornerDataProvider);
ILayer cornerLayer = new CornerLayer(
    cornerDataLayer,
    rowHeaderLayer,
    columnHeaderLayer)

Build up a GridLayer using the prior created layer stacks in the correct positions, similar to the following code snippet.

// create the grid layer composed with
// the prior created layer stacks
GridLayer gridLayer = new GridLayer(
        viewportLayer,
        columnHeaderLayer,
        rowHeaderLayer,
        cornerLayer);

Create a NatTable instance for the created GridLayer that uses the default configurations.

package com.vogella.nattable.part;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ReflectiveColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;

import com.vogella.model.person.Person;
import com.vogella.model.person.PersonService;

public class NatTableGridExamplePart {

    @PostConstruct
    public void postConstruct(Composite parent, PersonService personService) {
        parent.setLayout(new GridLayout());

        // property names of the Person class
        String[] propertyNames = {
                "firstName",
                "lastName",
                "gender",
                "married",
                "birthday" };

        // mapping from property to label, needed for column header labels
        Map<String, String> propertyToLabelMap = new HashMap<String, String>();
        propertyToLabelMap.put("firstName", "Firstname");
        propertyToLabelMap.put("lastName", "Lastname");
        propertyToLabelMap.put("gender", "Gender");
        propertyToLabelMap.put("married", "Married");
        propertyToLabelMap.put("birthday", "Birthday");

        IColumnPropertyAccessor<Person> columnPropertyAccessor =
                new ReflectiveColumnPropertyAccessor<Person>(propertyNames);

        // build the body layer stack
        IDataProvider bodyDataProvider =
                new ListDataProvider<Person>(
                        personService.getPersons(50),
                        columnPropertyAccessor);
        DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
        SelectionLayer selectionLayer = new SelectionLayer(bodyDataLayer);
        ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);

        // build the column header layer stack
        IDataProvider columnHeaderDataProvider =
                new DefaultColumnHeaderDataProvider(propertyNames, propertyToLabelMap);
        DataLayer columnHeaderDataLayer =
                new DataLayer(columnHeaderDataProvider);
        ILayer columnHeaderLayer =
                new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);

        // build the row header layer stack
        IDataProvider rowHeaderDataProvider =
                new DefaultRowHeaderDataProvider(bodyDataProvider);
        DataLayer rowHeaderDataLayer =
                new DataLayer(rowHeaderDataProvider, 40, 20);
        ILayer rowHeaderLayer =
                new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);

        // build the corner layer stack
        ILayer cornerLayer = new CornerLayer(
                new DataLayer(
                        new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider)),
                rowHeaderLayer,
                columnHeaderLayer);

        // create the grid layer composed with the prior created layer stacks
        GridLayer gridLayer =
                new GridLayer(viewportLayer, columnHeaderLayer, rowHeaderLayer, cornerLayer);

        NatTable natTable = new NatTable(parent, gridLayer);

        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
    }

}

6.4.4. Run

Start the example application by opening the product definition file and clicking Launch an Eclipse application. The application should now display four parts similar to the following screenshot. One showing the data example, one showing the layer stack example which is scrollable, one that shows the scrollable composition of column header and body and the newly created grid example.

In a grid the headers typically don’t move while scrolling the body region. And because of the default configuration of the GridLayer it is possible to print Ctrl+P and export Ctrl+E the NatTable instance.

nattable grid example

7. About this website

Nothing listed.

8.1. vogella GmbH training and consulting support

TRAINING SERVICE & SUPPORT

The vogella company provides comprehensive training and education services from experts in the areas of Eclipse RCP, Android, Git, Java, Gradle and Spring. We offer both public and inhouse training. Whichever course you decide to take, you are guaranteed to experience what many before you refer to as “The best IT class I have ever attended”.

The vogella company offers expert consulting services, development support and coaching. Our customers range from Fortune 100 corporations to individual developers.

Copyright © 2012-2016 vogella GmbH. Free use of the software examples is granted under the terms of the EPL License. This tutorial is published under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Germany license.

See Licence.