Table of Contents

NatTable

A table is created as following:

  1. create a BodyLayerStack. Each layer is based on previous.
  2. create a RowHeaderLayer - it will provide rownames
  3. create a ColumnNameLayer - it will provide columnnames
  4. create a CornerLayer - to add some functionality to the left, upper table corner
  5. assemble all layers in one GridLayer

The functionality is contributed in BodyLayerStack.

FallPits

      // 1. Use the dataProvider, which is the DataSource
        dataLayer = new DataLayer(bodyDataProvider);
        // 2.
        ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer);
        // 3. disables some columns
        ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(dataLayer);
        // 4. can handle selection
        SelectionLayer selectionLayer = new SelectionLayer(dataLayer);
        // 5. draw Scrollbars
        ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);

        viewportLayer.setRegionName(GridRegion.BODY); // set t

        // column names
//        DataLayer dataLayerColHeader = new DataLayer(this.columnDataProvider);
        // REFLECTIVE column provider
        DataLayer dataLayerColHeader = new DataLayer(columnDataProviderExt);
        ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(dataLayerColHeader, viewportLayer, selectionLayer);

        // row names.
        // DEMO of encapsulating layers and configs in an "AbstractLayerTransform"
        RowHeaderLayerStack rowHeaderLayer = new RowHeaderLayerStack(this.rowDatadataProvider, viewportLayer,

Switching between Position and INdex is possible via the nattable

// position -> index
natTable.getColumnIndexByPosition(columnPosition)
natTable.getRowIndexByPosition(rowPosition) 

Inserting own Data

To insert own data into the table - implement IDataProvider. IDataProvider asks for Data by providing the

  1. row
  2. column

Usualy own Data are stored in a Collection of own Objects. So the rows are mapped to the collection cells, and the right object is retrieved, which should be mapped to cells.

Now somehow the own-object's properties should be mapped to columns:

Access own DataObject by the Reflection.

There is a reflective way of accessing the objects, by using ReflectiveColumnPropertyAccessor. It finds the getter methods on its own, if it gets a list of attributes, to display.

//map the labels to attributes names
IDataProvider dataProvider = new ListDataProvider<Person>(List<OwnObject> ownObjectList, new ReflectiveColumnPropertyAccessor(propertyNames));

Concepts

Subject Describtion
IConfigRegistry configRegistry An object, that is registered to NatTables. This Object is passed around and collects the Table Configurations from all Layers.
To register the data to a configRegistry use the CellConfigAttributes:

configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, new CheckBoxPainter(), DisplayMode.NORMAL, CHECK_BOX_CONFIG_LABEL);
configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new CheckBoxCellEditor(), DisplayMode.NORMAL, CHECK_BOX_EDITOR_CONFIG_LABEL);    

IConfiguration An object, that is registered to ILayers. is is asked to add Configurations like Events, Style etc. to the IConfigRegistry
AggregateConfiguration An IConfiguration that stores many IConfiguration objects. Used to put many Configurations E.g. for Edit, head, print, styling etc. in 1 object. Use addConfiguration() in constructor, when subclass this class.
Labels Labels are registered to Cells, Columns, Rows. They allow to map the Configurations from ConfigRegistry to cells, columns, rows.

//associate cellStyle with Label GridRegion.ROW_HEADER


		Style cellStyle = new Style();
		cellStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, bgColor);
		cellStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, fgColor);
		cellStyle.setAttributeValue(CellStyleAttributes.GRADIENT_BACKGROUND_COLOR, gradientBgColor);
		cellStyle.setAttributeValue(CellStyleAttributes.GRADIENT_FOREGROUND_COLOR, gradientFgColor);
		cellStyle.setAttributeValue(CellStyleAttributes.HORIZONTAL_ALIGNMENT, hAlign);
		cellStyle.setAttributeValue(CellStyleAttributes.VERTICAL_ALIGNMENT, vAlign);
		cellStyle.setAttributeValue(CellStyleAttributes.BORDER_STYLE, borderStyle);
		cellStyle.setAttributeValue(CellStyleAttributes.FONT, font);

		configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, cellStyle, DisplayMode.NORMAL, GridRegion.ROW_HEADER);

UiBindingRegistry An object, that is registered to NatTables. This Object is passed around, like IConfigRegistry and collects the Mouse events, and other user actions.
To register the data to a configRegistry use the Attributes:

uiBindingRegistry.registerFirstSingleClickBinding(
		new CellPainterMouseEventMatcher(GridRegion.COLUMN_HEADER, MouseEventMatcher.LEFT_BUTTON, columnHeaderCheckBoxPainter),
		new ToggleCheckBoxColumnAction(columnHeaderCheckBoxPainter, bodyDataLayer)
);


//enables row resizing 
public void configureUiBindings(UiBindingRegistry uiBindingRegistry) {
	// Mouse move - Show resize cursor
	uiBindingRegistry.registerFirstMouseMoveBinding(new RowResizeEventMatcher(SWT.NONE, 0), new RowResizeCursorAction());
	uiBindingRegistry.registerMouseMoveBinding(new MouseEventMatcher(), new ClearCursorAction());
	
	// Row resize
	uiBindingRegistry.registerFirstMouseDragMode(new RowResizeEventMatcher(SWT.NONE, 1), new RowResizeDragMode());

	uiBindingRegistry.registerDoubleClickBinding(new RowResizeEventMatcher(SWT.NONE, 1), new AutoResizeRowAction());
	uiBindingRegistry.registerSingleClickBinding(new RowResizeEventMatcher(SWT.NONE, 1), new NoOpMouseAction());
}

CellStyleAttributes - Objects encapsulate the Labels, which are used to register functionality via labels, or otherwise. That objects have a generic parameter and contribute a generic type, so that only objects of the right type may be regitered with the given CellStyleAttribute.

