具有非均匀对象数组的 JTable,自定义 TableCellRenderer 实现的 return

JTable with nonuniform Object Array, return of Custom TableCellRenderer implementation

我从 JPanel 扩展了自定义组件。 名称是:PanelButtonPanelSlider

问题 1:

使用非均匀矩阵创建 xxxTableModel(...) 是否有效(或安全)?

String[] hdrsObjects = {"PanelButton Class", "PanelSlider Class"};
Object[][] objectMatrix = new Object[3][2];
objectMatrix[0][0] = new PanelButtonData(...);
objectMatrix[1][0] = new PanelButtonData(...);
objectMatrix[2][0] = new PanelButtonData(...);

// objectMatrix[0][1] = /*Non Assigned*/
objectMatrix[1][1] = new PanelSliderData(0, 20, 40);
objectMatrix[2][1] = new PanelSliderData(30, 40, 60);

JTable Mytable = new JTable(new MyTableModel(objectMatrix, hdrsObjects)) {...}

这相当于 3 行不同的长度:

jtblGeneral.setModel(new DefaultTableModel(
  new Object [][] { {"Cell Row:0,Col:0"}, {"Cell Row:1,Col:0", "Cell Row:1,Col:1"}, {"Cell Row:2,Col:0", "Cell Row:2,Col:1"}
  },
  new String [] {
    "Title 1", "Title 2"
  }
));

现在我正在实施我自己的 TableCellRenderer

class MyTableCellRenderer implements TableCellRenderer {

  private final PanelSlider ps = new PanelSlider(new PanelSliderData(0, 25, 50));
  private final PanelButton pb = new PanelButton(new PanelButtonData(false));

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

    if (value instanceof PanelButtonData) {
      pb.setData((PanelButtonData) value);
      return pb;
    }

    if (value instanceof PanelSliderData) {
      ps.setData((PanelSliderData) value);
      return ps;
    }

  //if (value != null)
  //  return (Component) value;
  //return this;

  //return null;

  //    return table.getDefaultRenderer(String.class).getTableCellRendererComponent(
  //        table, value, isSelected, hasFocus, row, column);

    return new JLabel();
  }
}

问题 2:

如果前一个问题的答案是肯定的。当该值为 null 且未在我的自定义 JPanel Classes 中定义(如 row:0、col:1 中的单元格)时,我必须 return 的类型对象是什么?

这里是异常 java.lang.NullPointerException:

  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)

问题 3:

但是,当值不为null且我不知道Class类型时,如何处理return?

所有代码

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

public class TableButtonSlider extends JFrame {

