Home » Eclipse Projects » NatTable » Misbehaving two-level column groups
Misbehaving two-level column groups [message #1240036] |
Wed, 05 February 2014 15:22 |
István Mészáros Messages: 51 Registered: October 2009 |
Member |
|
|
Hello all,
I need to create a somewhat complex configuration with NatTable. The aim is to achieve an excel-like behavior and look. Tree structure and multiple levels of column groups are also involved.
I have a problem with the multi-level column grouping. I followed the _001_Two_level_column_groups example to implement it, but this example also has the problem i encountered.
Here is a picture of how my NatTable currently looks like:
The topmost row with the colored groups is achieved with a ColumnGroupGroupHeaderLayer.
The "CAPEX FC *" groups are currently collapsed with the static "Total" column visible, achieved with ColumnGroupHeaderLayer.
The rest of the columns are grouped directly under the ColumnGroupGroupHeaderLayer.
My problem is that at the colored top-groups i'm not able to set the static columns (no method in "GroupGroup" layer), and collapsing/expanding does not work either. If i double click on a colored top group, it throws an exception like:
java.lang.NullPointerException
at org.eclipse.nebula.widgets.nattable.group.command.ColumnGroupExpandCollapseCommandHandler.doCommand(ColumnGroupExpandCollapseCommandHandler.java:47)
at org.eclipse.nebula.widgets.nattable.group.command.ColumnGroupExpandCollapseCommandHandler.doCommand(ColumnGroupExpandCollapseCommandHandler.java:1)
and instead of collapsing/expanding the group, it triggers a sorting command on the column "below" the event location.
I can only expand/collapse the sub-groups defined in the ColumnGroupHeaderLayer.
Is this the expected behavior?
I found a related bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=379940
Does this mean that multiple level column grouping is not fully supported? After digging into the sources and examining the exception above, to me it looks like it's not. If it is however, what am i doing wring?
I would post my code but i'm using a custom way to initialize the column groups from a hierarchical model, but in theory it works exactly like the _001_Two_level_column_groups example.
|
|
| |
Re: Misbehaving two-level column groups [message #1240314 is a reply to message #1240046] |
Thu, 06 February 2014 07:36 |
István Mészáros Messages: 51 Registered: October 2009 |
Member |
|
|
Hello Dirk,
first of all, thanks for your quick reply. The issues i'm having with _001_Two_level_column_groups:
1.) if you place the mouse pointer on "GroupGroup 1" but horizontally above the "rating" column and double click, "Group 1" gets closed instead of "GroupGroup 1"
2.) if you then double click again on the exact same location, an exception is thrown and the example freezes completely
3.) if you double click on "GroupGroup 1" having the mouse pointer above "UnBreakable group 2", it closes that group again instead of "GroupGroup 1"
4.) if you double click on "GroupGroup 1" having the mouse pointer above "Issue Date", an exception is thrown and the example freezes completely
My need is to be able to configure the "GroupGroup" layer in such a way that if one double clicks on "GroupGroup 1" only the "Issue Date" column (or only group 1, 2, etc...) remains visible, like it is possible with ColumnGroupHeaderLayer.
About this: "The rest of the columns are grouped directly under the ColumnGroupGroupHeaderLayer."
If you look at "UnBreakable group 3" in the example, it's columns are added to the ColumnGroupHeaderLayer. This way the group header takes place in the upper two rows in a merged cell, and expanding/collapsing the group works properly. Unfortunately i need a different visual outcome, having the group header only taking the uppermost row and the column headers spanning vertically two rows. While i could achieve this by adding the column indices to the ColumnGroupGroupHeaderLayer instead of the ColumnGroupHeaderLayer, collapsing/expanding no longer works, if i double click on the group header, an exception is thrown and sorting happens on the column below the event's location.
|
|
| | | | | | | | | |
Re: Misbehaving two-level column groups [message #1278930 is a reply to message #1273614] |
Fri, 28 March 2014 02:45 |
neal zhang Messages: 45 Registered: July 2012 |
Member |
|
|
Hi Dirk,
Thank you fixed this problem so quickly,and sorry reply late,recently very busy.
by the way,where i can download latest nattable? (you said "As I just pushed it to the repository, SNAPSHOTS >= 281 should contain the modifications.")
i can't find in "http://www.eclipse.org/nattable/download.php",sorry,maybe i am foolish, ,
|
|
| | |
Re: Misbehaving two-level column groups [message #1281220 is a reply to message #1273614] |
Mon, 31 March 2014 14:48 |
István Mészáros Messages: 51 Registered: October 2009 |
Member |
|
|
I still found a little flaw in ColumnGroupGroupHeaderLayer.
If you look at the screenshot in my first post, there is a pink GroupGroup (Total Project View). It is configured that if it is collapsed, the "FC (PSP)" column disappears, but all the others remain visible. When the viewport is scrolled to the right, and columns of the group starts the scroll off the visible area on the left, the pink header remains 7 cell width, and therefore intersects with the yellow group.
This can be easily worked out by changing ColumnGroupGroupHeaderLayer#getColumnSpan(int columnPosition) to do not concern whether the group is closed or not:
protected int getColumnSpan(int columnPosition) {
int columnIndex = getColumnIndexByPosition(columnPosition);
ColumnGroup columnGroup = model.getColumnGroupByIndex(columnIndex);
//if (columnGroup.isCollapsed()) {
// int sizeOfStaticColumns = columnGroup.getStaticColumnIndexes().size();
// return sizeOfStaticColumns == 0 ? 1 : sizeOfStaticColumns;
//} else {
int startPositionOfGroup = getStartPositionOfGroup(columnPosition);
int sizeOfGroup = columnGroup.getSize();
int endPositionOfGroup = startPositionOfGroup + sizeOfGroup;
List<Integer> columnIndexesInGroup = columnGroup.getMembers();
for (int i = startPositionOfGroup; i < endPositionOfGroup; i++) {
int index = getColumnIndexByPosition(i);
if (!columnIndexesInGroup.contains(Integer.valueOf(index))) {
sizeOfGroup--;
}
}
return sizeOfGroup;
//}
}
I assume the "true" branch of that "if" was meant to be a faster short cut to the "false" branch, but somehow does not work properly. I do not fully understand what's happening here, but noticed that if the group is expanded, the glitch does not come up.
|
|
| |
Re: Misbehaving two-level column groups [message #1281380 is a reply to message #1281335] |
Mon, 31 March 2014 20:11 |
Dirk Fauth Messages: 2903 Registered: July 2012 |
Senior Member |
|
|
I really tried to reproduce the issue by creating an example that has two level column grouping, sorting and filtering combined. As the issue occurs in the column header region, I don't think the tree has impact on that.
Maybe your layer composition is not correct. Could you try to reproduce the issue with the following code? And compare your code with that example? Maybe you just need to switch the layer order to avoid the issue.
public class _807_SortableFilterableColumnGroupExample extends AbstractNatExample {
private final ColumnGroupModel columnGroupModel = new ColumnGroupModel();
private final ColumnGroupModel sndColumnGroupModel = new ColumnGroupModel();
public static void main(String[] args) throws Exception {
StandaloneNatExampleRunner.run(600, 400, new _807_SortableFilterableColumnGroupExample());
}
@Override
public Control createExampleControl(Composite parent) {
//create a new ConfigRegistry which will be needed for GlazedLists handling
ConfigRegistry configRegistry = new ConfigRegistry();
//property names of the Person class
String[] propertyNames = {"firstName", "lastName", "gender", "married",
"address.street", "address.housenumber", "address.postalCode", "address.city",
"age", "birthday", "money",
"description", "favouriteFood", "favouriteDrinks"};
//mapping from property to label, needed for column header labels
Map<String, String> propertyToLabelMap = new HashMap<String, String>();
propertyToLabelMap.put("firstName", "Firstname");
propertyToLabelMap.put("lastName", "Lastname");
propertyToLabelMap.put("gender", "Gender");
propertyToLabelMap.put("married", "Married");
propertyToLabelMap.put("address.street", "Street");
propertyToLabelMap.put("address.housenumber", "Housenumber");
propertyToLabelMap.put("address.postalCode", "Postalcode");
propertyToLabelMap.put("address.city", "City");
propertyToLabelMap.put("age", "Age");
propertyToLabelMap.put("birthday", "Birthday");
propertyToLabelMap.put("money", "Money");
propertyToLabelMap.put("description", "Description");
propertyToLabelMap.put("favouriteFood", "Food");
propertyToLabelMap.put("favouriteDrinks", "Drinks");
IColumnPropertyAccessor<ExtendedPersonWithAddress> columnPropertyAccessor =
new ExtendedReflectiveColumnPropertyAccessor<ExtendedPersonWithAddress>(propertyNames);
BodyLayerStack<ExtendedPersonWithAddress> bodyLayer =
new BodyLayerStack<ExtendedPersonWithAddress>(PersonService.getExtendedPersonsWithAddress(10),
columnPropertyAccessor,
sndColumnGroupModel, columnGroupModel);
IDataProvider columnHeaderDataProvider = new DefaultColumnHeaderDataProvider(propertyNames, propertyToLabelMap);
DataLayer columnHeaderDataLayer = new DataLayer(columnHeaderDataProvider);
ILayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, bodyLayer, bodyLayer.getSelectionLayer());
SortHeaderLayer<ExtendedPersonWithAddress> sortHeaderLayer =
new SortHeaderLayer<ExtendedPersonWithAddress>(columnHeaderLayer,
new GlazedListsSortModel<ExtendedPersonWithAddress>(bodyLayer.getSortedList(),
columnPropertyAccessor, configRegistry, columnHeaderDataLayer));
ColumnGroupHeaderLayer columnGroupHeaderLayer = new ColumnGroupHeaderLayer(sortHeaderLayer, bodyLayer.getSelectionLayer(), columnGroupModel);
columnGroupHeaderLayer.addColumnsIndexesToGroup("Person", 0, 1, 2, 3);
columnGroupHeaderLayer.addColumnsIndexesToGroup("Address", 4, 5, 6, 7);
columnGroupHeaderLayer.addColumnsIndexesToGroup("Facts", 8, 9, 10);
columnGroupHeaderLayer.addColumnsIndexesToGroup("Personal", 11, 12, 13);
columnGroupHeaderLayer.setStaticColumnIndexesByGroup("Person", 0, 1);
columnGroupHeaderLayer.setStaticColumnIndexesByGroup("Address", 4, 5, 6);
ColumnGroupGroupHeaderLayer sndGroup =
new ColumnGroupGroupHeaderLayer(columnGroupHeaderLayer, bodyLayer.getSelectionLayer(), sndColumnGroupModel);
sndGroup.addColumnsIndexesToGroup("PersonWithAddress", 0, 1, 2, 3, 4, 5, 6, 7);
sndGroup.addColumnsIndexesToGroup("Additional Information", 8, 9, 10, 11, 12, 13);
sndGroup.setStaticColumnIndexesByGroup("PersonWithAddress", 0, 1);
// Note: The column header layer is wrapped in a filter row composite.
// This plugs in the filter row functionality
FilterRowHeaderComposite<ExtendedPersonWithAddress> filterRowHeaderLayer =
new FilterRowHeaderComposite<ExtendedPersonWithAddress>(
new DefaultGlazedListsFilterStrategy<ExtendedPersonWithAddress>(bodyLayer.getFilterList(), columnPropertyAccessor, configRegistry),
sndGroup, columnHeaderDataLayer.getDataProvider(), configRegistry
);
DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(bodyLayer.getBodyDataProvider());
DataLayer rowHeaderDataLayer = new DataLayer(rowHeaderDataProvider);
rowHeaderDataLayer.setDefaultColumnWidth(40);
ILayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, bodyLayer, bodyLayer.getSelectionLayer());
ILayer cornerLayer = new CornerLayer(
new DataLayer(
new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider)),
rowHeaderLayer, filterRowHeaderLayer);
GridLayer gridLayer = new GridLayer(bodyLayer, filterRowHeaderLayer, rowHeaderLayer, cornerLayer);
NatTable natTable = new NatTable(parent, gridLayer, false);
natTable.setConfigRegistry(configRegistry);
natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
//add filter row configuration
natTable.addConfiguration(new FilterRowConfiguration());
natTable.configure();
return natTable;
}
class BodyLayerStack<T> extends AbstractLayerTransform {
private final SortedList<T> sortedList;
private final FilterList<T> filterList;
private final IDataProvider bodyDataProvider;
private ColumnReorderLayer columnReorderLayer;
private ColumnGroupReorderLayer columnGroupReorderLayer;
private ColumnHideShowLayer columnHideShowLayer;
private ColumnGroupExpandCollapseLayer columnGroupExpandCollapseLayer;
private SelectionLayer selectionLayer;
private ViewportLayer viewportLayer;
public BodyLayerStack(List<T> values, IColumnPropertyAccessor<T> columnPropertyAccessor,
ColumnGroupModel... columnGroupModel) {
//wrapping of the list to show into GlazedLists
//see http://publicobject.com/glazedlists/ for further information
EventList<T> eventList = GlazedLists.eventList(values);
TransformedList<T, T> rowObjectsGlazedList = GlazedLists.threadSafeList(eventList);
//use the SortedList constructor with 'null' for the Comparator because the Comparator
//will be set by configuration
this.sortedList = new SortedList<T>(rowObjectsGlazedList, null);
// wrap the SortedList with the FilterList
this.filterList = new FilterList<T>(getSortedList());
this.bodyDataProvider =
new ListDataProvider<T>(filterList, columnPropertyAccessor);
DataLayer bodyDataLayer = new DataLayer(this.bodyDataProvider);
//layer for event handling of GlazedLists and PropertyChanges
GlazedListsEventLayer<T> glazedListsEventLayer =
new GlazedListsEventLayer<T>(bodyDataLayer, filterList);
columnReorderLayer = new ColumnReorderLayer(glazedListsEventLayer);
columnGroupReorderLayer = new ColumnGroupReorderLayer(columnReorderLayer, columnGroupModel[columnGroupModel.length-1]);
columnHideShowLayer = new ColumnHideShowLayer(columnGroupReorderLayer);
columnGroupExpandCollapseLayer = new ColumnGroupExpandCollapseLayer(columnHideShowLayer, columnGroupModel);
selectionLayer = new SelectionLayer(columnGroupExpandCollapseLayer);
viewportLayer = new ViewportLayer(selectionLayer);
setUnderlyingLayer(viewportLayer);
}
public SortedList<T> getSortedList() {
return sortedList;
}
public FilterList<T> getFilterList() {
return filterList;
}
public IDataProvider getBodyDataProvider() {
return bodyDataProvider;
}
public ColumnReorderLayer getColumnReorderLayer() {
return columnReorderLayer;
}
public ColumnGroupReorderLayer getColumnGroupReorderLayer() {
return columnGroupReorderLayer;
}
public ColumnHideShowLayer getColumnHideShowLayer() {
return columnHideShowLayer;
}
public ColumnGroupExpandCollapseLayer getColumnGroupExpandCollapseLayer() {
return columnGroupExpandCollapseLayer;
}
public SelectionLayer getSelectionLayer() {
return selectionLayer;
}
public ViewportLayer getViewportLayer() {
return viewportLayer;
}
}
/**
* The configuration to enable the edit mode for the grid and additional
* edit configurations like converters and validators.
*
* @author Dirk Fauth
*/
class FilterRowConfiguration extends AbstractRegistryConfiguration {
@Override
public void configureRegistry(IConfigRegistry configRegistry) {
//register the FilterRowTextCellEditor in the first column which immediately commits on key press
configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR,
new FilterRowTextCellEditor(),
DisplayMode.NORMAL,
FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + DataModelConstants.FIRSTNAME_COLUMN_POSITION);
//register a combo box cell editor for the gender column in the filter row
//the label is set automatically to the value of
//FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + column position
ICellEditor comboBoxCellEditor = new ComboBoxCellEditor(Arrays.asList(Gender.FEMALE, Gender.MALE));
configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR,
comboBoxCellEditor,
DisplayMode.NORMAL,
FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + DataModelConstants.GENDER_COLUMN_POSITION);
//register a combo box cell editor for the married column in the filter row
//the label is set automatically to the value of
//FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + column position
comboBoxCellEditor = new ComboBoxCellEditor(Arrays.asList(Boolean.TRUE, Boolean.FALSE));
configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR,
comboBoxCellEditor,
DisplayMode.NORMAL,
FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + DataModelConstants.MARRIED_COLUMN_POSITION);
configRegistry.registerConfigAttribute(
FilterRowConfigAttributes.TEXT_MATCHING_MODE,
TextMatchingMode.EXACT,
DisplayMode.NORMAL,
FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + DataModelConstants.GENDER_COLUMN_POSITION);
configRegistry.registerConfigAttribute(FilterRowConfigAttributes.TEXT_DELIMITER, "&"); //$NON-NLS-1$
}
}
}
|
|
|
Re: Misbehaving two-level column groups [message #1281698 is a reply to message #1281380] |
Tue, 01 April 2014 08:22 |
István Mészáros Messages: 51 Registered: October 2009 |
Member |
|
|
Hello Dirk,
Thanks for the answer! Yes, i'm using static columns. My layer topology seems to be right according to your code, but i created a graph about it, if you have a minute please take a look on it.
I also created a video about the overflow glitch:
https://www.youtube.com/watch?v=BAt78UYEhvk
When i tried to solve this issue, i first started to debug getColumnSpan in ColumnGroupGroupHeaderLayer. I noticed that this method somehow takes into account if a column is not visible. For example, if there is a column group with 8 columns, and 4 of those columns are scrolled off the screen, getColumnSpan returns 4 in ColumnGroupGroupHeaderLayer. But when the group is collapsed, it returns the number of static columns, which does not seem to be the correct value for me.
For now, my solution is to override getColumnSpan() with this code:
@Override
protected int getColumnSpan(int columnPosition) {
int columnIndex = getColumnIndexByPosition(columnPosition);
final ColumnGroup columnGroup = model.getColumnGroupByIndex(columnIndex);
int leastPossibleStartPositionOfGroup = columnPosition - (columnGroup.getSize() - 1);
int i = 0;
for (i = leastPossibleStartPositionOfGroup; i < columnPosition; i++) {
if (ColumnGroupUtils.isInTheSameGroup(getColumnIndexByPosition(i), columnIndex, model)) {
break;
}
}
int sizeOfGroup = columnGroup.getSize();
int endPositionOfGroup = i + sizeOfGroup;
final List<Integer> columnIndexesInGroup = columnGroup.getMembers();
for (; i < endPositionOfGroup; i++) {
int index = getColumnIndexByPosition(i);
if (!columnIndexesInGroup.contains(Integer.valueOf(index))) {
sizeOfGroup--;
}
}
return sizeOfGroup;
}
This is a mixture of the super implementation of getColumnpan() and getStartPositionOfGroup() since it's not visible for extending classes.
I also tried this solution when there are no static columns defined. In this case, when the group is collapsed, only the first column is visible, scrolling works properly, the group does not vanish.
|
|
| | |
Re: Misbehaving two-level column groups [message #1282360 is a reply to message #1282352] |
Wed, 02 April 2014 07:18 |
István Mészáros Messages: 51 Registered: October 2009 |
Member |
|
|
Hello Dirk,
i'm using the latest snapshot. Your example only needs a little modification to expose the flaw (if it's really a flaw).
Change the very end of the BodyLayerStack's constructor to:
viewportLayer = new ViewportLayer(selectionLayer);
FreezeLayer freezeLayer = new FreezeLayer(selectionLayer);
CompositeFreezeLayer compositeFreezeLayer = new CompositeFreezeLayer(freezeLayer, viewportLayer, selectionLayer);
setUnderlyingLayer(compositeFreezeLayer);
This way the scrolling text issues comes up as well as the overflowing issue when the group is collapsed.
To be honest, i like the scrolling text "issue", i thought that was a feature It is useful because the column header text remains readable as long as possible.
So it seems the freeze layer causes the problem. Is this the correct way to introduce the freeze layer?
About cell painters: nothing special, i use the following configuration for group group header:
final ICellPainter textPainter = new TextPainter(true, true);
final ICellPainter padddingPainter = new PaddingDecorator(textPainter, 4);
final ICellPainter borderPainter = new LineBorderDecorator(padddingPainter);
final ICellPainter sortHeaderPainter = new PaddingDecorator(
new SortableHeaderTextPainter(borderPainter, true, false), 0, 2, 0, 0);
final ICellPainter columnGroupHeaderPainter = new ColumnGroupHeaderTextPainter(padddingPainter);
[Updated on: Wed, 02 April 2014 07:20] Report message to a moderator
|
|
| | | | | | | |
Goto Forum:
Current Time: Tue Sep 24 22:26:56 GMT 2024
Powered by FUDForum. Page generated in 0.06387 seconds
|