CellAttributes collection What maay be registered?
CellConfigAttributes
  • CELL_PAINTER
  • CELL_STYLE
  • DISPLAY_CONVERTER
  • EXPORT_FORMATTER

configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, new CheckBoxPainter(), DisplayMode.NORMAL, CHECK_BOX_CONFIG_LABEL);

EditConfigAttributes
  • CELL_EDITABLE_RULE
  • CELL_EDITOR
  • DATA_VALIDATOR
  • CONVERSION_ERROR_HANDLER
  • VALIDATION_ERROR_HANDLER
  • VALIDATION_ERROR_STYLE

configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new CheckBoxCellEditor(), DisplayMode.NORMAL, CHECK_BOX_EDITOR_CONFIG_LABEL);

CellConfigAttributes
  • BACKGROUND_COLOR
  • FOREGROUND_COLOR
  • GRADIENT_BACKGROUND_COLOR
  • GRADIENT_FOREGROUND_COLOR
  • HORIZONTAL_ALIGNMENT
  • VERTICAL_ALIGNMENT
  • FONT
  • IMAGE
  • BORDER_STYLE
  • PASSWORD_ECHO_CHAR

IStyle cellStyle = new Style();
cellStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED);
configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, getStyle(), CHECK_BOX_CONFIG_LABEL);

useful API

API Describtion
ColumnResizeCommand Can resize a column.

                ColumnResizeCommand columnCommand = new ColumnResizeCommand(gridLayer, 3, 300);
                natTable.doCommand(columnCommand);

IDisplayConverter Can convert one kind of data into another, e.g. technical date into dd,mm,yy format.

 private IConfigRegistry configRegistry =  natTable.getConfigRegistry();
configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER,
                        new  DefaultDateDisplayConverter(), DisplayMode.NORMAL, getColumnLabel(columnNumber));

CompositeLayer Can display display two independent table parts near each other. E.g. a Table and near it a vertically dependent table. Used to paint Headers.


 ILayer createDependentCompositeLayer(ILayer verticallyDependFrom){
    	IUniqueIndexLayer baseLayer = getDependedLayer();
    	
    	CompositeLayer compositeLayer = new CompositeLayer(2, 1);

		DimensionallyDependentLayer testLayer = new DimensionallyDependentLayer(baseLayer, baseLayer, verticallyDependFrom);

		compositeLayer.setChildLayer("mainContent", verticallyDependFrom, 1, 0);
		compositeLayer.setChildLayer("testLayer", testLayer, 0, 0); //$NON-NLS-1$
    	
    	return compositeLayer;
    }
    
    IUniqueIndexLayer getDependedLayer(){
		return new DataLayer(new IDataProvider() {
			@Override
			public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
			}
			@Override
			public int getRowCount() {
				 // Is never used, because this layer is used to create a vertically depending layer.
				 // The rowCount is set by the layer, from which the current layer vertically depends.
				return 1000000;
			}
			@Override
			public Object getDataValue(int columnIndex, int rowIndex) {
				return "depend";
			}
			@Override
			public int getColumnCount() {
				return 1;
			}
		});
    }

DimensionallyDependentLayer Can make make the cell amount equal to the cell number of the layer it depends from. Adopts the cell Size too. Adopts the number of Cells too.

    	IUniqueIndexLayer baseLayer = getDependedLayer();  	
	DimensionallyDependentLayer testLayer = new DimensionallyDependentLayer(baseLayer, baseLayer, verticallyDependFrom);
 
    IUniqueIndexLayer getDependedLayer(){
		return new DataLayer(new IDataProvider() {
			@Override
			public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
			}
			@Override
			public int getRowCount() {
				 // Is never used, because this layer is used to create a vertically depending layer.
				 // The rowCount is set by the layer, from which the current layer vertically depends.
				return 1000000;
			}
			@Override
			public Object getDataValue(int columnIndex, int rowIndex) {
				return "depend";
			}
			@Override
			public int getColumnCount() {
				return 1;
			}
		});
    }

AbstractLayerTransform Can be used to extract a group of layers to an object .


    private class RowHeaderLayerStack extends AbstractLayerTransform {

        public RowHeaderLayerStack(IDataProvider dataProvider, ILayer bodyDataLayer, SelectionLayer selectionLayer) {
            DataLayer dataLayer = new DataLayer(dataProvider, 50, 20);
            RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(dataLayer, bodyDataLayer, selectionLayer);
            setUnderlyingLayer(rowHeaderLayer);
        }
    }
 

EditCellCommand EditCellCommandHandler

Commands are passed through all layers top down. Default commands are handled by handlers, many of them are owned by the GridLayer.

//Layers can execute commands and pass them to sublayers
Layer.doCommand(ILayerCommand command)

/*
/  EDITING HAPPENS AS FOLLOWING:
*/

//register an Editor to the cell's label
configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, cellEditor,
                            DisplayMode.EDIT, configLabelForCell);

// make the cell, which we registered an editor for - editable
configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, IEditableRule.ALWAYS_EDITABLE, DisplayMode.EDIT, configLabelForCell);

//register a mouse event, which will trigger editing (here it is a the first doubleclick)
uiBindingRegistry.registerFirstDoubleClickBinding(new BodyCellEditorMouseEventMatcher(TextCellEditor.class), new MouseEditAction());

//FIRST DOUBLE CLICK occurs

//ask, whether the event with found editor matches requirements for editing: BodyCellEditorMouseEventMatcher.matches(...)
//if yes - trigger an EditCellCommand

//ask, EditCellCommandHandler in GridLayer to Handle the EditCellCommand

//pass editing to the registered editor in cell

