Search code examples
nullpointerexceptionjtablejcomponenttablecellrenderertablecelleditor

JTable for JPanel with multiple type JComponent (Swing)


This is a long question, but all code is needed (I was thinking a basic question, with not common components).

I need to build a JTable with multiple Columns with multiple type Custom JPanel (with JSlider + JComboBox + JTextField) in only one Column.

I was reading: http://pekalicious.com/blog/custom-jpanel-cell-with-jbuttons-in-jtable/

In this question, I will use one Column with only one Customized JPanel with name PanelSpinnerRadioButton.

The Custom Panel:PanelSpinnerRadioButton.java

public class PanelSpinnerRadioButton extends JPanel {
  private final JRadioButton jrbOption01= new JRadioButton("01");
  private final JRadioButton jrbOption02 = new JRadioButton("12");

  private final JSpinner jspnValues = new JSpinner(new SpinnerNumberModel(5, 0, 10, 1));

  private final JPanel jpPanelSpinnerRadioButton = new JPanel();

  PanelSpinnerRadioButton() {
    this(new PanelSpinnerRadioButtonData(false, 20, 40));
  }

  PanelSpinnerRadioButton(PanelSpinnerRadioButtonData data) {
    super();

    jpPanelSpinnerRadioButton.setLayout(new BoxLayout(jpPanelSpinnerRadioButton, BoxLayout.LINE_AXIS));
    jpPanelSpinnerRadioButton.add(jrbOption01);
    jpPanelSpinnerRadioButton.add(jrbOption02);
    jpPanelSpinnerRadioButton.add(Box.createRigidArea(new Dimension(5,0)));
    jpPanelSpinnerRadioButton.add(new JSeparator(JSeparator.VERTICAL));
    jpPanelSpinnerRadioButton.add(Box.createRigidArea(new Dimension(5,0)));
    jpPanelSpinnerRadioButton.add(jspnValues);
    //Change States of RadioButtons (will be readOnly, ButtonGroup is not needed)
    jrbOption02.setSelected(data.getOption());
    jrbOption01.setSelected(!data.getOption());
    //Change States of Spinner
    ((SpinnerNumberModel)jspnValues.getModel()).setValue(data.getFrom());
    ((SpinnerNumberModel)jspnValues.getModel()).setMaximum(data.getSize());

    init();
  }

  private void init() {
    setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
    setBackground(new Color(0, 0, 0, 0/*64*/));
    add(jpPanelSpinnerRadioButton);
  }

  // Used in PSRBTableCellEditor.getCellEditorValue()
  public PanelSpinnerRadioButtonData getData() {
    return new PanelSpinnerRadioButtonData(
        jrbOption02.isSelected(),
        (Integer)((SpinnerNumberModel)jspnValues.getModel()).getValue(),
        (Integer)((SpinnerNumberModel)jspnValues.getModel()).getMaximum()
    );
  }

}

Here the Image Result of before Custom JPanel

enter image description here

Data Type for Custom Panel:PanelSpinnerRadioButtonData.java

public class PanelSpinnerRadioButtonData {

  private boolean opt02 = false;

  private Integer from = 0;
  private Integer size = 1;

  PanelSpinnerRadioButtonData() {
    this(false, 5, 10);
  }

  PanelSpinnerRadioButtonData(boolean opt02, Integer from, Integer size) {
    this.opt02 = opt02;
    this.from = from;
    this.size = size;
  }

  public boolean getOption() { return opt02; }
  public Integer getFrom() { return from; }
  public Integer getSize() { return size; }

}

Now the coded to handle my Custom JPanel (with inner JComponents)

the Table Model:PSRBTableModel.java

public class PSRBTableModel extends AbstractTableModel {

  private final Object[][] data;
  private final Object[] columns;

  public PSRBTableModel(Object[][] data, Object[] columns) {
//    super();
    this.data = data;
    this.columns = columns;
  }

