JTable cell with multiple Component

3.2k views Asked by At

My task is to implement a JTable which one of it's column has this appearance: enter image description here

it should contain 4 elements:
1. a JCombobox, which controls the enable status of the other two: they are enabled for "manual" state, and locked on certain numerical values for the other states (let's call the other combo states: 'first', 'second', 'third').
2. a JTextField which, when enabled, controls the value of the slider.
3. a JLabel which reads "%" and does nothing.
4. s JSlider which, when enabled, controls the JTextField value (can this mutual control even exist?).

I crawled all over the web searching for ways to implement this. Most examples are too superficial including this similar question.

I had my share of Cell Rendering/fireEditingStopped etc. torment before I asked this, but have no simple short code to show unfortunately.

Can any one please show me specific guidelines or code snippet which can send me on the right path? Thanks in advance.

4

There are 4 answers

2
aterai On BEST ANSWER

Is that a single Renderer/Editor for the all the cell's elements or multiple Ren'/Ed' for each element?

I'm not sure of the problem, but here is a simple example of retrieving the data from TableCellRenderer and TableCellEditor.

enter image description here

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class MultipleComponentCellTest {
  private final String[] columnNames = {"Band Type"};
  private final Object[][] data = {
    {new BandType("manual",  50)},
    {new BandType("locked", 100)},
    {new BandType("manual",  32)},
    {new BandType("locked",   0)},
  };
  private final TableModel model = new DefaultTableModel(data, columnNames) {
    @Override public Class<?> getColumnClass(int column) {
      return BandType.class;
    }
  };
  private final JTable table = new JTable(model);
  public JComponent makeUI() {
    table.setRowHeight(42);
    table.setDefaultRenderer(BandType.class, new BandTypeRenderer());
    table.setDefaultEditor(BandType.class, new BandTypeEditor());
    return new JScrollPane(table);
  }
  public static void main(String... args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        createAndShowGUI();
      }
    });
  }
  public static void createAndShowGUI() {
    try {
      for (UIManager.LookAndFeelInfo laf: UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(laf.getName())) {
          UIManager.setLookAndFeel(laf.getClassName());
        }
      }
    } catch (ClassNotFoundException | InstantiationException
           | IllegalAccessException | UnsupportedLookAndFeelException ex) {
      ex.printStackTrace();
    }
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.getContentPane().add(new MultipleComponentCellTest().makeUI());
    f.setSize(320, 240);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}

class BandType {
  public final String state;
  public final int value;
  public BandType(String state, int value) {
    this.state = state;
    this.value = value;
  }
}

class BandTypePanel extends JPanel {
  private static String DEFAULT = "0";
  public final JSlider slider = new JSlider(0, 100);
  public final JTextField textField = new JTextField(3);
  public final JComboBox<String> comboBox = new JComboBox<>(
      new String[] {"manual", "locked"});
  public BandTypePanel() {
    super(new BorderLayout(5, 5));
    setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

    comboBox.addItemListener(new ItemListener() {
      @Override public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          boolean f = "manual".equals(e.getItem());
          slider.setEnabled(f);
          textField.setEnabled(f);
        }
      }
    });

    textField.setEditable(false);
    textField.setHorizontalAlignment(JTextField.RIGHT);

    slider.setOpaque(false);
    slider.setFocusable(false);
    slider.getModel().addChangeListener(new ChangeListener() {
      @Override public void stateChanged(ChangeEvent e) {
        BoundedRangeModel m = (BoundedRangeModel) e.getSource();
        textField.setText(Objects.toString(m.getValue(), DEFAULT));
      }
    });

    JPanel p = new JPanel();
    p.setOpaque(false);
    p.add(comboBox);
    p.add(textField);
    p.add(new JLabel("%"));

    add(p, BorderLayout.WEST);
    add(slider);
  }
  public void updateValue(BandType bt) {
    comboBox.setSelectedItem(bt.state);
    slider.setValue(bt.value);
    textField.setText(Objects.toString(bt.value, DEFAULT));
  }
}