natTable.getConfigLabelsByPosition(columnPosition, rowPosition)
natTable.getDataValueByPosition(columnPosition, rowPosition)
natTable.getColumnIndexByPosition(columnPosition)
natTable.getRowIndexByPosition(rowPosition) 

Methodsavailable directly in nattable
Classname Comment.


 

Header Size

Header-Height depends on the TextPainter height.
Header-Width depends on body width.
See the diagram below.


	/**
	 * The Column Resizing has to happen AFTER the Column was rendered. For that
	 * a Paintlistener is used, which will unregister itselfe.
	 * 
	 * Details: http://www.eclipse.org/nattable/documentation.php?page=faq
	 */
	private void delayedColumnResize() {
		natTable.addListener(SWT.Paint, new Listener() {

			@Override
			public void handleEvent(Event arg0) {
				for (int i = 0; i < natTable.getPreferredColumnCount(); i++) {
					InitializeAutoResizeColumnsCommand columnCommand = new InitializeAutoResizeColumnsCommand(
							natTable, i, natTable.getConfigRegistry(),
							new GCFactory(natTable));
					natTable.doCommand(columnCommand);
				}

				natTable.removeListener(SWT.Paint, this);
			}
		});
	}

private void addNormalModeStyling(IConfigRegistry configRegistry) {
		Image bgImage = CommonResourceUtil.getImage(COLUMN_HEADER_BG_PATH);

		// Painter of headers
		// + Padding
		// + make cell as high/wide as content
		TextPainter txtPainter = new TextPainter(false, false,
				Constants.TABLE_PADDING_COLUMNHEADER, true, true);

		// Header BG
		ICellPainter bgImagePainter = new BackgroundImagePainter(txtPainter,
				bgImage, BG_COLOR);

		SortableHeaderTextPainter sortableHeaderTextPainter = new SortableHeaderTextPainter(
				bgImagePainter, false, true);

		configRegistry.registerConfigAttribute(
				CellConfigAttributes.CELL_PAINTER, sortableHeaderTextPainter,
				DisplayMode.NORMAL, GridRegion.COLUMN_HEADER);

		configRegistry.registerConfigAttribute(
				CellConfigAttributes.CELL_PAINTER, sortableHeaderTextPainter,
				DisplayMode.NORMAL, GridRegion.CORNER);

		// the top filter row in the corner should have the same BG color
		configRegistry.registerConfigAttribute(
				CellConfigAttributes.CELL_PAINTER, sortableHeaderTextPainter,
				DisplayMode.NORMAL, IdsNattable.LABEL_CORNER_ROW0);
	}



Styling

The styling may happen via different mechanisms:

Style

The cellStyle may style cells or headers, since headers are cells too.

Style cellStyle = new Style();
BACKGROUND_COLOR
FOREGROUND_COLOR
GRADIENT_BACKGROUND_COLOR
GRADIENT_FOREGROUND_COLOR
HORIZONTAL_ALIGNMENT
VERTICAL_ALIGNMENT
FONT
IMAGE
BORDER_STYLE
PASSWORD_ECHO_CHAR
TEXT_DECORATION

The cellStyle is registered to different DisplayModes, may become active when the cell is Selected, Edited or just viewed.

configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE,
				cellStyle, DisplayMode.NORMAL, GridRegion.COLUMN_HEADER);
		configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE,
				cellStyle, DisplayMode.NORMAL, GridRegion.CORNER);
		configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE,
				cellStyle, DisplayMode.NORMAL, GridRegion.ROW_HEADER);

Painter

The Painter may draw BG or forbid to draw bg.
When renderer forbids t odraw BG - no registered style is used:

		// no wrap
		// + DONT draw BG
		// + Padding
		// + make cell as high/wide as content & spacing
		ICellPainter txtPainter = new TextPainter(false, false,
				TABLE_PADDING_COLUMNHEADER, true);

		// no wrap
		// + DRAW BG
		// + Padding
		// + make cell as high/wide as content & spacing
		ICellPainter txtPainter = new TextPainter(false, true,
				TABLE_PADDING_COLUMNHEADER, true);

Style resolution mechanism

Which Style wins, if you register 2 different Styles to different Labels, which are assigned to the same cell?

DisplayMode Priority

The order (and priority in which Labels are evaluated). Definde in DefaultDisplayModeOrdering:

SELECT_HOVER, SELECT, HOVER, NORMAL

All styles from the current mode up to NORMAL-DisplayMode-Labels registered styles are considered.

E.g.

Label Priority

The mechanism is encapsulated inside:

package org.eclipse.nebula.widgets.nattable.style;

class CellStyleUtil{
...

    public static IStyle getCellStyle(ILayerCell cell,
            IConfigRegistry configRegistry) {
        return new CellStyleProxy(configRegistry, cell.getDisplayMode(), cell.getConfigLabels().getLabels());
    }

The important part is the

cell.getConfigLabels().getLabels()

which retrieves the labels in a special order X. This order decides, which Style is chosen.