  public TableButtonSlider()  {
    setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    setSize(600, 300);
    setVisible(true);
    setLocationRelativeTo(null);
  }
  public static void setLAF(Container container, String laf) {
    try {
      UIManager.setLookAndFeel(laf);
    } catch (ClassNotFoundException | InstantiationException
        | IllegalAccessException | UnsupportedLookAndFeelException e) {
    }
    SwingUtilities.updateComponentTreeUI(container);
  }
  static final JFrame frame = new JFrame();
  public JComponent makeUI() {

    String[] hdrsObjects = {"PanelButton Class", "PanelSlider Class"};
    Object[][] objectMatrix = new Object[3][2];
    objectMatrix[0][0] = new PanelButtonData(true);
    objectMatrix[1][0] = new PanelButtonData(false);
    objectMatrix[2][0] = new PanelButtonData(false);

//    objectMatrix[0][1] = new PanelSliderData(10, 30, 40);
    objectMatrix[1][1] = new PanelSliderData(0, 20, 40);
    objectMatrix[2][1] = new PanelSliderData(30, 40, 60);

    JTable Mytable = new JTable(new MyTableModel(objectMatrix, hdrsObjects)) {
      @Override public void updateUI() {
        super.updateUI();
        setRowHeight(30);
        TableColumn tc;

        tc = getColumn("PanelSlider Class");
        tc.setCellRenderer(new MyTableCellRenderer());
        tc.setCellEditor(new MyTableCellEditor());
        tc = getColumn("PanelButton Class");
        tc.setCellRenderer(new MyTableCellRenderer());
        tc.setCellEditor(new MyTableCellEditor());
      }
    };

    JScrollPane scrollPane = new JScrollPane(Mytable);

    JPanel pH = new JPanel();
    pH.setLayout(new BoxLayout(pH, BoxLayout.LINE_AXIS));

    JPanel pV = new JPanel();
    pV.setLayout(new BoxLayout(pV, BoxLayout.PAGE_AXIS));

    JButton bInsert = new JButton("Insert");
    bInsert.addActionListener((ActionEvent e) -> {
      ((MyTableModel)Mytable.getModel()).addRow(
        new Object[] {
          new PanelButtonData(false),
          new PanelSliderData(0, 25, 50)
        }
      );
      Mytable.updateUI();
    });

    JButton bMetal = new JButton("Metal");
    bMetal.addActionListener((ActionEvent) -> {
      setLAF(TableButtonSlider.this, "javax.swing.plaf.metal.MetalLookAndFeel");
    });

    JButton bMotif = new JButton("Motif");
    bMotif.addActionListener((ActionEvent) -> {
      setLAF(TableButtonSlider.this, "com.sun.java.swing.plaf.motif.MotifLookAndFeel");
    });

    JButton bNimbus = new JButton("Nimbus");
    bNimbus.addActionListener((ActionEvent) -> {
      setLAF(TableButtonSlider.this, "javax.swing.plaf.nimbus.NimbusLookAndFeel");
    });

    JButton bMacOS = new JButton("mac");
    bMacOS.addActionListener((ActionEvent evt) -> {
      setLAF(TableButtonSlider.this, "com.apple.laf.AquaLookAndFeel");
    });

    JButton bWindows = new JButton("win");
    bWindows.addActionListener((ActionEvent) -> {
      setLAF(TableButtonSlider.this, "com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
    });

    JButton bLinux = new JButton("lnx");
    bLinux.addActionListener((ActionEvent) -> {
      setLAF(TableButtonSlider.this, "com.sun.java.swing.plaf.gtk.GTKLookAndFeel");
    });

    pH.add(bInsert);
    pH.add(Box.createRigidArea(new Dimension(1,0)));
    pH.add(new JSeparator(JSeparator.VERTICAL));
    pH.add(Box.createRigidArea(new Dimension(1,0)));
    pH.add(bLinux);
    pH.add(bMacOS);
    pH.add(bWindows);
    pH.add(Box.createRigidArea(new Dimension(1,0)));
    pH.add(new JSeparator(JSeparator.VERTICAL));
    pH.add(Box.createRigidArea(new Dimension(1,0)));
    pH.add(bMetal);
    pH.add(bMotif);
    pH.add(bNimbus);

    pV.add(pH);
    pV.add(scrollPane);
    return pV;
  }
  public static void main(String... args) {
    UIManager.put("Slider.paintValue", false);
    EventQueue.invokeLater(() -> {
      TableButtonSlider f = new TableButtonSlider();
      f.getContentPane().add(f.makeUI());
    });
  }
}

class PanelButton extends JPanel {
  JButton jbtWavRow = new JButton();
  private final JPanel panel = new JPanel();
  PanelButton(PanelButtonData data) {
    super();
    panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
    panel.add(Box.createRigidArea(new Dimension(2,0)));
    panel.add(jbtWavRow);
    panel.add(Box.createRigidArea(new Dimension(2,0)));
    jbtWavRow.setFont(new Font("Monospaced", Font.PLAIN, 10));
    setData(data);
    setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
    add(panel);
  }
  public PanelButtonData getData() {
    return new PanelButtonData(jbtWavRow.getActionCommand().equals("+"));
  }
  public void setData(PanelButtonData data) {
    for (ActionListener al : jbtWavRow.getActionListeners()) {
      jbtWavRow.removeActionListener(al);
    }

    if(data.getIns()) {
      jbtWavRow.setText("Insert");
      jbtWavRow.setActionCommand("+");
      jbtWavRow.addActionListener((ActionEvent e) -> {
        JTable table = (JTable)SwingUtilities.getAncestorOfClass(
            JTable.class, (Component) e.getSource());
        table.getCellEditor().stopCellEditing();
        ((MyTableModel)table.getModel()).addRow(
            new Object[] {
              new PanelButtonData(false),
              new PanelSliderData(0, 25, 50)
            }
        );
        table.updateUI();
      });
    } else {
      jbtWavRow.setText("Remove");
      jbtWavRow.setActionCommand("-");
      jbtWavRow.addActionListener((ActionEvent e) -> {
        JTable table = (JTable) SwingUtilities.getAncestorOfClass(
            JTable.class, (Component) e.getSource());

        int row = table.getEditingRow();
        table.getCellEditor().stopCellEditing();
        ((MyTableModel) table.getModel()).removeRow(row);
        // table.updateUI();
      });
    }
  }
}

class PanelButtonData {
  private boolean add = false;
  PanelButtonData(Boolean add) { this.add = add; }
  public void setIns(Boolean add) { this.add = add; }
  public boolean getIns() { return add; }
}

class PanelSlider extends JPanel {
  private final JSlider jslChanger = new JSlider(SwingConstants.HORIZONTAL);
  private final JPanel panel = new JPanel();
  PanelSlider(PanelSliderData data) {
    super();
    panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
    panel.add(Box.createRigidArea(new Dimension(2,0)));
    panel.add(jslChanger);
    panel.add(Box.createRigidArea(new Dimension(2,0)));
    setData(data);
    setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
    add(panel);
  }
  public void setData(PanelSliderData data) {
    jslChanger.setMinimum(data.getMin());
    jslChanger.setValue(data.getVal());
    jslChanger.setMaximum(data.getMax());
  }
  // Used in MyTableCellRenderer.getCellEditorValue()
  public PanelSliderData getData() {
    return new PanelSliderData(jslChanger.getMinimum(), jslChanger.getValue(), jslChanger.getMaximum());
  }
}

class PanelSliderData {
  private Integer Min = 0;
  private Integer Val = 25;
  private Integer Max = 50;
  PanelSliderData(int Min, int Val, int Max) {
    this.Min = Min;
    this.Val = Val;
    this.Max = Max;
  }
  public Integer getMin() { return Min; }
  public Integer getVal() { return Val; }
  public Integer getMax() { return Max; }
}

class MyTableCellRenderer implements TableCellRenderer {
  private final PanelSlider ps = new PanelSlider(new PanelSliderData(0, 25, 50));
  private final PanelButton pb = new PanelButton(new PanelButtonData(false));
  @Override public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    if (value instanceof PanelButtonData) {
      pb.setData((PanelButtonData) value);
      return pb;
    }
    if (value instanceof PanelSliderData) {
      ps.setData((PanelSliderData) value);
      return ps;
    }

