I would like to use column percentage sizing to force the table to take on the width of the parent.
This does not work when I hide column(s) by default because the setColumnPercentageSizing() method does not seem to exclude hidden columns and does not correctly calculate the width.
Is there an easy way to adjust this in my code?
Example:
public void example(){
createGlazedListsGridLayer();
autoResizeColumns();
nattable.configure();
}
public GlazedListsGridLayer createGlazedListsGridLayer(){
SortedList<T> sortedList = new SortedList<>(eventList, null);
this.bodyDataProvider = new ListDataProvider<>(sortedList,
columnPropertyAccessor);
this.bodyDataLayer = new DataLayer(this.bodyDataProvider);
ColumnHideShowLayer columnHideShowLayer = new
ColumnHideShowLayer(bodyDataLayer);
// In this example, hide the first column
columnHideShowLayer.hideColumnPositions(Lists.newArrayList(0));
this.bodyLayerStack = new DefaultBodyLayerStack(new
GlazedListsEventLayer<>(columnHideShowLayer, eventList));
//...etc
}
protected void autoResizeColumns() {
glazedListsGridLayer.getBodyDataLayer().setColumnPercentageSizing(true);
nattable.addConfiguration(new DefaultNatTableStyleConfiguration() {
{
cellPainter = new LineBorderDecorator(new TextPainter(false,
true, 5, true));
}
});
}
UPDATE
It's not ideal but this is the closest I could get to it
public void adjustColumnWidth() {
getBodyDataLayer().setColumnPercentageSizing(false);
// Avoid the first column since it's hidden
for (int x = 1; x <= numColumns; x++) {
getBodyDataLayer().setColumnWidthByPosition(x,
getParent().getSize().x / numColumns, true);
}
}
UPDATE 2
Here are a couple of different things I tried in various combinations. None of them seem to keep the column hidden after a table is dynamically populated with data.
protected void enableAutoResizeColumns() {
getBodyDataLayer().setColumnPercentageSizing(true);
getBodyDataLayer().setDefaultColumnWidthByPosition(0, 0);
getBodyDataLayer().setColumnWidthByPosition(0, 0);
getBodyDataLayer().setColumnWidthPercentageByPosition(0, 0);
getNatTable().addConfiguration(new
DefaultNatTableStyleConfiguration() {
{
cellPainter = new LineBorderDecorator(new TextPainter
(false, true, 5, true));
}
});
}
Currently there is no solution for that. The reason for this is that the column widths are calculated in the DataLayer. The ColumnHideShowLayer sits on top of it and simply hides columns. It doesn't communicate back to the DataLayer that something is hidden.
In the end the ColumnHideShowLayer would need to re-trigger percentage size calculation based on the hidden state. But there is currently no API for that.
Feel free to create an enhancement ticket and provide a patch if you have an idea how to solve it.
Related
We have fairly complicated model in a JTable. In new development I noticed that GUI does not refresh when I call fireTableChanged(...) for individual cells.
So, my question is:
What do I put into TableModelEvent - model row id or view row id?
Looking into JTable code (I have jdk1.8.0_202):
public class JTable extends JComponent implements TableModelListener, Scrollable,
...
public void tableChanged(TableModelEvent e) {
...
int modelColumn = e.getColumn();
int start = e.getFirstRow();
int end = e.getLastRow();
Rectangle dirtyRegion;
if (modelColumn == TableModelEvent.ALL_COLUMNS) {
// 1 or more rows changed
dirtyRegion = new Rectangle(0, start * getRowHeight(),
getColumnModel().getTotalColumnWidth(), 0);
}
else {
// A cell or column of cells has changed.
// Unlike the rest of the methods in the JTable, the TableModelEvent
// uses the coordinate system of the model instead of the view.
// This is the only place in the JTable where this "reverse mapping"
// is used.
int column = convertColumnIndexToView(modelColumn);
dirtyRegion = getCellRect(start, column, false);
}
I see that in order to calculate dirty region, it converts column index, but does not do the same for row.
What does "reverse mapping" comment mean?
Looks like a bug in Swing to me.
What do you think?
UPDATE
My code is simple:
model.fireTableChanged(new TableModelEvent(model, rowNumber, rowNumber, columnNumber));
GUI does NOT refresh the cell.
UPDATE2
The issue is in my model which is too complicated to post it here. :(
I cannot blame JTable. It is designed this way. The only possible addition to it is RowSorter, and in there it does correct conversion:
private void repaintSortedRows(ModelChange change) {
...
int modelIndex = change.startModelIndex;
while (modelIndex <= change.endModelIndex) {
int viewIndex = convertRowIndexToView(modelIndex++);
if (viewIndex != -1) {
Rectangle dirty = getCellRect(viewIndex, columnViewIndex,
false);
int x = dirty.x;
int w = dirty.width;
if (eventColumn == TableModelEvent.ALL_COLUMNS) {
x = 0;
w = getWidth();
}
repaint(x, dirty.y, w, dirty.height);
}
}
}
Thanks everybody. Sorry for disturbance.
My code is simple:
model.fireTableChanged(new TableModelEvent(model, rowNumber, rowNumber, columnNumber));
That is not how you change data in a JTable. You should NOT be invoking that method directly. It is the responsibility of the TableModel to invoke that method when data is changed.
The point of using a TableModelListener is to listen for changes in the TableModel. You only need to implement the listener if you want to do special processing AFTER the data has changed as I demonstrated in the link I provided in my comment.
If you have data in an existing cell and you want to change its value then can do something like:
model.setValueAt("new value", 0, 0);
If you want to add a new row of data you use:
model.addRow(...);
The point is all changes should be done via the TableModel.
Note the JTable also has a convenience setValueAt(...) method which will invoke the model for you.
I'm trying to make a simple 3-column TableView:
One icon column containing a fixed size icon. This column must not be resizable, and must have a fixed size.
One text column with a predefined prefered size, which can be resized if needed.
One last column taking all available space.
Unfortunatly, this simple use case seems to be really complicated with Java FX 8. I tried the following, which should work according to my understanding of the documentation:
TableColumn<DebuggerItem, ImageView> iconColumn = new TableColumn<>("ICON");
TableColumn<DebuggerItem, String> typeColumn = new TableColumn<>("TEXT");
TableColumn<DebuggerItem, String> textColumn = new TableColumn<>("DATA");
setColumnResizePolicy(CONSTRAINED_RESIZE_POLICY);
// Fixed size column
iconColumn.setPrefWidth(40);
iconColumn.setMinWidth(40);
iconColumn.setMaxWidth(40);
iconColumn.setResizable(false);
// Predefined preferred size of 100px
typeColumn.setPrefWidth(100);
getColumns().addAll(iconColumn, typeColumn, textColumn);
This results in the following TableView:
We can see that if the first column has a correct size, the second and the hird have the same size, which is not what I expected. The second column should be 100px wide, and the last one take the rest of the space.
What did I miss ?
According to #kleopatra link, the solution is to use properties to compute last column width, and NOT use CONSTRAINED_RESIZE_POLICY :
LastColumnWidth = TableViewWidth - SUM(Other Columns Widths)
Which, according to my example give the following Java code:
TableColumn<DebuggerItem, ImageView> iconColumn = new TableColumn<>("ICON");
TableColumn<DebuggerItem, String> typeColumn = new TableColumn<>("TEXT");
TableColumn<DebuggerItem, String> textColumn = new TableColumn<>("DATA");
// Fixed size column
iconColumn.setPrefWidth(40);
iconColumn.setMinWidth(40);
iconColumn.setMaxWidth(40);
iconColumn.setResizable(false);
// Predefined preferred size of 100px
typeColumn.setPrefWidth(100);
// Automatic width for last column
textColumn.prefWidthProperty().bind(
widthProperty().subtract(
iconColumn.widthProperty()).subtract(
typeColumn.widthProperty()).subtract(2)
);
getColumns().addAll(iconColumn, typeColumn, textColumn);
Please note that we need to substract 2 pixels to get the exact width, it's not clear why.
I created a more general solution, that pixel-perfectly calculates width of last column for TableViews that:
have any number of columns
possibly hide or show columns at runtime
possibly have custom insets
possibly have scrollbar, possibly showing and hiding at runtime.
Usage:
bindLastColumnWidth(tableView);
Source:
public static <T> void bindLastColumnWidth (TableView<T> tableView) {
List<TableColumn<T,?>> columns = tableView.getColumns();
List<TableColumn<T,?>> columnsWithoutLast = columns.subList(0, columns.size() - 1);
TableColumn lastColumn = columns.get(columns.size() - 1);
NumberExpression expression = tableView.widthProperty();
Insets insets = tableView.getInsets();
expression = expression.subtract(insets.getLeft() + insets.getRight());
for (TableColumn column : columnsWithoutLast) {
NumberExpression columnWidth = Bindings.when(column.visibleProperty())
.then(column.widthProperty())
.otherwise(0);
expression = expression.subtract(columnWidth);
}
ScrollBar verticalScrollBar = getScrollBar(tableView, Orientation.VERTICAL);
if (verticalScrollBar != null) {
NumberExpression scrollBarWidth = Bindings.when(verticalScrollBar.visibleProperty())
.then(verticalScrollBar.widthProperty())
.otherwise(0);
expression = expression.subtract(scrollBarWidth);
}
expression = Bindings.max(lastColumn.getPrefWidth(), expression);
lastColumn.prefWidthProperty().bind(expression);
}
private static ScrollBar getScrollBar (Node control, Orientation orientation) {
for (Node node : control.lookupAll(".scroll-bar")) {
if (node instanceof ScrollBar) {
ScrollBar scrollBar = (ScrollBar)node;
if (scrollBar.getOrientation().equals(orientation)) {
return scrollBar;
}
}
}
return null;
}
You can call this below code after you add all columns to table.
This is nice when the code that resizes does not have a explicit list of columns.
public static void makeLastColumnGrow(TableView<?> items) {
var last = items.getColumns().get(items.getColumns().size() - 1);
var aBinding = Bindings.createDoubleBinding(() ->{
double width = items.getWidth();
for (int i = 0; i < items.getColumns().size() - 1 ; i++) {
width = width - items.getColumns().get(i).getWidth();
}
return width - 2; // minus -2 to account for border i think
}, items.widthProperty());
last.prefWidthProperty().bind(aBinding);
}
Is it possible to control whether a column should be available in a column control popup menu? I'm aware of toggling (Disable/enable using CheckBoxList) and gray-out the column. But I do not want column entry in popup menu as The column is must-have column in Jtable. I'm using the JXTable. Anyone that have any hints?
A TableColumnExt has a property hideable which effectly disables the hiding. It is still shown in the popup and you can toggle the checkbox (that's a bug, just filed - the menu item should be disabled ;), but at least the column isn't hidden. To work around the bug, you can implement a custom column control (as Robin correctly suggested) which doesn't add the checkbox, something like:
JXTable table = new JXTable(new AncientSwingTeam());
// here the hideable property is configured manually,
// in production code you'll probably have a custom ColumnFactory
// doing it based on some data state
table.getColumnExt(0).setHideable(false);
ColumnControlButton columnControl = new ColumnControlButton(table) {
#Override
protected ColumnVisibilityAction createColumnVisibilityAction(
TableColumn column) {
if (column instanceof TableColumnExt
&& !((TableColumnExt) column).isHideable())
return null;
return super.createColumnVisibilityAction(column);
}
};
table.setColumnControl(columnControl);
table.setColumnControlVisible(true);
As to not including the menu item: when introducing the hideable property, we decided to go for keeping the item in the list but disable it because users might get confused not seeing all columns in the control. So once the bug will be fixed (just done, committed as of revision #4315), I would recommend to remove the custom column control again. Just my 2 euro-cents, though :-)
ColumnControlButton#createColumnVisibilityAction looks like the method you are looking for. According to the documentation:
Creates and returns a ColumnVisibilityAction for the given TableColumn. The return value might be null, f.i. if the column should not be allowed to be toggled
you can return null for your case.
You should be able to plug this in by using the JXTable#setColumnControl method.
First way:
myTable().getColumnExt(_column_number_).setHideable(false);
This works smooth but has one UI drawback: text in menu is gray and thick is black - bad user experience.
So try to fix it, text will be gray and thick won't be here:
public class MyTable extends JXTable
{
public MyTable(AbstractTableModel model)
{
//first two columns won't be hiddeable
ColumnControlButton controlButton = new ColumnControlButton(this)
{
#Override
protected ColumnControlPopup createColumnControlPopup()
{
return (new NFColumnControlPopup());
}
class NFColumnControlPopup extends DefaultColumnControlPopup
{
#Override
public void addVisibilityActionItems(List<? extends AbstractActionExt> actions)
{
for(int i = 0; i < actions.size(); i++)
{
AbstractActionExt action = actions.get(i);
JCheckBoxMenuItem chk = new JCheckBoxMenuItem(action);
//Disabling unwanted items but they will be still shown for smooth user experience
if(i == 0 || i == 1)
{
chk.setEnabled(false);
chk.setSelected(false);
//chk.setIcon(new ImageIcon(Icons.class.getResource("check.png")));
}
else
{
chk.setSelected(true);
}
chk.addItemListener(action);
super.addItem(chk);
}
}
}
};
this.setColumnControl(controlButton);
}
}
and if you need to hide controls for "show horizontal scrollbar", "pack" and "pack all" add into code:
//remove items for horizontal scrollbar, pack and packall
this.getActionMap().remove("column.horizontalScroll");
this.getActionMap().remove("column.packAll");
this.getActionMap().remove("column.packSelected");
right after calling super(model)
I have create a table with bars which shows frequency of the words in a text.I show the number of special word which user click on them or frequency of whole words in the text. I fetch my list of list and send it to the fill table function. All thing is OK but when I select a special word and then click to show whole words I get indexoutofbounds exception. I guess it is because I change my datasource. It is really strange but simple. However, I could not solve it.
public void fill_count_table(List<RootWordSet> source){
final List<RootWordSet> mysource=source;
if(source!=null){
for(int i=0;i<source.size();i++){
TableItem ti=new TableItem(count_table, SWT.NONE);
ti.setText(source.get(i).getRoot());
}
count_table.addListener(SWT.PaintItem, new Listener() {
public void handleEvent(Event event) {
if (event.index == 1) {
try{
GC gc = event.gc;
TableItem item = (TableItem)event.item;
int index = count_table.indexOf(item);
System.out.println(mysource.size());
int percent = mysource.get(index).getWordNumber();
org.eclipse.swt.graphics.Color foreground = gc.getForeground();
org.eclipse.swt.graphics.Color background = gc.getBackground();
gc.setForeground(Display_1.getSystemColor(SWT.COLOR_BLUE));
gc.setBackground(Display_1.getSystemColor(SWT.COLOR_DARK_BLUE));
int width = (tc2.getWidth() - 1) * percent / 100;
gc.fillGradientRectangle(event.x, event.y, width, event.height, true);
Rectangle rect2 = new Rectangle(event.x, event.y, width-1, event.height-1);
gc.drawRectangle(rect2);
gc.setForeground(Display_1.getSystemColor(SWT.COLOR_RED));
String text = Integer.toString(percent) ;
Point size = event.gc.textExtent(text);
int offset = Math.max(0, (event.height - size.y) / 2);
gc.drawText(text, event.x+2, event.y+offset, true);
gc.setForeground(background);
gc.setBackground(foreground);
}
catch(Exception e){
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
});
}
else{
count_table.removeAll();
count_table.redraw();
}
}
this is the line that make error: int percent = mysource.get(index).getWordNumber();
I really do not know what happens when I change datasources. when I shift to each other it stuck. Even I put a println to check the size of datasource but it was quite strange it had two size. One belong to former datasource and one belong to newwr. Anyway If I remove this graphic part table fill correctly. What do you think?
change this line:
int percent = mysource.get(index).getWordNumber();
to
int percent = mysource.get(index-1).getWordNumber();
To disable vertical scroll bar i used the following syntax
table.getHorizontalBar().setEnabled(false);
But it is not working. It is ruining my application ui. How can i disable it?
Use option SWT.NO_SCROLL and SWT.V_SCROLL while constructing the table as following:
new Table (shell, SWT.NO_SCROLL | SWT.V_SCROLL);
You can't prevent the Table from showing its scrollbars if it wants to. However, if you give the table the space it requires, it should not have to display any scrollbars.
Note:
You can simply use SWT.NO_SCROLL in the constructor but if you want to update it later it won't be possible.
ScrollBar / Table position synchronization in Java SWT turorial
Probably, if you you want to disable a scrollbar, it means you want to synchronize the content with something else. Here is example how to do it with two tables, where content of main table called tableExcel is mirrored in extra table called tableRow (please, be aware the code may not be perfect, because I am beginner):
*Note: the code is not complete & shows only key things (beginners, please use Window Builder and then edit/add code):
1) As ankur.trapasiya mentioned, do this:
tableRow = new Table(sashFormExcel, SWT.BORDER | SWT.VIRTUAL | SWT.NO_SCROLL);
2) And (SWT.VIRTUAL will be required to do the workaround, and to load tables fast, it will load what you see & where you scroll)
tableExcel = new Table(sashFormExcel, SWT.BORDER | SWT.VIRTUAL);
3) Because I used sash-form for better navigation, insert this above point 1:
SashForm sashFormExcel = new SashForm(sashForm_Main, SWT.NONE);
4) Then implement this (after point 3):
synchronizer synchronize = new synchronizer(tableRow, tableExcel); tableExcel.getVerticalBar().addSelectionListener(synchronize);
5) Add this class (will synchronize the tables content position):
class synchronizer implements SelectionListener
{
Table t1, t2;
public synchronizer(Table tableRow, Table tableMain)
{
t1 = tableRow;
t2 = tableMain;
}
#Override
public void widgetSelected(SelectionEvent e) {
t1.setTopIndex(t2.getTopIndex());
}
#Override
public void widgetDefaultSelected(SelectionEvent e) {
// TODO Auto-generated method stub
}
}
6) And after point 2, add this (it will make to load main table fast, and will also synchronize your table where scroll bar is disabled):
tableExcel.addListener( SWT.SetData, new Listener() {
public void handleEvent( Event event ) {
TableItem item = (TableItem) event.item;
int index = event.index;
int page = index / PAGE_SIZE;
int start = page * PAGE_SIZE;
int end = start + PAGE_SIZE;
end = Math.min (end, virtTableExcel.size());
for (int i = start; i < end; i++) {
item = tableExcel.getItem (i);
item.setText (virtTableExcel.get(i));
tableRow.getItem(i).setText(Integer.toString(i));
}
}
});
And on top of code, add this:
private static Table tableExcel;
ArrayList<String[]> virtTableExcel = new ArrayList<String[]>();
final int PAGE_SIZE = 64;
private Table tableRow;
As it is mentioned in point 7, you take data from array list virtTableExcel, but to trigger point 6, use this (somewhere in code, after you generated virtTableExcel with data), where rowMax is integer which is equal to virtTableExcel.size(); :tableExcel.setItemCount(rowMax);
tableRow.setItemCount(rowMax);
Please, don't blame me for code, I used it from my application. But, all these bits might be useful for other beginners.
What work for me:
When declaring the TableViewer I do this:
this.viewer.getTable().getHorizontalBar().setVisible(false);
this.viewer.getTable().pack();
this.viewer.getTable().getParent().layout();
this.viewer.getTable().getParent().getParent().layout();
and when i am changing the viewer input i also call the three last lines.
I got this solution from: https://bugs.eclipse.org/bugs/show_bug.cgi?id=304128
Try
table.getHorizontalBar().setVisible(false);
Is't simple:
leftTable.getVerticalBar().addListener(SWT.Selection, event -> {
rightTableViewer.getTable().setTopIndex(leftTableViewer.getTable().getTopIndex());
});
and vice versa.