  1. The Class StyleProxy iterates all labels, in the given order X, when it is asked for a special Style-Attribute A. First label, for which there is an instance of Style-Attribute A in configRegistry - wins the race.
  2. if no Style-Attribute A for no cell labels, then there is a fallback to a none label request.

package org.eclipse.nebula.widgets.nattable.style;

class StyleProxy{
...
    public <T> T getAttributeValue(ConfigAttribute<T> styleAttribute) {
    ...
       for (String displayMode : displayModeOrdering
                .getDisplayModeOrdering(this.targetDisplayMode)) {
            for (String configLabel : this.configLabels) {
                IStyle cellStyle = this.configRegistry.getSpecificConfigAttribute(
                        this.styleConfigAttribute, displayMode, configLabel);
                if (cellStyle != null) {
                    styleAttributeValue = cellStyle
                            .getAttributeValue(styleAttribute);
                    if (styleAttributeValue != null) {
                        return styleAttributeValue;
                    }
                }
            }

            // default
            IStyle cellStyle = this.configRegistry.getSpecificConfigAttribute(
                    this.styleConfigAttribute, displayMode, null);
            if (cellStyle != null) {
                styleAttributeValue = cellStyle
                        .getAttributeValue(styleAttribute);
                if (styleAttributeValue != null) {
                    return styleAttributeValue;
                }
            }
        }

        return null;
    }

Sorting

Sorting is implemented via ISortModel. It is passed to the SortHeaderLayer

import org.eclipse.nebula.widgets.nattable.sort.ISortModel;
ISortModel tableSorter = new ISortModel{
 ...
}

public TableHeaderStack() {

            // 0. datalayer. stores the headers of columns
            dataLayerColHeader = new DataLayer(columnDataProvider);

            // 1. column header layer. stores the headers
            columnHeaderLayer = new ColumnHeaderLayer(dataLayerColHeader, bodyStack.viewportLayer,
                    bodyStack.selectionLayer);

            // 2. sorting layer handles SortCommands. it is placed below the column header layer.
            // it handles the SortCommands by manipulating the ISortModel
            // it handles the SortCommands by adding sort labels to sorted columns
            sortHeaderLayer = new SortHeaderLayer(columnHeaderLayer, tableSorter, false);

            // create filterRow composite - the widget where filter text is entered
            filterRowHeaderComposite = createFilterRow(sortHeaderLayer, columnDataProvider);
        }

Via Configurations you can add some contextmenus to add sort functionality

   /**
     * Creates the {@link PopupMenuBuilder} for the column header menu with the menu
     * items that should be added to the menu.
     *
     * @param natTable
     *            The NatTable where the menu should be attached.
     * @return The {@link PopupMenuBuilder} that is used to build the column
     *         header menu.
     */
    protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
        return new PopupMenuBuilder(natTable).withHideColumnMenuItem(Messages.MenuItemProviders_hideColumn)
                .withShowAllColumnsMenuItem(Messages.MenuItemProviders_showAllColumns)
                .withAutoResizeSelectedColumnsMenuItem(Messages.MenuItemProviders_autoResizeAllSelectedColumns)
                .withClearAllFilters(Messages.MenuItemProviders_clearAllFilters)
                .withMenuItemProvider(getMenuItemSort());
    }