    //if (value != null)
    //  return (Component) value;
    //return this;

    return null;

    //return table.getDefaultRenderer(String.class).getTableCellRendererComponent(
    //    table, value, isSelected, hasFocus, row, column);

    //return new JLabel();
  }
}

class MyTableCellEditor extends AbstractCellEditor implements TableCellEditor {
  protected Object output;
  private final PanelButton pb = new PanelButton(new PanelButtonData(false));
  private final PanelSlider ps = new PanelSlider(new PanelSliderData(0, 25, 50));
  @Override public Object getCellEditorValue() {
    if (output instanceof PanelButton) {
      return pb.getData();
    }
    if (output instanceof PanelSlider) {
      return ps.getData();
    }
    return null;
  }
  @Override public Component getTableCellEditorComponent(
      JTable table, Object value, boolean isSelected, int row, int column) {
    if (value instanceof PanelButtonData) {
      pb.setData((PanelButtonData) value);
      output = pb;
      return pb;
    }
    if (value instanceof PanelSliderData) {
      ps.setData((PanelSliderData) value);
      output = ps;
      return ps;
    }
    return null;
  }
}

class MyTableModel extends AbstractTableModel {
//class MyTableModel extends DefaultTableModel {
  private Object[][] data;
  private Object[] columns;
  public MyTableModel(Object[][] data, Object[] columns) {
    this.data = data;
    this.columns = columns;
  }
  @Override public Object getValueAt(int rowIndex, int columnIndex) {
    if (data != null) {
      if (data.length > 0) {
        return data[rowIndex][columnIndex];
      }
    }
    return null;
  }
  @Override public int getColumnCount() {
    return ((columns == null) ? 0: columns.length);
  }
  @Override public int getRowCount() {
    return ((data == null) ? 0: data.length);
  }
  @Override public Class getColumnClass(int columnIndex) {
    if (data != null) {
      if (data.length > 0) {
        if (data[0][columnIndex] instanceof PanelButton) {
          return PanelButton.class;
        }
        if (data[0][columnIndex] instanceof PanelSlider) {
          return PanelSlider.class;
        }
        //return data[0][columnIndex].getClass();
        return String.class;
      }
    }
    return Object.class;
  }
  @Override public boolean isCellEditable(int rowIndex, int columnIndex) {
    return true;
  }
  @Override public void setValueAt(Object value, int row, int col) {
    data[row][col] = value;
    fireTableCellUpdated(row, col);
  }
  @Override public String getColumnName(int columnIndex) {
    return (String)columns[columnIndex];
  }
  //@Override 
  public void removeRow(int row) {
    Object[][] newData = new Object[data.length - 1][data[0].length];
    int rown = 0;
    for (int row1 = 0; row1 <data.length; row1++) {
      if (row1 != row) {
        for (int col = 0; col < data[0].length; col++) {
          newData[rown][col] = data[row1][col];
        }
        rown++;
      }
    }
    data = newData;
  }
  //@Override 
  public void addRow(Object[] rowData) {
    Object[][] newData;
    int maxCol;
    if ((data != null) && (data.length > 0)) {
      newData = new Object[data.length + 1][data[0].length];
      for (int row = 0; row <data.length; row++) {
        for (int col = 0; col < data[0].length; col++) {
          newData[row][col] = data[row][col];
        }
      }
      maxCol = data[0].length < rowData.length?data[0].length:rowData.length;
    } else {
      newData = new Object[1][rowData.length];
      maxCol = rowData.length;
    }
    //Insert rowData objects
    for (int col = 0; col < maxCol; col++) {
      newData[newData.length - 1][col] = rowData[col];
    }
    data = newData;
  }
}

