Home Tutorials Training Consulting Books Company Contact us


Get more...

This tutorial shows how to deal with NatTable states and persisting them.

This tutorial is based on Nebula NatTable 1.5.0.

1. State Persistence

NatTable supports persisting and restoring its state. This is done via implementations of the IPersistable interface. Every class that implements this interface needs to provide methods for saving and loading the states it provides. These methods are:

void saveState(String, Properties)
void loadState(String, Properties)

Looking at the method parameters you will notice that the persistence mechanism in NatTable is implemented by using java.util.Properties. Therefore every state needs to be persisted as a key-value-pair, where values need to be Strings. This first parameter is the state key prefix, which can be used to handle multiple state sets in one Properties object. The second parameter is the Properties object to which the states should be added.

All layers that support saving their states implement the IPersistable interface by default. It is also possible to register custom IPersistable implementations with any layer using ILayer#registerPersistable(). These implementations will also be handled automatically on saving and loading NatTable states.

The following picture shows the process on saving a state with default layer and custom IPersistable implementations.

persistable
Figure 1. Persisting a NatTable state

Settings that can be persisted using the persistence mechanism in NatTable include, but are not limited to the following:

  • Visible state of columns (ColumnHideShowLayer)

  • Column order (ColumnReorderLayer)

  • Column/Row size (DataLayer)

  • Sorting state (SortHeaderLayer)

  • Freeze state (CompositeFreezeLayer)

  • Column group state (ColumnGroupModel)

  • Filter row state (FilterRowDataProvider)

  • Styles that are applied using the DisplayColumnStyleEditor

The persistence mechanism will only gather the states in memory, within the provider Properties object. If these settings should be persisted in the file system or a database, this needs to be implemented additionally. The NatTable class also implements the IPersistable interface to trigger the state processing.

A typical code block for saving a NatTable state to a file could look similar to the following code snippet:

Storing NatTable state to a file
Properties myState = new Properties();
natTable.saveState("", myState);
try (FileOutputStream tableStateStream =
        new FileOutputStream("myState.properties")) {
    myState.store(tableStateStream, null);
} catch (IOException e) {
    e.printStackTrace();
}

To support multiple state sets that can be even changed at runtime, there are two options. . either create a new Properties object for every state that should be persisted . make use of the prefix mechanism

The first approach supports a clean separation of state sets even on the file level. The disadvantage is that this leaves you to deal with the state management yourself.
The second approach will manage all state sets within one Properties object. The separation of state sets is done via state prefixes as explained initially. The state prefix will be prepended to every key that is stored by calling IPersistable#saveState(String, Properties) or loaded via IPersistable#loadState(String, Properties).

When adding additional states by implementing the IPersistable interface, you have to ensure that the state keys start with a dot. This is required to work in combination with state prefixes. Otherwise there may be issues in processing state persistence.

2. View Configuration Management

Since Nebula NatTable 1.0.0 there is a convenience persistence dialog for managing multiple state sets. In this context state sets are called view configurations, which is a more user friendly notation than the more technical state set. The persistence dialog mainly supports creating and loading view configurations and can be added to a NatTable by adding the appropriate menu item and command handler to a menu. The following code will add the menu item to the column header menu and the command handler for opening the dialog to the grid layer of the NatTable instance:

Register the command handler and add the menu item to add view configuration functionality to a NatTable
// add the necessary action to the column header
// menu of the default HeaderMenuConfiguration
natTable.addConfiguration(
    new HeaderMenuConfiguration(natTable) {
        @Override
        protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
            return super.createColumnHeaderMenu(natTable)
                        .withStateManagerMenuItemProvider();
        }
});

// register the necessary command handler
// to the grid layer of the NatTable instance
DisplayPersistenceDialogCommandHandler handler =
    new DisplayPersistenceDialogCommandHandler();
gridLayer.registerCommandHandler(handler);

After selecting the menu item out of the column header menu a dialog will show up, giving the user the opportunity to save the current active state set as a new view configuration with a custom name. This view configuration can be reloaded afterwards or even deleted, to give a user the flexibility to change the view of a table on simple clicks. The list of view configuration will also indicate the current active view configuration with a leading asterisk �*�.