    private IMenuItemProvider getMenuItemSort() {
        return new IMenuItemProvider() {

            @Override
            public void addMenuItem(final NatTable natTable, Menu popupMenu) {

                /*
                 * at this place we do not receive the concrete column - so we only can introduce a
                 * general menu for all columns.
                 *
                 * That means we can not display sorting state of the current column,
                 * we can not disable the menu, if sorting is not allowed on this column.
                 *
                 * Introduces a mechanism which would allow to react on the state current sorting
                 * state.
                 */
                 *
                Image iconUp = GUIHelper.getImage("up_0"); //$NON-NLS-1$
                Image iconDown = GUIHelper.getImage("down_0"); //$NON-NLS-1$

                // group
                MenuItem sortMenuGroupItem = new MenuItem(popupMenu, SWT.CASCADE);
                /** the hotkey is defined in ConfigurationHeaderActions */
                sortMenuGroupItem.setText(Messages.ConfigurationHeaderContextMenu_sorting + MENU_TAB_CHAR + HOTKEY_CTRL
                        + PLUS_CHAR + HOTKEY_SEARCH);
                sortMenuGroupItem.setImage(iconDown);

                sortMenuGroupItem.setEnabled(true);
                Menu sortMenuGroup = new Menu(sortMenuGroupItem);
                sortMenuGroupItem.setMenu(sortMenuGroup);

    
                // Item sort none
                MenuItem sortMenuItemNo = new MenuItem(sortMenuGroup, SWT.PUSH);
                sortMenuItemNo.setText(Messages.ConfigurationHeaderContextMenu_noSort);
                sortMenuItemNo
                        .addSelectionListener(getSelectionAdapterSorting(SortDirectionEnum.NONE, sortModel, natTable));

                // item sort asc
                MenuItem sortMenuItemAsc = new MenuItem(sortMenuGroup, SWT.PUSH);
                sortMenuItemAsc.setText(Messages.ConfigurationHeaderContextMenu_ascending);
                sortMenuItemAsc.setImage(iconUp);
                sortMenuItemAsc
                        .addSelectionListener(getSelectionAdapterSorting(SortDirectionEnum.ASC, sortModel, natTable));

                // Item sort desc
                MenuItem sortMenuItemDesc = new MenuItem(sortMenuGroup, SWT.PUSH);
                sortMenuItemDesc.setText(Messages.ConfigurationHeaderContextMenu_descending);
                sortMenuItemDesc.setImage(iconDown);
                sortMenuItemDesc
                        .addSelectionListener(getSelectionAdapterSorting(SortDirectionEnum.DESC, sortModel, natTable));

    // HELPER

    private SelectionAdapter getSelectionAdapterSorting(final SortDirectionEnum sortDirectionEnum,
            final ISortModel sortModel, final NatTable natTable) {
        return new SelectionAdapter() {

            @SuppressWarnings("unused")
            @Override
            public void widgetSelected(SelectionEvent e) {
                NatEventData natEventData = MenuItemProviders.getNatEventData(e);
                int columnPosition = natEventData.getColumnPosition();
                int columnIndex = natTable.getColumnIndexByPosition(columnPosition);

                // switch model into the predescessor state. SortColumnCommand will siwtch it into
                // the next state
                sortModel.sort(columnIndex, TableSorter.getPreviousSortDirection(sortDirectionEnum), false);

                // send a command
                // the command will switch the model into the next sorting-state
                natTable.doCommand(new SortColumnCommand(natTable, columnPosition, false));
            }

        };
    }

Example Snippets Repository

Are checked in on GitHub: https://github.com/skipidar/NatTableExamples

Labels

Jede Konfiguration, ob Style, oder Renderer ist mit einem Label assoziiert.

Where is the place, where Labels are ?

In ILayer.class

    @Override
    public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) {
        LabelStack configLabels = new LabelStack();
        if (this.configLabelAccumulator != null) {
            this.configLabelAccumulator.accumulateConfigLabels(configLabels, columnPosition, rowPosition);
        }
        if (this.regionName != null) {
            configLabels.addLabel(this.regionName);
        }
        return configLabels;
    }

How to contribute a label by a custom layer

Use method getConfigLabelsByPosition in ILayer.class

                // need to contribute a label for the whole layer
                
                // WRONG - layers have no chance to contribute Labels, but only via Regions. Noone asks for registered IConfigLabelAccumulator
//                @Override
//                public LabelStack getRegionLabelsByXY(int x, int y) {
//                    LabelStack stack = super.getRegionLabelsByXY(x, y);
//                    stack.addLabel(GridRegion.ROW_HEADER);
//                    return stack;
//                }

                // RIGHT
                @Override
                public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) {
                    LabelStack stack = super.getConfigLabelsByPosition(columnPosition, rowPosition);
                    stack.addLabel(GridRegion.ROW_HEADER);
                    return stack;
                }

Attach Labels via Regions. Each region becomes a label too.

                combinedCheckboxRowheaderLayer = new CompositeLayer(2, 1);
                combinedCheckboxRowheaderLayer.setChildLayer(REGIONS.CHECKBOX_LAYER, checkboxesLayer, 0, 0);
                combinedCheckboxRowheaderLayer.setChildLayer(REGIONS.ROW_HEADER_LAYER, rowHeaderLayer, 1, 0);

Example Code

Example of using NatTable

 
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration;
import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.data.DummyBodyDataProvider;
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.hideshow.ColumnHideShowLayer;
import org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;
import org.eclipse.nebula.widgets.nattable.painter.cell.ButtonCellPainter;
import org.eclipse.nebula.widgets.nattable.painter.cell.ImagePainter;
import org.eclipse.nebula.widgets.nattable.painter.cell.TextPainter;
import org.eclipse.nebula.widgets.nattable.painter.cell.decorator.CellPainterDecorator;
import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.style.Style;
import org.eclipse.nebula.widgets.nattable.ui.action.IMouseAction;
import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry;
import org.eclipse.nebula.widgets.nattable.ui.matcher.CellLabelMouseEventMatcher;
import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher;
import org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum;
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class Table {
    public static final String CUSTOM_CELL_LABEL = "Cell_LABEL";
 
    public static void main(String[] args) {
        Display display = new Display ();
        Shell shell = new Shell (display);
        shell.setLayout(new GridLayout());
         
        shell.setSize(1000, 600);
         
        new Table(shell);
         
        shell.open ();
        while (!shell.isDisposed ()) {
            if (!display.readAndDispatch ()) display.sleep ();
        }
        display.dispose ();
         
    }
     
    public Table(Shell parent) {
    	
        parent.setLayout(new FillLayout());

        IDataProvider bodyDataProvider = setupBigDataProvider();
         
        //All of the layers, needed for the table
        BodyStack bodyStack = new BodyStack(bodyDataProvider);
         
        //column names
        IDataProvider dataProviderColHeader = getColumnLabelProvider(bodyDataProvider);
        DataLayer dataLayerColHeader = new DataLayer(dataProviderColHeader);
        ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(dataLayerColHeader, bodyStack, bodyStack.selectionLayer);  //ACHTUNG: use viewportlayer here
         
 
        //row names. 
            //DEMO of encapsulating layers and configs in an "AbstractLayerTransform"
        IDataProvider dataProviderRowHeader = getRowLabelProvider(bodyDataProvider);        
        RowHeaderLayerStack rowHeaderLayer = new RowHeaderLayerStack(dataProviderRowHeader, bodyStack, bodyStack.selectionLayer); //ACHTUNG: use viewportlayer here
                                                                                                //Alternative to creating ColumnHeaderLayer: 
                                                                                                //CUSTOM AbstractLayerTransform, 
                                                                                                //encapsulates row header creation. 
                                                                                                //Can change provide changes to layers beneath.
         
        //(left upper)corner
        DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(dataProviderColHeader, dataProviderRowHeader);
        CornerLayer cornerLayer = new CornerLayer(new DataLayer(cornerDataProvider), rowHeaderLayer, columnHeaderLayer);
 
        //top layer
        GridLayer gridLayer = new GridLayer(bodyStack, columnHeaderLayer, rowHeaderLayer, cornerLayer);
        NatTable natTable = new NatTable(parent, gridLayer, false); //"autoconfigure" I can apply my own configuration
                 
         
         
        IConfigRegistry configRegistry = new ConfigRegistry();
         
 
        // Step 1: Create a label accumulator - adds custom labels to all cells which we
        // wish to render differently. In this case render as a button.
        ColumnOverrideLabelAccumulator cellLabelAccumulator =   new ColumnOverrideLabelAccumulator(bodyStack.bodyDataLayer);
        //accumulator will add Label "CUSTOM_CELL_LABEL" to the column 2
        cellLabelAccumulator.registerColumnOverrides(2, CUSTOM_CELL_LABEL);
 
        // Step 2: Apply the Accumulator to the column
        bodyStack.bodyDataLayer.setConfigLabelAccumulator(cellLabelAccumulator);
 
         
        //Step 3: Apply the Custom style Painter to the cells, annotated by the Label "CUSTOM_CELL_LABEL"
 
                //3.1 new painter
                final ButtonCellPainter buttonPainter = new ButtonCellPainter(
                            new CellPainterDecorator(
                                    new TextPainter(), CellEdgeEnum.RIGHT, new ImagePainter(GUIHelper.getImage("preferences"))
                            )
                        );
         
                //3.2 make painter responsible for drawing CUSTOM_CELL_LABEL annotated cells 
                configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER,
                        buttonPainter,
                        DisplayMode.NORMAL,
                        CUSTOM_CELL_LABEL);
         
                 
                //3.3 Add the listener to the button
                buttonPainter.addClickListener(new IMouseAction() {
                     
                    @Override
                    public void run(NatTable natTable, MouseEvent event) {
                        System.out.println("MouseClick");
                    }
                });
                 
                 
                //3.4 set the style for the CUSTOM_CELL_LABEL annotated cells 
                Style style = new Style();
                style.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_WHITE);   // Set the color of the cell. This is picked up by the button painter to style the button
         
                configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, style, DisplayMode.NORMAL, CUSTOM_CELL_LABEL);
                configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, style, DisplayMode.SELECT, CUSTOM_CELL_LABEL);
                 
             
                //3.5 add Mouse listener to use the Painter on Mouse event
                natTable.addConfiguration(new AbstractUiBindingConfiguration() {
                     
                    @Override
                    public void configureUiBindings(UiBindingRegistry uiBindingRegistry) {
                        // Match a mouse event on the body, when the left button is clicked
                        // and the custom cell label is present
                        CellLabelMouseEventMatcher mouseEventMatcher = new CellLabelMouseEventMatcher(
                                                                            GridRegion.BODY,
                                                                            MouseEventMatcher.LEFT_BUTTON,
                                                                            CUSTOM_CELL_LABEL);
                        // Inform the button painter of the click.
                        uiBindingRegistry.registerMouseDownBinding(mouseEventMatcher, buttonPainter);
                    }
                });
                 
        //Step 4 now apply the configuration to the NatTable
            //apply the style, which will draw the data into the Table
        	natTable.setConfigRegistry(configRegistry);
        	natTable.addConfiguration(new DefaultNatTableStyleConfiguration());         	// must be done after the setConfigRegistry, because the Configuration is stored in the configRegistry
            natTable.configure();
 
    }
 
     
     
     
    //HELPER
    private IDataProvider setupBigDataProvider() {
        DummyBodyDataProvider data = new DummyBodyDataProvider(500, 100000000);
        return data;
    }
    
    
    public class BodyStack extends AbstractLayerTransform {
    	
    	private DataLayer bodyDataLayer;
		private ColumnReorderLayer columnReorderLayer;
		private ColumnHideShowLayer columnHideShowLayer;
		private SelectionLayer selectionLayer;
		private ViewportLayer viewportLayer;

		public BodyStack(IDataProvider bodyDataProvider){
            bodyDataLayer = new DataLayer(bodyDataProvider);
            columnReorderLayer = new ColumnReorderLayer(bodyDataLayer);
            columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer);
            selectionLayer = new SelectionLayer(columnHideShowLayer);
            viewportLayer = new ViewportLayer(selectionLayer);
            
            // make the bottom layer to the layer, which will be used to extend the stack 
            setUnderlyingLayer(viewportLayer);
    	}
    	
    }
     
    private IDataProvider getRowLabelProvider(final IDataProvider bodyDataProvider){
        return new IDataProvider() {
             
            @Override
            public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
                //nothing
            }
             
            @Override
            public int getRowCount() {
                return bodyDataProvider.getRowCount();
            }
             
            @Override
            public Object getDataValue(int columnIndex, int rowIndex) {
                //here the name of the row is set
                return rowIndex;
            }
             
            @Override
            public int getColumnCount() {
                return 1;
            }
        };
    }
    private IDataProvider getColumnLabelProvider(final IDataProvider bodyDataProvider){
        return new IDataProvider() {
             
            @Override
            public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
                //nothing
            }
             
            @Override
            public int getRowCount() {
                return 1;
            }
             
            @Override
            public Object getDataValue(int columnIndex, int rowIndex) {
                //here the name of the row is set
                return columnIndex;
            }
             
            @Override
            public int getColumnCount() {
                return bodyDataProvider.getColumnCount();
            }
        };
    }
     
    private class RowHeaderLayerStack extends AbstractLayerTransform {
 
        public RowHeaderLayerStack(IDataProvider dataProvider, ILayer bodyDataLayer, SelectionLayer selectionLayer) {
            DataLayer dataLayer = new DataLayer(dataProvider, 50, 20);
            RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(dataLayer, bodyDataLayer, selectionLayer);
            setUnderlyingLayer(rowHeaderLayer);
        }
    }
 
}

