I have a rows of data, where I can make actions on a remote server.
I'm displaying these data in a JTable
as it avoids the cost of creating a component for each row, and there's a lot.
I have managed to show the component and be able to interact with the cell, (however I still a first click).
But what I'm struggling with is that I want my editor component to be expandable, and of course update the current row height. And of course to revert to regular row height when collapsed. I think I need to register some listeners to the cell editor, but I'm currently not able to correctly do that.
ComponentListner::componentResized
make the whole table being constantly repainted.editorComponent.addPropertyChangeListener("preferredSize", propertyChangeListener)
, doesn't always repaint the tableeditorComponent.addPropertyChangeListener("preferredSize", propertyChangeListener)
, doesn't always repaint the table
Hence this question.
Somewhat out of scope, I'm currently debating if the component should stay expanded when going to another cell (but this can be an expanded
property that is part of the model).
Here's a minimal example without my attempts to register listeners on the editor component (in getTableCellEditorComponent
).
Thanks in advance for ay pointers.
package io.github.bric3.fireplace.ui;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
public class DynamicCellRow {
@NotNull
private static JTable makeTable() {
var jTable = new JTable(
new Object[][]{
{"a", "charly"},
{"b", "tango"}
},
new Object[]{"id", "control"}
);
{
var richColumn = jTable.getColumnModel().getColumn(1);
richColumn.setCellRenderer(new TableCellRenderer() {
private final ExpandablePanel expandablePanelRenderComponent = new ExpandablePanel();
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
expandablePanelRenderComponent.setValue(value);
return updatePreferredRowHeight(table, expandablePanelRenderComponent, row, column);
}
});
richColumn.setCellEditor(new DynamicCellEditor());
}
return jTable;
}
public static <T extends JComponent> T updatePreferredRowHeight(JTable table, T cellComponent, int row, int column) {
// Adjust cell height per component
int originalPreferredHeight = cellComponent.getPreferredSize().height;
cellComponent.setSize(
table.getColumnModel().getColumn(column).getWidth(),
originalPreferredHeight
);
int newPreferredHeight = cellComponent.getPreferredSize().height;
if (table.getRowHeight(row) < newPreferredHeight) {
table.setRowHeight(row, newPreferredHeight);
}
return cellComponent;
}
static class ExpandablePanel extends JPanel {
private final JLabel comp;
ExpandablePanel() {
setBorder(BorderFactory.createLineBorder(Color.black));
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
// horizontal left-to-right layout
gbc.gridx = 0;
gbc.gridy = 0;
// resizing behavior
gbc.weightx = 1;
gbc.weighty = 1;
gbc.insets = new Insets(2, 2, 2, 2);
gbc.fill = GridBagConstraints.BOTH;
JPanel advanced = new JPanel();
{
advanced.setLayout(new BoxLayout(advanced, BoxLayout.Y_AXIS));
advanced.setBorder(new TitledBorder("Advance Settings"));
advanced.add(new JCheckBox("Live"));
advanced.add(new JCheckBox("Condition"));
advanced.add(new JCheckBox("Disable"));
}
advanced.setVisible(false);
var standard = new JPanel();
{
standard.setLayout(new BoxLayout(standard, BoxLayout.X_AXIS));
comp = new JLabel("Label 1");
standard.add(comp);
standard.add(new JButton("Button 1"));
var expandButton = new JButton(" ");
expandButton.addActionListener(e -> {
if (advanced.isVisible()) {
advanced.setVisible(false);
expandButton.setText(" ");
} else {
advanced.setVisible(true);
expandButton.setText("-");
}
});
standard.add(expandButton);
}
add(standard, gbc);
gbc.gridy ;
gbc.weighty = 0;
add(advanced, gbc);
}
public void setValue(Object value) {
comp.setText(value.toString());
}
}
private static class DynamicCellEditor extends AbstractCellEditor implements TableCellEditor {
Object value;
@Override
public Object getCellEditorValue() {
return value; // not changing
}
private final ExpandablePanel expandablePanel = new ExpandablePanel();
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
this.value = value;
expandablePanel.setValue(value);
return updatePreferredRowHeight(table, expandablePanel, row, column);
}
}
public static void main(String[] args) {
var contentPane = new JPanel(new BorderLayout());
contentPane.add(new JScrollPane(makeTable()));
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("DynamicCellRow");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(contentPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
CodePudding user response:
@kleopatra Thank you! Your tip got me on the right track. I made the following code to precompute the row height during the layout operation.
And when a cell gets edited, the editor is added to the table, when then do a revalidation, and computes the height accordingly.
I had a problem before because setting the height in the renderer actually prevented the correct display of the cell component.
In the following code, notice how doLayout
is adjusting the row height, according to cell component preferred size (wether the cell is rendered or edited).
Note I choose to collapse the component once editing is stopped, hence the listener in the DynamicExpndablePanelCellEditor
.
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
public class DynamicCellRow {
public static void main(String[] args) {
var contentPane = new JPanel(new BorderLayout());
contentPane.add(new JScrollPane(makeTable()));
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("DynamicCellRow");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(contentPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
@NotNull
private static JTable makeTable() {
var jTable = new JTable(
new Object[][]{
{"a", "charly"},
{"b", "tango"}
},
new Object[]{"id", "control"}
) {
@Override
public void doLayout() {
super.doLayout();
adjustRowHeights();
}
private void adjustRowHeights() {
for (int row = 0; row < getRowCount(); row ) {
int rowHeight = getRowHeight();
for (int column = 0; column < getColumnCount(); column ) {
var editorComponent = getEditorComponent();
if (getEditingRow() == row && getEditingColumn() == column && editorComponent != null) {
editorComponent.setSize(getColumnModel().getColumn(column).getWidth(), 0);
rowHeight = Math.max(rowHeight, editorComponent.getPreferredSize().height);
} else {
var comp = prepareRenderer(getCellRenderer(row, column), row, column);
rowHeight = Math.max(rowHeight, comp.getPreferredSize().height);
}
}
setRowHeight(row, rowHeight);
}
}
};
{
var richColumn = jTable.getColumnModel().getColumn(1);
richColumn.setCellRenderer(new ExpandablePanelCellRenderer());
richColumn.setCellEditor(new DynamicExpndablePanelCellEditor());
}
return jTable;
}
static class ExpandablePanel extends JPanel {
private final JLabel comp;
private final JPanel advanced;
ExpandablePanel() {
setBorder(BorderFactory.createLineBorder(Color.black));
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
// horizontal left-to-right layout
gbc.gridx = 0;
gbc.gridy = 0;
// resizing behavior
gbc.weightx = 1;
gbc.weighty = 1;
gbc.insets = new Insets(2, 2, 2, 2);
gbc.fill = GridBagConstraints.BOTH;
advanced = new JPanel();
{
advanced.setLayout(new BoxLayout(advanced, BoxLayout.Y_AXIS));
advanced.setBorder(new TitledBorder("Advance Settings"));
advanced.add(new JCheckBox("Live"));
advanced.add(new JCheckBox("Condition"));
advanced.add(new JCheckBox("Disable"));
}
advanced.setVisible(false);
var standard = new JPanel();
{
standard.setLayout(new BoxLayout(standard, BoxLayout.X_AXIS));
comp = new JLabel("Label 1");
standard.add(comp);
standard.add(new JButton("Button 1"));
var expandButton = new JButton(" ");
expandButton.addActionListener(e -> {
if (advanced.isVisible()) {
advanced.setVisible(false);
expandButton.setText(" ");
} else {
advanced.setVisible(true);
expandButton.setText("-");
}
});
standard.add(expandButton);
}
add(standard, gbc);
gbc.gridy ;
gbc.weighty = 0;
add(advanced, gbc);
}
public void setValue(Object value) {
comp.setText(value.toString());
}
public void setAdvancedVisibility(boolean visible) {
advanced.setVisible(visible);
}
}
private static class DynamicExpndablePanelCellEditor extends AbstractCellEditor implements TableCellEditor {
Object value;
@Override
public Object getCellEditorValue() {
return value; // not changing
}
private final ExpandablePanel expandablePanel = new ExpandablePanel();
{
addCellEditorListener(new CellEditorListener() {
@Override
public void editingStopped(ChangeEvent e) {
expandablePanel.setAdvancedVisibility(false);
}
@Override
public void editingCanceled(ChangeEvent e) {
expandablePanel.setAdvancedVisibility(false);
}
});
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
this.value = value;
expandablePanel.setValue(value);
return expandablePanel;
}
}
private static class ExpandablePanelCellRenderer implements TableCellRenderer {
private final ExpandablePanel expandablePanelRenderComponent = new ExpandablePanel();
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
expandablePanelRenderComponent.setValue(value);
return expandablePanelRenderComponent;
}
}
}