class BandTypeRenderer extends BandTypePanel implements TableCellRenderer {
  public BandTypeRenderer() {
    super();
    setName("Table.cellRenderer");
  }
  @Override public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected, boolean hasFocus,
      int row, int column) {
    setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
    if (value instanceof BandType) {
      updateValue((BandType) value);
    }
    return this;
  }
}

class BandTypeEditor extends BandTypePanel implements TableCellEditor {
  protected transient ChangeEvent changeEvent;
  @Override public Component getTableCellEditorComponent(
    JTable table, Object value, boolean isSelected, int row, int column) {
    this.setBackground(table.getSelectionBackground());
    if (value instanceof BandType) {
      updateValue((BandType) value);
    }
    return this;
  }
  @Override public Object getCellEditorValue() {
    return new BandType(comboBox.getSelectedItem().toString(), slider.getValue());
  }
  //Copied from AbstractCellEditor
  //protected EventListenerList listenerList = new EventListenerList();
  @Override public boolean isCellEditable(EventObject e) {
    return true;
  }
  @Override public boolean shouldSelectCell(EventObject anEvent) {
    return true;
  }
  @Override public boolean stopCellEditing() {
    fireEditingStopped();
    return true;
  }
  @Override public void cancelCellEditing() {
    fireEditingCanceled();
  }
  @Override public void addCellEditorListener(CellEditorListener l) {
    listenerList.add(CellEditorListener.class, l);
  }
  @Override public void removeCellEditorListener(CellEditorListener l) {
    listenerList.remove(CellEditorListener.class, l);
  }
  public CellEditorListener[] getCellEditorListeners() {
    return listenerList.getListeners(CellEditorListener.class);
  }
  protected void fireEditingStopped() {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == CellEditorListener.class) {
        // Lazily create the event:
        if (Objects.isNull(changeEvent)) {
          changeEvent = new ChangeEvent(this);
        }
        ((CellEditorListener) listeners[i + 1]).editingStopped(changeEvent);
      }
    }
  }
  protected void fireEditingCanceled() {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == CellEditorListener.class) {
        // Lazily create the event:
        if (Objects.isNull(changeEvent)) {
          changeEvent = new ChangeEvent(this);
        }
        ((CellEditorListener) listeners[i + 1]).editingCanceled(changeEvent);
      }
    }
  }
}
1
chilltouch On

You need to implement TableCellRenderer. So you can add multiple components to same cell. Here you can find more information http://www.coderanch.com/t/614645/GUI/java/adding-components-cell-jtable

3
trashgod On

You'll need both a TableCellRenderer and a TableCellEditor for any components that are editable. A general example is shown here.

  • The default renderer will work for the String, "%", as shown here; alternatively, you may want to display it in the adjacent column's renderer.

  • The default renderer & editor will work for a Double or Float model value representing the percent, as shown here.

  • A DefaultCellEditor will work for the combo box, as shown here; you can decorate the renderer as shown here.

  • Try SliderColumn or SpinnerColumn, shown here, for the last column; examine the examples seen here for more.

0
Ken Perry On

The sample here works if all your doing is looking. It breaks down when you try to interact with it. I am blind and there are many ways to access a computer other than using a mouse. When I use this table all I see is 4 rows with band type as the cell 1 name. I can not get to the combobox, or any of the controls. Try using this band type table with just keyboard and see if you can do anything with the combobox or if you can select the edit boxes. If you want to see how really bad this is Download the NVDA free screen reader and try the table using nothing but keyboard and the screen reader. It can be found here: https://www.nvaccess.org/download/

I am currently working on Arduino IDE to try to fix someones mess of a table like this and so far I have not been able to get all the components to focus. It is important that people make sure that when they create stuff like this The focus works if your using keyboard navigation as well as mouse and even touch. I am very interested in anyone who can make this table work for NVDA. If I get there first I will post a correct implementation of a Panel Cell multi component cell but this one is not it.