Rendering a Cell as a Checkbox

   @Override
    protected void registerDomainSpecificLabels(
    		ColumnOverrideLabelAccumulator columnOverrideLabelAccumulator) {
    	
	//1. add labels to specific columns
        columnOverrideLabelAccumulator.registerColumnOverrides(0, CHECK_BOX_EDITOR_CONFIG_LABEL, CHECK_BOX_CONFIG_LABEL);
        columnOverrideLabelAccumulator.registerColumnOverrides(1, CHECK_BOX_EDITOR_CONFIG_LABEL, CHECK_BOX_CONFIG_LABEL);
    }
    
    @Override
    protected void modifyConfigRegistry(IConfigRegistry configRegistry) {

	//2. register painter using labels
	configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, checkBoxPainter, DisplayMode.NORMAL, CHECK_BOX_CONFIG_LABEL);

	//3. register editor for BOTH modes NORMAL and EDIT using labels
	CheckBoxCellEditor checkBoxCellEditor = new CheckBoxCellEditor();
	configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, checkBoxCellEditor, DisplayMode.NORMAL, CHECK_BOX_EDITOR_CONFIG_LABEL);
	configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, checkBoxCellEditor, DisplayMode.EDIT, CHECK_BOX_EDITOR_CONFIG_LABEL);
		
	//4. when are the cells editable? - always
	configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, IEditableRule.ALWAYS_EDITABLE, DisplayMode.EDIT, CHECK_BOX_CONFIG_LABEL);
		configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultBooleanDisplayConverter(), DisplayMode.NORMAL, CHECK_BOX_CONFIG_LABEL);
	}