  public Object getValueAt(int rowIndex, int columnIndex) {
    if (data != null) {
      if (data.length > 0) {
        if (data[rowIndex][columnIndex] instanceof PanelSpinnerRadioButton) {
          return (PanelSpinnerRadioButton)data[rowIndex][columnIndex];
        }
        return data[rowIndex][columnIndex];
      }
    }
    return null;
  }

  public int getColumnCount() {
    return ((columns == null) ? 0: columns.length);
  }

  public int getRowCount() {
    return ((data == null) ? 0: data.length);
  }

  public Class getColumnClass(int columnIndex) {
    if (data != null) {
      if (data.length > 0) {
        if (data[0][columnIndex] instanceof PanelSpinnerRadioButton) {
          return PanelSpinnerRadioButton.class;
        }
        return data[0][columnIndex].getClass();
      }
    }
    return null;
  }

  public boolean isCellEditable(int rowIndex, int columnIndex) {
    if (data != null) {
      if (data.length > 0) {
        if (data[0][columnIndex] instanceof PanelSpinnerRadioButton) {
          //PanelSpinnerRadioButton Is not Editable
          return false;
        }
      }
    }
    return true;
  }

  public void setValueAt(Object value, int row, int col) {
    data[row][col] = value;
    fireTableCellUpdated(row, col);
  }

  public String getColumnName(int columnIndex) {
    return (String)columns[columnIndex];
  }

}

Now the Renderer:PSRBTableCellRenderer.java

public class PSRBTableCellRenderer implements TableCellRenderer {

  public Component getTableCellRendererComponent(JTable table, Object value,
        boolean isSelected, boolean hasFocus, int row, int column) {

    Object output = null;

    if (value instanceof PanelSpinnerRadioButtonData) {
      output = new PanelSpinnerRadioButton((PanelSpinnerRadioButtonData)value);
    }
    return (Component)output;
  }

}

And the EditorCell:PSRBTableCellEditor.java

public class PSRBTableCellEditor extends AbstractCellEditor implements TableCellEditor {

  protected Object output;

  PSRBTableCellEditor() {
  }

  public Object getCellEditorValue() {
    //Returns the value contained in the editor.
    if (output  instanceof PanelSpinnerRadioButton) {
      return ((PanelSpinnerRadioButton)output).getData();
    }
    return null;
  }

  public Component getTableCellEditorComponent(JTable table, Object value,
      boolean isSelected, int row, int column) {
    //Sets an initial value for the editor.

    if (value instanceof PanelSpinnerRadioButtonData) {
      output = new PanelSpinnerRadioButton((PanelSpinnerRadioButtonData)value);
      return (PanelSpinnerRadioButton)output;
    }
    return null;
  }

}

