Table of Contents
NatTable
A table is created as following:
- create a BodyLayerStack. Each layer is based on previous.
- create a RowHeaderLayer - it will provide rownames
- create a ColumnNameLayer - it will provide columnnames
- create a CornerLayer - to add some functionality to the left, upper table corner
- assemble all layers in one GridLayer
The functionality is contributed in BodyLayerStack.
FallPits
- Layers are created like SWT Widgets - one is part of another. When creating the Gridlayer - use the bottom layer.
- When creating the Column / row layer - use the bottom Layer either asf
// 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,
- a table can oly have ONE ColumnOverrideLabelAccumulator - setConfigLabelAccumulator() allways overrides the old one!
- Columns and rows in a layer are referenced either by position or index:
The position of a column/row in a layer corresponds to the physical location of the column/row in the current layer. Positions always start from 0 and increase sequentially. Positions reorder Indexes.
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. Usually the lowest layer in the layer stack will be the DataLayer. Indexes are not necessarily ordered, and in some cases may not even be unique within a layer. They correspond to the numbers within data providers.These concepts are illustrated by the following example:
ColumnHideShowLayer C 0 1 2 3 4 <- column positions 1 0 3 4 5 <- column indexes ColumnReorderLayer B 0 1 2 3 4 5 <- column positions 2 1 0 3 4 5 <- column indexes DataLayer A 0 1 2 3 4 5 <- column positions 0 1 2 3 4 5 <- column indexes
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
- row
- 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 |
configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, new CheckBoxPainter(), DisplayMode.NORMAL, CHECK_BOX_CONFIG_LABEL); |
EditConfigAttributes |
configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new CheckBoxCellEditor(), DisplayMode.NORMAL, CHECK_BOX_EDITOR_CONFIG_LABEL); |
CellConfigAttributes |
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.
- If current mode is SELECT - then the NatTable will consider all Styles for modes: SELECT, HOVER, NORMAL.
- It will omit the Styles, registerd for the mode SELECT_HOVER
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.
- 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.
- 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; } }