Catching Single Clicks on Column

@Override
	protected void modifyUiBindingRegistry(UiBindingRegistry uiBindingRegistry) {
		
		IMouseEventMatcher mouseEventMatcher = new IMouseEventMatcher() {
			@Override
			public boolean matches(NatTable natTable, MouseEvent event,
					LabelStack regionLabels) {
				return true;
			}
		};
		IMouseAction mouseAction = new IMouseAction() {
			@Override
			public void run(NatTable natTable, MouseEvent event) {
				int columnPosition = natTable.getColumnPositionByX(event.x); // only the columns in the
                int rowPosition = natTable.getRowPositionByY(event.y);
                
                int columnIndex = natTable.getColumnIndexByPosition(columnPosition);
                int rowIndex = natTable.getRowIndexByPosition(rowPosition);
                
                IDataProvider dataProvider = getDataProvider();
                boolean val = (boolean) dataProvider.getDataValue(columnIndex, rowIndex);
                dataProvider.setDataValue(columnIndex, rowIndex, val ? false : true );
			}
		};
		
		uiBindingRegistry.registerFirstSingleClickBinding(
				mouseEventMatcher,
				mouseAction
		);
	}

Registering Comboboxes on a Cell

...
		registerColumnLabels(columnOverrideLabelAccumulator, natTable);
		makeColumnsEditable();
		registerComboBox(configRegistry);
		registerValidators(configRegistry);
}


//RENDERING
	// 1. add label to the columns
	private void registerColumnLabels(ColumnOverrideLabelAccumulator columnOverrideLabelAccumulator, NatTable natTable){
		columnOverrideLabelAccumulator.registerColumnOverrides(0, "LABEL0");
		columnOverrideLabelAccumulator.registerColumnOverrides(1, "LABEL1");
		columnOverrideLabelAccumulator.registerColumnOverrides(2, "LABEL2");
		columnOverrideLabelAccumulator.registerColumnOverrides(3, "LABEL3", "LABEL_editor");
		columnOverrideLabelAccumulator.registerColumnOverrides(4, "LABEL4");
	}
	
	//2. make cells editable
	private void makeColumnsEditable(){
		//2. make the cell editable
//		configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, IEditableRule.ALWAYS_EDITABLE, DisplayMode.EDIT, "LABEL0");
		configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, IEditableRule.ALWAYS_EDITABLE, DisplayMode.EDIT, "LABEL1");
		configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, IEditableRule.ALWAYS_EDITABLE, DisplayMode.EDIT, "LABEL2");
		configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, IEditableRule.ALWAYS_EDITABLE, DisplayMode.EDIT, "LABEL3");
		configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, IEditableRule.ALWAYS_EDITABLE, DisplayMode.EDIT, "LABEL4");
	}

	//3. register painter
	//4. register editor
	private static void registerComboBox(IConfigRegistry configRegistry ) {
		String COMBO_BOX_CONFIG_LABEL = "LABEL4";
		String FORMAT_PRICING_TYPE_CONFIG_LABEL = "LABEL4";
		
		ICellPainter comboBoxCellPainter = new ComboBoxPainter();
//		ICellEditor comboBoxCellEditor = new ComboBoxCellEditor(Arrays.asList(new PricingTypeBean("Manuell"), new PricingTypeBean("Automatic")));
//		ICellEditor comboBoxCellEditor = new ComboBoxCellEditor(Arrays.asList("222", "333"));
		ICellEditor comboBoxCellEditor = new ComboBoxCellEditor(Arrays.asList(1, 98, 99));

		configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, comboBoxCellPainter, DisplayMode.NORMAL, COMBO_BOX_CONFIG_LABEL);
		configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, comboBoxCellEditor, DisplayMode.NORMAL, COMBO_BOX_CONFIG_LABEL);
		configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, comboBoxCellEditor, DisplayMode.EDIT, COMBO_BOX_CONFIG_LABEL);

		// displayed values can be displayed differently
		// ACHTUNG: DISPLAY_CONVERTER can't return Integer when using ComboBoxCellEditor
		configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new NumbersToTextDisplayConverter(), DisplayMode.NORMAL, FORMAT_PRICING_TYPE_CONFIG_LABEL);
	}
	
	
	//5. data validator
	private void registerValidators(IConfigRegistry configRegistry){
//		configRegistry.registerConfigAttribute(EditConfigAttributes.DATA_VALIDATOR,new IntegerValidator(), DisplayMode.EDIT,"LABEL0");
	}

Retrieving an ICellEditor from the cell

ILayerCell cell = natTable.getCellByPosition(natTable.getColumnPositionByX(event.x), natTable.getRowPositionByY(event.y));
ICellEditor cellEditor = natTable.getConfigRegistry().getConfigAttribute(EditConfigAttributes.CELL_EDITOR, DisplayMode.EDIT, cell.getConfigLabels().getLabels());

