package problem; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.nebula.widgets.nattable.NatTable; import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration; import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration; import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes; import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration; import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; import org.eclipse.nebula.widgets.nattable.config.IEditableRule; import org.eclipse.nebula.widgets.nattable.data.IDataProvider; import org.eclipse.nebula.widgets.nattable.data.convert.DefaultBooleanDisplayConverter; import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes; import org.eclipse.nebula.widgets.nattable.edit.action.CellEditDragMode; import org.eclipse.nebula.widgets.nattable.edit.action.MouseEditAction; import org.eclipse.nebula.widgets.nattable.edit.editor.CheckBoxCellEditor; import org.eclipse.nebula.widgets.nattable.grid.GridRegion; import org.eclipse.nebula.widgets.nattable.grid.data.DefaultBodyDataProvider; import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider; import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer; import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer; import org.eclipse.nebula.widgets.nattable.grid.layer.DimensionallyDependentLayer; import org.eclipse.nebula.widgets.nattable.grid.layer.config.DefaultGridLayerConfiguration; import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer; import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer; import org.eclipse.nebula.widgets.nattable.layer.DataLayer; import org.eclipse.nebula.widgets.nattable.layer.ILayer; import org.eclipse.nebula.widgets.nattable.layer.LabelStack; import org.eclipse.nebula.widgets.nattable.layer.cell.IConfigLabelAccumulator; import org.eclipse.nebula.widgets.nattable.layer.event.VisualRefreshEvent; import org.eclipse.nebula.widgets.nattable.painter.cell.CheckBoxPainter; import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; import org.eclipse.nebula.widgets.nattable.style.DisplayMode; import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry; import org.eclipse.nebula.widgets.nattable.ui.matcher.CellEditorMouseEventMatcher; import org.eclipse.nebula.widgets.nattable.ui.matcher.CellPainterMouseEventMatcher; import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher; import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; /** * Short description: The sample is meant to show two problems i faced with NatTable, when trying to add an {@link DimensionallyDependentLayer} above the header. * The layer contains controls (checkboxes, in reality also comboboxes) which are meant to change data for the elements in the whole row. * *
{@link DataBean} - simple class only provided to show some data in the body area. *
{@link SettingsDataProvider} - shows the problems I face *
{@link TrivialRowSettingsBean} - one per row. Used to maintain the data for {@link SettingsDataProvider} * * Problem Description *
    *
  1. {@link SettingsDataProvider#getDataValue(int, int)} always is invoked with the correct column indices, independent whether the first column is visible or not. * {@link SettingsDataProvider#setDataValue(int, int, Object)} is called with the wrong indices. (0 for the first visible column, so the indices change dependent on whether the * first column is shown or not) *
  2. *
  3. Quite minor but confused me: I need to manually force a redraw of the table in {@link SettingsDataProvider#setDataValue(int, int, Object)}, otherwise the checkboxes dont * display the changed state. This works out of the box in case the editors/painters are used in the body area.
  4. *
* * Well ok - likely I make a mistake somewhere, but I simply dont understand where ;-). * In case it is not my mistake the sample may be valueable for you as it might show an existing problem. * * ah and not to forget: setDataValue only gets invoked in case there is a state change. As the indices are mixed up when the first column is hidden * column with index 1 and 2 need to have the same state in order to trigger the setter (when clicking on column 2) as it seems to compare with the state of column 1 * before tiriggering the setDataValue method. * * @author chris lewold */ public class IndexTransformInHeader { // trivial bean to be applied per row private static class TrivialRowSettingsBean { public TrivialRowSettingsBean(boolean b) { selected = b; } boolean selected;; } public static class DataBean { public DataBean(String col1, String col2, String col3) { valCol1 = col1; valCol2 = col2; valCol3 = col3; } public String getValCol1() { return valCol1; } public void setValCol1(String valCol1) { this.valCol1 = valCol1; } public String getValCol2() { return valCol2; } public void setValCol2(String valCol2) { this.valCol2 = valCol2; } public String getValCol3() { return valCol3; } public void setValCol3(String valCol3) { this.valCol3 = valCol3; } private String valCol1; private String valCol2; private String valCol3; } public static void main(String[] args) { run(600, 400, new IndexTransformInHeader()); } static List getTestFixture() { final List rc = new ArrayList<>(); for (int i = 1; i < 5; i++) { final String prefix = "Bean " + i; rc.add(new DataBean(prefix + ", col 1", prefix + ", col 2", prefix + ", col 3")); } return rc; } private class SettingsDataProvider implements IDataProvider { @Override public Object getDataValue(int columnIndex, int rowIndex) { // always correct column index. // in case the first column is hidden only index 1 and 2 is invoked System.out.println("getDataValue for col " + columnIndex); return rowSettingsBeans[columnIndex].selected; } @Override public void setDataValue(int columnIndex, int rowIndex, Object newValue) { // WRONG columnIndex in case the first column is hidden // so e.g. if I hide the first column (index 0) and click the first visible checkbox // then the provided columnIndex is not index 1 as expected, but index 0. System.out.println("-- setDataValue for col " + columnIndex); rowSettingsBeans[columnIndex].selected = (boolean) newValue; // I don't know why, but I need to force redrawing the cell .... natTable.fireLayerEvent(new VisualRefreshEvent(natTable)); } @Override public int getColumnCount() { return 3; } @Override public int getRowCount() { return 1; } } private static final String SETTINGS_REGION = "SETTINGS_REGION"; private static final String SETTINGS_LABEL = "SETTINGS_LABEL"; private final TrivialRowSettingsBean[] rowSettingsBeans = new TrivialRowSettingsBean[3]; // needed for redraw in setDataValue. private NatTable natTable; public IndexTransformInHeader() { rowSettingsBeans[0] = new TrivialRowSettingsBean(true); rowSettingsBeans[1] = new TrivialRowSettingsBean(false); rowSettingsBeans[2] = new TrivialRowSettingsBean(true); } public Control createExampleControl(Composite parent) { parent.setLayout(new GridLayout()); final String[] propertyNames = { "valCol1", "valCol2", "valCol3" }; final DataLayer bodyDataLayer = new DataLayer(new DefaultBodyDataProvider<>(getTestFixture(), propertyNames)); final ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(bodyDataLayer); final SelectionLayer selectionLayer = new SelectionLayer(columnHideShowLayer); final ViewportLayer viewportLayer = new ViewportLayer(selectionLayer); viewportLayer.setRegionName(GridRegion.BODY); final ILayer columnHeaderLayer = new ColumnHeaderLayer( new DefaultColumnHeaderDataLayer(new DefaultColumnHeaderDataProvider(propertyNames)), viewportLayer, selectionLayer); // The settings area as composite above the column headers SettingsDataProvider settingsDataProvider = new SettingsDataProvider(); DataLayer settingsDataLayer = new DataLayer(settingsDataProvider); settingsDataLayer.addConfiguration(new AbstractRegistryConfiguration() { @Override public void configureRegistry(IConfigRegistry configRegistry) { configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, IEditableRule.ALWAYS_EDITABLE, DisplayMode.EDIT, SETTINGS_LABEL); configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new CheckBoxCellEditor(), DisplayMode.EDIT, SETTINGS_LABEL); configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, new CheckBoxPainter(), DisplayMode.NORMAL, SETTINGS_LABEL); configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultBooleanDisplayConverter(), DisplayMode.NORMAL, SETTINGS_LABEL); } }); settingsDataLayer.addConfiguration(new AbstractUiBindingConfiguration() { @Override public void configureUiBindings(UiBindingRegistry uiBindingRegistry) { uiBindingRegistry.registerSingleClickBinding(new CellEditorMouseEventMatcher(SETTINGS_REGION), new MouseEditAction()); uiBindingRegistry.registerMouseDragMode(new CellEditorMouseEventMatcher(SETTINGS_REGION), new CellEditDragMode()); uiBindingRegistry.registerFirstSingleClickBinding( new CellPainterMouseEventMatcher(SETTINGS_REGION, MouseEventMatcher.LEFT_BUTTON, CheckBoxPainter.class), new MouseEditAction()); uiBindingRegistry.registerFirstMouseDragMode( new CellPainterMouseEventMatcher(SETTINGS_REGION, MouseEventMatcher.LEFT_BUTTON, CheckBoxPainter.class), new CellEditDragMode()); } }); settingsDataLayer.setConfigLabelAccumulator(new IConfigLabelAccumulator() { @Override public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) { configLabels.addLabel(SETTINGS_LABEL); configLabels.addLabel(SETTINGS_REGION); } }); DimensionallyDependentLayer dimLayer = new DimensionallyDependentLayer(settingsDataLayer, viewportLayer, new SelectionLayer(settingsDataLayer)); CompositeLayer compositeLayer = new CompositeLayer(1, 3); compositeLayer.setChildLayer(SETTINGS_REGION, dimLayer, 0, 0); compositeLayer.setChildLayer(GridRegion.COLUMN_HEADER, columnHeaderLayer, 0, 1); compositeLayer.setChildLayer(GridRegion.BODY, viewportLayer, 0, 2); compositeLayer.addConfiguration(new DefaultGridLayerConfiguration(compositeLayer)); final Button checkBoxHideFirstColumn = new Button(parent, SWT.CHECK); checkBoxHideFirstColumn.setText("Hide first column"); checkBoxHideFirstColumn.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (checkBoxHideFirstColumn.getSelection()) { columnHideShowLayer.hideColumnPositions(Arrays.asList(0)); } else { columnHideShowLayer.showAllColumns(); } } }); natTable = new NatTable(parent, NatTable.DEFAULT_STYLE_OPTIONS | SWT.BORDER, compositeLayer, false); natTable.addConfiguration(new DefaultNatTableStyleConfiguration()); natTable.configure(); GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable); return natTable; } private static void run(int shellWidth, int shellHeight, IndexTransformInHeader example) { final Display display = Display.getDefault(); final Shell shell = new Shell(display, SWT.SHELL_TRIM); shell.setLayout(new FillLayout()); shell.setSize(shellWidth, shellHeight); final Control exampleControl = example.createExampleControl(shell); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } exampleControl.dispose(); shell.dispose(); display.dispose(); } }