Test the Before Code (I'm working with JFrame)

String[] hdrsObjects = new String[]{"PanelSpinnerRadioButton Class Column"};
Object[][] objectMatrix = new Object[3][hdrsObjects.length];

objectMatrix[0][0] = new PanelSpinnerRadioButton();
objectMatrix[1][0] = new PanelSpinnerRadioButton();
objectMatrix[2][0] = new PanelSpinnerRadioButton();

JTable jtbl = new JTable(new PSRBTableModel(objectMatrix, hdrsObjects));
//jtbl.setDefaultRenderer(PanelSpinnerRadioButton.class, new PSRBTableCellRenderer());
//jtbl.setDefaultEditor(PanelSpinnerRadioButton.class, new PSRBTableCellEditor());
jtbl.getColumn("PanelSpinnerRadioButton Class Column").setCellRenderer(new PSRBTableCellRenderer());
jtbl.getColumn("PanelSpinnerRadioButton Class Column").setCellEditor(new PSRBTableCellEditor());

add(new JScrollPane(jtbl));

But I can't see the JTable with (3 rows with Custom JPanel 'PanelSpinnerRadioButton')

I have this exception...

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
  at javax.swing.plaf.synth.SynthTableUI.paintCell(SynthTableUI.java:684)
  at javax.swing.plaf.synth.SynthTableUI.paintCells(SynthTableUI.java:580)
  at javax.swing.plaf.synth.SynthTableUI.paint(SynthTableUI.java:364)
  at javax.swing.plaf.synth.SynthTableUI.update(SynthTableUI.java:275)
  at javax.swing.JComponent.paintComponent(JComponent.java:780)
  at javax.swing.JComponent.paint(JComponent.java:1056)
  at javax.swing.JComponent.paintChildren(JComponent.java:889)
  at javax.swing.JComponent.paint(JComponent.java:1065)
  at javax.swing.JViewport.paint(JViewport.java:728)
  at javax.swing.JComponent.paintChildren(JComponent.java:889)
  at javax.swing.JComponent.paint(JComponent.java:1065)
  at javax.swing.JComponent.paintChildren(JComponent.java:889)
  at javax.swing.JComponent.paint(JComponent.java:1065)
  at javax.swing.JComponent.paintChildren(JComponent.java:889)
  at javax.swing.JComponent.paint(JComponent.java:1065)
  at javax.swing.JComponent.paintChildren(JComponent.java:889)
  at javax.swing.JComponent.paint(JComponent.java:1065)
  at javax.swing.JComponent.paintChildren(JComponent.java:889)
  at javax.swing.JComponent.paint(JComponent.java:1065)
  at javax.swing.JComponent.paintChildren(JComponent.java:889)
  at javax.swing.JComponent.paint(JComponent.java:1065)
  at javax.swing.JComponent.paintChildren(JComponent.java:889)
  at javax.swing.JComponent.paint(JComponent.java:1065)
  at javax.swing.JComponent.paintChildren(JComponent.java:889)
  at javax.swing.JComponent.paint(JComponent.java:1065)
  at javax.swing.JComponent.paintChildren(JComponent.java:889)
  at javax.swing.JComponent.paint(JComponent.java:1065)
  at javax.swing.JLayeredPane.paint(JLayeredPane.java:586)
  at javax.swing.JComponent.paintChildren(JComponent.java:889)
  at javax.swing.JComponent.paintToOffscreen(JComponent.java:5217)
  at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1579)
  at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1502)
  at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:306)
  at javax.swing.RepaintManager.paint(RepaintManager.java:1272)
  at javax.swing.JComponent.paint(JComponent.java:1042)
  at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:39)
  at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:79)
  at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:116)
  at java.awt.Container.paint(Container.java:1975)
  at java.awt.Window.paint(Window.java:3912)
  at javax.swing.RepaintManager$4.run(RepaintManager.java:842)
  at javax.swing.RepaintManager$4.run(RepaintManager.java:814)
  at java.security.AccessController.doPrivileged(Native Method)
  at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
  at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:814)
  at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:789)
  at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:738)
  at javax.swing.RepaintManager.access$1200(RepaintManager.java:64)
  at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1732)
  at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
  at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756)
  at java.awt.EventQueue.access$500(EventQueue.java:97)
  at java.awt.EventQueue$3.run(EventQueue.java:709)
  at java.awt.EventQueue$3.run(EventQueue.java:703)
  at java.security.AccessController.doPrivileged(Native Method)
  at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
  at java.awt.EventQueue.dispatchEvent(EventQueue.java:726)
  at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
  at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
  at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
  at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
  at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
  at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

QUESTION

  • What's wrong?
  • Why I can't see the JTable with 3 PanelSpinnerRadioButton?

EDITION

Please consider this scenario (Another Custom JPanel handled by the same TableCellRenderer PSRBTableCellRenderer and TableCellEditor PSRBTableCellEditor)

class PanelButton extends JPanel {

  private final JPanel jpPanelButton = new JPanel();
  // the ActionListener does not matter now
  JButton jbtAction = new JButton("Action");

  PanelButton() {
    this(new PanelButtonEmpty());
  }
  PanelButton(PanelButtonEmpty data) {
    super();
    jpPanelButton.setLayout(new BoxLayout(jpPanelButton, BoxLayout.LINE_AXIS));

    jpPanelButton.add(jbtAction);

    /*Overridable method call in constructor*/
    setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
    setBackground(new Color(0, 0, 0, 0/*64*/));
    add(jpPanelButton);
  }