persistence dialog
Figure 2. PersistenceDialog for managing view configurations

The dialog does not support choosing an empty name for a view configuration. This is related to the prefixing and a supported convenience functionality. If you create the DisplayPersistenceDialogCommandHandler instance with the NatTable instance it should be attached to as parameter, the initial view configuration without any user modifications will be stored as the default configuration. This default configuration is not editable by the user and provides the functionality to always be able to return to the initial default view configuration.

To make use of storing the default configuration in the view configuration management, you need to register the DisplayPersistenceDialogCommand with the current NatTable instance AFTER calling NatTable#configure() on the NatTable instance. This is necessary as otherwise some state information might be missing and it could cause some unexpected behavior if the registering is done before.

To open the dialog in another way than by a header menu, it is also possible to execute the DisplayPersistenceDialogCommand via NatTable#doCommand() programmatically. This enables you for example to add a command to the toolbar of your application that opens the dialog, rather than a popup menu on the table itself.

Open the PersistenceDialog programmatically
Button viewConfigButton = new Button(buttonPanel, SWT.PUSH);
viewConfigButton.setText("View Configuration");
viewConfigButton.addSelectionListener(new SelectionAdapter() {
    @Override
    public void widgetSelected(SelectionEvent e) {
        natTable.doCommand(new DisplayPersistenceDialogCommand(natTable));
    }
});

It is also possible to listen for changes on view configurations within the PersistenceDialog, like adding, removing or changing a view configuration. To do this you need to register listeners of type IStateChangeListener to the DisplayPersistenceDialogCommandHandler. On opening a new dialog, the listeners will be registered to the dialog, so you can be informed about view configuration changes outside the dialog.

Add a listener for listening to view configuration changes
DisplayPersistenceDialogCommandHandler handler =
    new DisplayPersistenceDialogCommandHandler(natTable);
handler.addStateChangeListener(new IStateChangedListener() {
    @Override
    public void handleStateChange(StateChangeEvent event) {
        //do something on state change
    }
});

There is also API provided to manage view configurations programmatically. The PersistenceHelper class helps on retrieving the view configuration names and deleting a view configuration out of a Properties instance. This can be used for example to implement a custom dialog for view configuration management, or to for example implement a combobox to easily switch between created view configurations.

The following snippet shows how an implementation of a combobox for switching between view configurations could look like.

Combobox to switch between view configurations directly
Composite toolbar = new Composite(panel, SWT.NONE);

// create the command handler with a Properties object
Properties natTableState = new Properties();
DisplayPersistenceDialogCommandHandler handler =
        new DisplayPersistenceDialogCommandHandler(natTableState, natTable);
gridLayer.registerCommandHandler(handler);

// create a combobox for showing the available view states
Combo viewStates = new Combo(toolbar, SWT.DROP_DOWN);
viewStates.setItems(PersistenceHelper
        .getAvailableStates(natTableState)
        .toArray(new String[] {}));
viewStates.addSelectionListener(new SelectionAdapter() {

    @Override
    public void widgetSelected(SelectionEvent e) {
        int index = viewStates.getSelectionIndex();
        if (index >= 0) {
            String selected = viewStates.getItem(index);
            // load the state
            natTable.loadState(selected, natTableState);
        }
    }
});

// add listener to update the combo on view state management changes
handler.addStateChangeListener(new IStateChangedListener() {

    @Override
    public void handleStateChange(StateChangeEvent event) {
        viewStates.setItems(PersistenceHelper
                .getAvailableStates(natTableState)
                .toArray(new String[] {}));
    }
});

// add button to show dialog
Button manage = new Button(toolbar, SWT.PUSH);
manage.setText("View Management");
manage.addSelectionListener(new SelectionAdapter() {
    @Override
    public void widgetSelected(SelectionEvent e) {
        natTable.doCommand(new DisplayPersistenceDialogCommand(natTable));
    }
});