Retrieving an ICellPainter from the cell

ILayerCell cell = natTable.getCellByPosition(natTable.getColumnPositionByX(event.x), natTable.getRowPositionByY(event.y));
ICellPainter painter = natTableNew.getCellPainter(colPosition, rowPosition, cell, configRegistry);

Rendering an own SWT Widget inside of the table

Control myControl = new Label(natTable, SWT.NONE);

ICellPainter cellPainter = new AbstractCellPainter() {
	public void paintCell(ILayerCell cell, GC gc, Rectangle bounds, IConfigRegistry configRegistry) {
		myControl.setBounds(bounds);
	}
						
	public int getPreferredWidth(ILayerCell cell, GC gc, IConfigRegistry configRegistry) {
		return myControl.getBounds().y;
	}
						
	public int getPreferredHeight(ILayerCell cell, GC gc, IConfigRegistry configRegistry) {
		return myControl.getBounds().x;
						}
};

NatTable Entry-Point

Here the painting of the table is started:

GridLineCellLayerPainter.class

    @Override
    public void paintLayer(ILayer natLayer, GC gc, int xOffset, int yOffset,
            Rectangle rectangle, IConfigRegistry configRegistry) {
        Boolean renderConfig = null;
        LabelStack stack = natLayer.getRegionLabelsByXY(xOffset, yOffset);
        if (stack != null) {
            // check if there is a configuration telling to not rendering grid
            // lines
            renderConfig = configRegistry.getConfigAttribute(
                    CellConfigAttributes.RENDER_GRID_LINES, DisplayMode.NORMAL,
                    stack.getLabels());
        }

        this.renderGridLines = (renderConfig != null) ? renderConfig : true;

        // Draw GridLines
        if (this.renderGridLines)
            drawGridLines(natLayer, gc, rectangle, configRegistry);

        super.paintLayer(natLayer, gc, xOffset, yOffset, rectangle,
                configRegistry);
    }

Useful Code

Debugging Table

For debugging it is really important to know, which settings are saved for each particular cell in table. This code will echo the configuration for each cell, which the mouse hovers over.

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

import org.eclipse.core.runtime.Platform;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.data.convert.IDisplayConverter;
import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;
import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;

public class TableObjectsDebugTrace {

    private static final String PARAMETER_FOR_DEBUG_TRACE_IN_NATTABLE = "-nattabletrace";

    // init
    boolean showCoordinates = true;
    boolean showLabels = true;
    boolean showCellPainter = true;
    boolean showCellEditor = true;
    boolean showDisplayConverter = true;

    private NatTable natTable;
    private IConfigRegistry configRegistry;

    public TableObjectsDebugTrace(NatTable nattable) {
        this.natTable = nattable;
        this.configRegistry = nattable.getConfigRegistry();

        // check for my custom runtime configuration parameter
        List<String> parameters = Arrays.asList(Platform.getCommandLineArgs());

        if (parameters.contains(PARAMETER_FOR_DEBUG_TRACE_IN_NATTABLE)) {
            nattable.addMouseMoveListener(getDebugTraceMouseListener());
        }
    }

    private MouseMoveListener getDebugTraceMouseListener() {

        MouseMoveListener listener = new MouseMoveListener() {

            @Override
            public void mouseMove(MouseEvent event) {

                int row = natTable.getRowPositionByY(event.y);
                int col = natTable.getColumnPositionByX(event.x);
                ILayerCell cellTemp = natTable.getCellByPosition(col, row);

                if (cellTemp == null) {
                    // we are pointing to an empty space, ther is no cell
                    return;
                }

                LabelStack labelStack = cellTemp.getConfigLabels();

                if (labelStack == null) {
                    return;
                }
                List<String> listLabels = labelStack.getLabels();

                ICellPainter cellPainter = configRegistry.getConfigAttribute(CellConfigAttributes.CELL_PAINTER,
                        DisplayMode.NORMAL, listLabels);
                ICellEditor cellEditor = configRegistry.getConfigAttribute(EditConfigAttributes.CELL_EDITOR,
                        DisplayMode.EDIT, listLabels);

                IDisplayConverter displayConverterNorm = configRegistry.getConfigAttribute(
                        CellConfigAttributes.DISPLAY_CONVERTER, DisplayMode.NORMAL, listLabels);
                IDisplayConverter displayConverterEdit = configRegistry.getConfigAttribute(
                        CellConfigAttributes.DISPLAY_CONVERTER, DisplayMode.EDIT, listLabels);

                StringBuilder stringBuilder = new StringBuilder();

                if (showCoordinates) {
                    stringBuilder.append("PosRow:" + row + " PosCol:" + col);
                    stringBuilder.append("  ");
                }

                if (showLabels) {
                    stringBuilder.append("Labels:" + listLabels);
                    stringBuilder.append("  ");
                }

                if (showCellPainter) {
                    stringBuilder.append("Painter:" + cellPainter.getClass().getSimpleName());
                    stringBuilder.append("  ");
                }

                if (showCellEditor) {
                    stringBuilder.append("Editor:" + cellEditor.getClass().getSimpleName());
                    stringBuilder.append("  ");
                }

                if (showDisplayConverter) {
                    stringBuilder.append("DisplayConverter::");
                    stringBuilder.append("DMode=NORMAL:" + displayConverterNorm.getClass().getSimpleName());
                    stringBuilder.append(" ");
                    stringBuilder.append("DMode=EDIT:" + displayConverterEdit.getClass().getSimpleName());
                    stringBuilder.append("  ");
                }

                // echo result
                System.out.println(stringBuilder.toString());
            }

        };

        return listener;
    }
}