  public PanelButtonEmpty getData() {
    return new PanelButtonEmpty();
  }

  public void setData(PanelButtonEmpty data) {
  }
}

I was thinking in a totally empty class (to cast the value in TableCellRenderer PSRBTableCellEditor)

class PanelButtonEmpty {
  PanelButtonEmpty() { }
  //public boolean getLazy() { return true; }
  //public void setLazy(boolean b) {}
}

In this case I have two types of Custom JPanel for this reason I have

for TableCellEditor

public class PSRBTableCellEditor extends AbstractCellEditor implements TableCellEditor {
  private Object output;
  @Override public Object getCellEditorValue() {
    if (output  instanceof PanelSpinnerRadioButton) {
      return ((PanelSpinnerRadioButton)output).getData();
    }
    if (output  instanceof PanelButton) {
      return ((PanelButton)output).getData();
    }
    return null;
  }

  @Override public Component getTableCellEditorComponent(JTable table, Object value,
      boolean isSelected, int row, int column) {
    if (value instanceof PanelSpinnerRadioButtonData) {
      output = new PanelSpinnerRadioButton((PanelSpinnerRadioButtonData)value);
      return (PanelSpinnerRadioButton)output;
    }
    if (value instanceof PanelButtonEmpty) {
      output = new PanelButton((PanelButtonEmpty)value);
      return (PanelButton)output;
    }
    return null;
  }
}

for TableCellRenderer

public class PSRBTableCellRenderer implements TableCellRenderer {
  private Object output = null;
  @Override public Component getTableCellRendererComponent(JTable table, Object value,
        boolean isSelected, boolean hasFocus, int row, int column) {
    if (value instanceof PanelSpinnerRadioButtonData) {
      output = new PanelSpinnerRadioButton((PanelSpinnerRadioButtonData)value);
    }
    if (value instanceof PanelButtonEmpty) {
      output = new PanelButton((PanelButtonEmpty)value);
    }
    return (Component)output;
  }
}

QUESTIONS

How to avoid your Second Warning (in the new scenario)?

  • It is a waste to create each time a component in the TableCellRenderer#getTableCellRendererComponent(...).

How to handle with Objects different to here described?

PD:

I'm not sure to put this second part question in another post with the same source code...


