I have a TableViewer widget with a single column, where each row in the table's contents is a Composite with a number of child widgets. The column has a label provider that is a subclass of OwnerDrawLabelProvider. The label provider is supposed to render the Composite children in the table cell, but when paint() is called, nothing is rendered.
I have found a number of examples on how to draw plain or styled text items, or primitives using the GC in the Event object passed in, but nothing on drawing the contents of a Composite in the cell's area.
Is this possible, and if so, what am I doing wrong?
Here is the code to create the table:
viewer = new TableViewer(container, SWT.NONE);
final Table table = viewer.getTable();
TableViewerColumn viewerColumn = new TableViewerColumn(viewer, SWT.LEFT);
TableColumn tableColumn = viewerColumn.getColumn();
tableColumn.setText(Messages.getString("column.header.name"));
tableColumn.setResizable(true);
tableColumn.setMoveable(false);
tableColumn.setWidth(500);
viewerColumn.setLabelProvider(new CompositeLabelProvider());
Here are the measure and paint methods for the custom label provider, CompositeLabelProvider:
#Override
protected void measure(Event event, Object element) {
CompositeTableItem row = rowMap.get(element);
Composite contents = row.getContents(viewer.getTable().getParent());
Point size = contents.computeSize(SWT.DEFAULT, SWT.DEFAULT);
event.setBounds(new Rectangle(event.x, event.y, size.x, size.y));
}
#Override
protected void paint(Event event, Object element) {
CompositeTableItem row = rowMap.get(element);
Composite contents = row.getContents(viewer.getTable().getParent());
contents.redraw(event.getBounds().x, event.getBounds().y, event.getBounds().width, event.getBounds().height, true);
contents.update();
}
I think OwnerDrawLabelProvider is meant to be used if you want to draw your own component using the GC. If you only want render a normal SWT widget in a table you should probably use TableEditor and #setEditor(Control) to set the control you want to show in the table cell.
Related
The last column of my Table Viewer contains a check box only. The check box appears in the left side of the cell, and because the column name is pretty long it looks ugly as hell. How can I center the check box in the middle of the cell ? Is it possible without using images ? Here is how I create the column:
// third column - check box. temporary
TableColumn column = new TableColumn(viewer.getTable(), SWT.NONE);
column.setText("PrettyLongColumnName");
column.setWidth(100);
TableViewerColumn checkColumn = new TableViewerColumn(viewer, column);
checkColumn.setLabelProvider(new ColumnLabelProvider() {
// the checkboxes should be disposed and rebuilt when input changes
#Override
public void update(ViewerCell cell) {
MyObject system = (MyObject) cell.getElement();
TableItem item = (TableItem) cell.getItem();
Button button;
if (buttonsMap.containsKey(cell.getElement())) {
button = rightTableButtons.get(cell.getElement());
} else {
button = new Button((Composite) cell.getViewerRow().getControl(), SWT.CHECK);
button.setEnabled(true);
buttonsMap.put(cell.getElement(), button);
TableEditor editor = new TableEditor(item.getParent());
editor.grabHorizontal = true;
editor.grabVertical = true;
editor.setEditor(button, item, cell.getColumnIndex());
editor.layout();
}
}
}
});
TL;DR: Not available nativley, but can be implemented via epic hackery.
Checkboxes are actually an OS feature. As SWT is cross-platform, we rely on it being provided by OS.
AFIK the only thing provided by all OS's (Gtk/Win32/Cocoa) is a single checkbox on the first column.
Other
fancy functionality has to be implemented manually.
One way I've seen people do it is to draw custom icons and then update the icon when you click on it with event listeners.
One example on how to draw icons on the right is in this snippet. You'd have to add click listener to change the icon when clicked into checked/unchecked.
Note, this may cause your application to look inconsistent across platforms and themes (e.g dark theme).
To get around this, we have had some people that actually generate a native checkbox, then programatically take a screen shot, then draw it in the right side of a column. I think this is hackery at it's finest.
Let me know if you have questions.
I made .pack file and load it to skin. I can set it as drawable for Image class and it works fine. But Table.setBackground do nothing.
Drawable background = skin.getDrawable("tooltip_background");
tooltipGroup.setBackground(background);
Why this code not work?
Full code:
tooltipGroup = new Table(skin);
tooltipGroup.setWidth(w/6);
Label.LabelStyle headerStyle = new Label.LabelStyle(headerFont, Color.GREEN);
Label headerLabel = new Label(effect.getName(), headerStyle);
headerLabel.setWidth(w/6);
headerLabel.setX(20);
headerLabel.setWrap(true);
headerLabel.setAlignment(Align.topLeft);
tooltipGroup.addActor(headerLabel);
Label.LabelStyle style = new Label.LabelStyle(font, Color.WHITE);
Label descriptionLabel = new Label(effect.getDescription(), style);
descriptionLabel.setWidth(w/6);
descriptionLabel.setWrap(true);
descriptionLabel.setAlignment(Align.topLeft);
descriptionLabel.setPosition(20, -descriptionLabel.getHeight());
tooltipGroup.addActor(descriptionLabel);
tooltipGroup.setPosition(mouseX, h - mouseY);
Drawable background = skin.getDrawable("tooltip_background");
tooltipGroup.setBackground(background);
stage.addActor(tooltipGroup);
Don't use addActor on the Table. In order to add Actors to the table, use the add method, and specify custom parameters like fill or width / height of the actors in chaining methods. Like this, you can control how you add the actors to the table, since the table does auto layouting.
Table rootTable = new Table();
rootTable.setFillParent(true);
TextField fieldFirst = new TextField("First", skin);
TextField fieldSecond = new TextField("Second", skin);
rootTable.add("First:");
rootTable.add(fieldFirst).width(100).row();
rootTable.add("Second:");
rootTable.add(fieldSecond).width(100);
stage.addActor(rootTable);
As you can see, this allows you to specify the width, in the table, with the expand(), and fill() methods, you can even have an element fill all the available space within one row. Once you use this auto layouting of your table, the table should have the appropriate size, and your background should show. More on how to use the libGdx table layouting can be found here.
I am working with swing JTable and have a trouble with repainting table. I draw a JTable with thr following code
Object[] column = new Object[]{"Entity", "Attribute"};
Object[][] rowData = new Object[][]{{"E1", "A1"},{"E2", "A2"}};
TableCellRenderer cellRenderer = new TableCellRenderer();
JTable table = new JTable(new DefaultTableModel(rowData, column));
table.setCellSelectionEnabled(true);
table.getColumnModel().getColumn(0).setCellRenderer(cellRenderer);
Below is my table renderer code ..
public class TableCellRenderer implements javax.swing.table.TableCellRenderer {
//private JPanel panel;
JTextField field;
private JTable table;
#Override
public Component getTableCellRendererComponent(final JTable table, final Object value,
boolean isSelected, boolean hasFocus, final int row, final int column) {
this.table = table;
//JTextField field = null;
System.out.println("Rendere : Row : " + row + "Column : " + column);
final JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
JButton button = new JButton("?");
button.setPreferredSize(new Dimension(20, panel.getHeight()));
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
new SelectionDialog(panel, table, value, row, column);
}
});
if(table.getValueAt(row, column) != null){
field = new JTextField(table.getValueAt(row, column).toString());
}else{
field = new JTextField(table.getValueAt(row, column).toString());
}
field.setPreferredSize(new Dimension(30, panel.getHeight()));
panel.add(field, BorderLayout.WEST);
panel.add(button, BorderLayout.EAST);
return panel;
}
this is how i am updating contents of a cell in table..
SelectionDialog.this.table.getModel().setValueAt("E7", 0, 0);
I am updating the table model data via the SelectionDialog for e.g change data at row 0, colum 0 to E7 etc. After changing data in table model i have tried the following options but none of them has refreshed the table data in view however the data in model of JTable was updated correctly. If I add a new row on the fly and then call the below methods then every thing work fine but if I modify data in the model of an existing row then none of the solution mentioned below is working
//((DefaultTableModel)SelectionDialog.this.table.getModel()).addRow(new Object[]{"E3", "A3"});
//((DefaultTableModel)SelectionDialog.this.table.getModel()).fireTableCellUpdated(SelectionDialog.this.row, SelectionDialog.this.column);
//((DefaultTableModel)SelectionDialog.this.table.getModel()).fireTableChanged(new TableModelEvent(SelectionDialog.this.table.getModel()));
//((DefaultTableModel)SelectionDialog.this.table.getModel()).fireTableStructureChanged();
//SelectionDialog.this.table.repaint();
// SelectionDialog.this.table.revalidate();
Please provide any insights about the problem as I am to swing and may have missed some very prominent things.
Edit 1: Just adding one more note which i wanted to place earlier but don't know how i missed. Table is not updated in general but if i focus out from the cell in which change was made or i change the size of table then it immediately change the contents of that particular cell to fresh selected value.
Problem Solved:
I am placing my findings for someone who is facing similar problem.
I rendered a button and a text box inside each cell in my table. When button was clicked (Editor code is not provided as it looks irrelevant to me to place here) a dialog appear which inputs value from user and update the specific column and row.
The lines i mentioned as not working at the end of my post (before edit 1) were working correctly however renderer was not executing unless i manually focus out from the selected cell (whose button was clicked) or manually change the size of jtable which make sense as button was inside editor and button click shows that cell is edited and off-course renderer will not execute unless editing is finished which requires focus out or enter key etc.
I applied the following code as
table.editCellAt(-1, -1);
It focus out from the edited cell (edited with the button) and hence renderer executes and work as expected.
When you are using a DefaultTableModel and you want to update a certain value, you need to use the DefaultTableModel#setValueAt method.
Calling this method (on the Event Dispatch Thread of course) will update your model and fire the necessary events. Those events will cause your JTable to update itself.
A few additional remarks about your renderer:
Creating new components in each call of your renderer is not the way to go. This becomes incredibly slow for large tables. Create all components once in the constructor of your renderer, and just update their state
Adding a JButton to your table in the renderer has no effect, unless you have an editor as well. The button will not be clickable, and the action listener you attach to it will never be called. See the renderers and editors section in the JTable tutorial for more information.
There should be no need to call getValueAt in your renderer. The value is passed in as one of the arguments
OK, I must be completely brainless but I can't seem to implement the code needed to set (permanently) the background color of the selected (clicked) cell in my JTable. I've read through most of the answers on this site but I'm still not getting it.
I'm using the preparedRenderer() method but I don't understand why it isn't working?
table.addMouseListener(
new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent m) {
row = table.getSelectedRow();
column = table.getSelectedColumn();
}
}
);
table = new JTable(data, columnNames) {
public Component prepareRenderer(TableCellRenderer rend, int r, int k) {
Component g = super.prepareRenderer(rend, row, column);
g.setBackground(Color.BLUE);
return g;
}
};
The way I'm understanding it is that prepareRenderer is taking a specific cell from the table as a Component and then allowing me to change the properties of that Component. But even if I write:
Component g = super.prepareRenderer(rend, 1, 1);
g.setBackground(Color.BLUE);
return g;
it just paints the whole table and not the cell at row=1, column=1???
I'm just not getting it...
it just paints the whole table and not the cell at row=1, column=1???
The prepareRenderer() method is called for every cell that gets repainted. This is done dynamically as the user selects a row or tabs to a new cell or clicks on a cell.
set (permanently) the background color of the selected (clicked) cell in my JTable.
Maybe you can create a Set of Point objects to represent the cells that you want to paint a different color. So when you click on the cell you create a Point object for the row/column and then add the Point to the set.
Then in the prepareRenderer(...) method you create a new Point representing the row/column you are about to renderer. If this Point is found in your Set then you change the background color.
I have window that shows log events in table.
For example, user is reading text in some row of table. When new logs come, they are added in the beginning of the table, and the row, the user was reading moves down. I have a requirement to prevent JScrollPane to scroll when new rows are added at the beginning of the table. I tried diffrent things but nothing helped. Can someone advice me how to implement this?
Thanks in advance!
Tricky task :-) The moving out happens because by default the scroll position is not adjusted in any way: adding rows above simply keeps the vertical scroll position which then points to a different row as before.
What's needed:
keep track of the visibleRect/last row before the insert
listen to model changes of type insert
calculate the new visible rect such that the old last row is scrolled back into the view
trigger the scroll
It's tricky because we need to listen to model changes. That listener is a neighbour of the modelListener registered internally by the table which updates itself on changes. So we need to be sure to act after the internal changes are done and at the same time use information before the internal update.
A dirty solution - depending on the usual sequence of swing listener notification which is last-added-first-notified (beware: DONT in production code! It will break easily, f.i. when changing the LAF) - is to gather the before-state when notified and adjust the scroll position wrapped in an invokeLater:
final DefaultTableModel model = new DefaultTableModel(20, 2);
for (int i = 0; i < model.getRowCount(); i++) {
model.setValueAt(i, i, 0);
}
final JTable table = new JTable(model);
Action action = new AbstractAction("add row at 0") {
#Override
public void actionPerformed(ActionEvent e) {
model.insertRow(0, new Object[] {});
}
};
JXFrame frame = wrapWithScrollingInFrame(table, "modify scroll");
TableModelListener l = new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
if (!TableUtilities.isInsert(e)) return;
final Rectangle r = table.getVisibleRect();
final int lastRow = table.rowAtPoint(
new Point(0, r.y + r.height - 5));
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
// assuming
// a) the insert was exactly 1 row
// b) was _before_ the old last row in view-coordinates
Rectangle cell = table.getCellRect(lastRow + 1, 0, true);
cell.translate(0, -r.height + cell.height);
cell.height = r.height;
table.scrollRectToVisible(cell);
}
});
}
};
model.addTableModelListener(l);
The appropriate way of getting the before-state cleanly depends on your exact context. You can keep track of the first/last row during the lifetime of the table, updating them in (one or all)
a ListSelectionListener to the row selectionModel (if the row the user is reading is always selected)
a ChangeListener to the vertical scrollBar
a ComponentListener to scrollPane's size changes