编辑 1

  public static void main(String... args) {
    UIManager.put("Slider.paintValue", false);
    try {
      UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
    } catch (ClassNotFoundException | InstantiationException
        | IllegalAccessException | UnsupportedLookAndFeelException e) { }

    EventQueue.invokeLater(() -> {
      TableButtonSlider f = new TableButtonSlider();
      f.getContentPane().add(f.makeUI());
    });
  }

建立 nimbus LookAndFeel 就像在 main 方法上的第一个操作引发异常:

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.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.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:3904)
  at javax.swing.RepaintManager.run(RepaintManager.java:842)
  at javax.swing.RepaintManager.run(RepaintManager.java:814)
  at java.security.AccessController.doPrivileged(Native Method)
  at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
  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.access00(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.access0(EventQueue.java:97)
  at java.awt.EventQueue.run(EventQueue.java:709)
  at java.awt.EventQueue.run(EventQueue.java:703)
  at java.security.AccessController.doPrivileged(Native Method)
  at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
  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)

Is it valid (or secure) to create a xxxTableModel(...) with nonuniform Matrix?

是的,你可以做到,但你需要付出很多努力才能让它发挥作用,因为 JTable 确实不是为这种方式设计的

If Answer is YES for before Question. When the value is null and not defined (like cell in row:0, col:1) in my custom JPanel Classes, what Type Object I must return?

您必须 return Component 的一个实例,您不能 return null

But, When the value Is not null and I don't know the Class type, How handle the return?

这与你的第二个问题的答案相同,你必须 return Component 的一个实例,这取决于你想如何处理 null 之间的差异和“未知”值

观察结果

在深入了解你的代码后,你正在调用 updateUI 这是不合适的,这不是在你改变它的状态时触发对 UI 的“更新”的方法,它是仅用于(由系统)用于通知组件外观委托已更改

在您的 TableModel 中,您应该在更新模型时触发适当的事件,这将通知父级 JTable 并进行适当的更改。

我删除了您代码中对 updateUI 的所有调用,并简单地更新了您的 addRow 方法以调用 fireTableRowsInserted

public void addRow(Object[] rowData) {
    Object[][] newData;
    int maxCol;
    if ((data != null) && (data.length > 0)) {
        newData = new Object[data.length + 1][data[0].length];
        for (int row = 0; row < data.length; row++) {
            for (int col = 0; col < data[0].length; col++) {
                newData[row][col] = data[row][col];
            }
        }
        maxCol = data[0].length < rowData.length ? data[0].length : rowData.length;
    } else {
        newData = new Object[1][rowData.length];
        maxCol = rowData.length;
    }
    //Insert rowData objects
    for (int col = 0; col < maxCol; col++) {
        newData[newData.length - 1][col] = rowData[col];
    }
    data = newData;
    fireTableRowsInserted(data.length - 1, data.length - 1);
}

并且 NullPointerException 消失了。您还必须对 removeRow 进行适当的更改