Solution

  • objectMatrix[0][0] = new PanelSpinnerRadioButton();

    • Should be set PanelSpinnerRadioButtonData to the TableModel, rather than a component such as PanelSpinnerRadioButton.

    output = new PanelSpinnerRadioButton((PanelSpinnerRadioButtonData)value);

    • It is a waste to create each time a component in the TableCellRenderer#getTableCellRendererComponent(...).
    import java.awt.*;
    import javax.swing.*;
    import javax.swing.table.*;
    
    public class TableTest {
      public JComponent makeUI() {
        String[] hdrsObjects = {"PanelSpinnerRadioButton Class Column"};
        Object[][] objectMatrix = new Object[3][hdrsObjects.length];
        objectMatrix[0][0] = new PanelSpinnerRadioButtonData(false, 10, 40);
        objectMatrix[1][0] = new PanelSpinnerRadioButtonData(true,  20, 40);
        objectMatrix[2][0] = new PanelSpinnerRadioButtonData(false, 30, 40);
    
        JTable table = new JTable(new DefaultTableModel(objectMatrix, hdrsObjects));
        table.setRowHeight(30);
    
        TableColumn tc = table.getColumn("PanelSpinnerRadioButton Class Column");
        tc.setCellRenderer(new PSRBTableCellRenderer());
        tc.setCellEditor(new PSRBTableCellEditor());
    
        JPanel p = new JPanel(new BorderLayout());
        p.add(new JScrollPane(table));
        return p;
      }
      public static void main(String... args) {
        EventQueue.invokeLater(() -> {
          try {
            for (UIManager.LookAndFeelInfo laf: UIManager.getInstalledLookAndFeels()) {
              if ("Nimbus".equals(laf.getName())) {
                UIManager.setLookAndFeel(laf.getClassName());
              }
            }
          } catch (Exception e) {
            e.printStackTrace();
          }
          JFrame f = new JFrame();
          f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
          f.getContentPane().add(new TableTest().makeUI());
          f.setSize(240, 240);
          f.setLocationRelativeTo(null);
          f.setVisible(true);
        });
      }
    }
    
    class PanelSpinnerRadioButtonData {
      private boolean opt02 = false;
      private Integer from = 0;
      private Integer size = 1;
    
      PanelSpinnerRadioButtonData() {
        this(false, 5, 10);
      }
      PanelSpinnerRadioButtonData(boolean opt02, Integer from, Integer size) {
        this.opt02 = opt02;
        this.from = from;
        this.size = size;
      }
      public boolean getOption() {
        return opt02;
      }
      public Integer getFrom() {
        return from;
      }
      public Integer getSize() {
        return size;
      }
    }
    
    class PanelSpinnerRadioButton extends JPanel {
      public final JRadioButton jrbOption01 = new JRadioButton("01");
      public final JRadioButton jrbOption02 = new JRadioButton("12");
      public final JSpinner jspnValues = new JSpinner(new SpinnerNumberModel(5, 0, 10, 1));
    
      private final JPanel panel = new JPanel();
    
      PanelSpinnerRadioButton() {
        this(new PanelSpinnerRadioButtonData(false, 20, 40));
      }
    
      PanelSpinnerRadioButton(PanelSpinnerRadioButtonData data) {
        super();
    
        panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
        panel.add(jrbOption01);
        panel.add(jrbOption02);
        panel.add(Box.createRigidArea(new Dimension(5, 0)));
        panel.add(new JSeparator(JSeparator.VERTICAL));
        panel.add(Box.createRigidArea(new Dimension(5, 0)));
        panel.add(jspnValues);
        ButtonGroup bg = new ButtonGroup();
        bg.add(jrbOption01);
        bg.add(jrbOption02);
        ((SpinnerNumberModel) jspnValues.getModel()).setMaximum(data.getSize());
        setData(data);
    
        init();
      }
    
      private void init() {
        setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
        setBackground(new Color(0, 0, 0, 0));
        add(panel);
      }
    
      public void setData(PanelSpinnerRadioButtonData data) {
        if (data.getOption()) {
          jrbOption02.setSelected(true);
        } else {
          jrbOption01.setSelected(true);
        }
        ((SpinnerNumberModel) jspnValues.getModel()).setValue(data.getFrom());
      }
    
      // Used in PSRBTableCellEditor.getCellEditorValue()
      public PanelSpinnerRadioButtonData getData() {
        return new PanelSpinnerRadioButtonData(
                 jrbOption02.isSelected(),
                 (Integer) ((SpinnerNumberModel) jspnValues.getModel()).getValue(),
                 (Integer) ((SpinnerNumberModel) jspnValues.getModel()).getMaximum());
      }
    }
    
    class PSRBTableCellRenderer implements TableCellRenderer {
      private final PanelSpinnerRadioButton renderer = new PanelSpinnerRadioButton();
      @Override public Component getTableCellRendererComponent(
          JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
        if (value instanceof PanelSpinnerRadioButtonData) {
          renderer.setData((PanelSpinnerRadioButtonData) value);
        }
        return renderer;
      }
    }
    
    class PSRBTableCellEditor extends AbstractCellEditor implements TableCellEditor {
      private final PanelSpinnerRadioButton editor = new PanelSpinnerRadioButton();
      @Override public Object getCellEditorValue() {
        return editor.getData();
      }
      @Override public Component getTableCellEditorComponent(
          JTable table, Object value, boolean isSelected, int row, int column) {
        if (value instanceof PanelSpinnerRadioButtonData) {
          editor.setData((PanelSpinnerRadioButtonData) value);
        }
        return editor;
      }
    }