Swing编程 — JTable遇到的坑和经验总结


文章目录

  • Swing编程 — JTable遇到的坑和经验总结
  • 1. TableCellRender 无法设置背景颜色
  • 2. TableCellRender 和 CellEditor 比较优雅的使用方式 (个人认为)
  • Render代码
  • Editor 代码
  • 在TableColumn中使用
  • 可以看到
  • 3. TableCellRender + TableCellEditor 所选Cell内容不更新


1. TableCellRender 无法设置背景颜色

在使用 自定义的TableCellRender 渲染Jtable页面,结果死活设置不上颜色,导致界面会产生一种无法点击的错觉,结果发现 是因为CellRender 继承的是 JComponent, 给JComoponent 和他的子组件 设置背景颜色都会不起作用

2. TableCellRender 和 CellEditor 比较优雅的使用方式 (个人认为)

JTable的界面展示分工是特别明确的。

之前我一直想用 TableCellRender 来展示 JButton 并完成点击事件,结果毛用没有,按钮根本点击不了。

原因是,当你点击的时候,如果该列是可以被编辑的,Jtable会把界面切换到 Editor界面让你点击。
如果你没有设置Editor,他会显示默认的Editor(也就是一个JTextField 编辑框或者一个JCheckBox)。如果你设置该列的Eiditor, swing不会给render分发任何的事件

后来发现了需要Editor和Render配合使用才能达到编辑的效果,所以总结了下面的一个比较方便的使用方式,注意:这种使用方式只适用于Editor和Render界面一致的情况,不过我认为大部分的设计它们都是一致的,因为不一致不符合所见即所得,增加用户的理解和使用成本。

这种方式的核心就是,Editor复用Render的Ui
既然Render已经完成了界面的展示,那么直接复用是最方便的,下面是示例,下面的Cell展示了四个按钮,分别是启动,配置,删除和定位。添加Editor的目的就是为了能让他们响应点击事件。你会发现如果仅仅使用Render展示,按钮是无法点击的,因为能响应事件的是Editor而不是Render

java TableField 设置不允许修改_开发语言

Render代码

@Getter
public class CheckItemOptionTableCellRender extends JToolBar implements TableCellRenderer {

    protected final JButton startOrKillButton = new JButton();
    protected final JButton settingButton = new JButton(UiUtil.getSvgIcon("icon/setting_filled.svg", 16));
    protected final JButton deleteButton = new JButton(UiUtil.getSvgIcon("icon/delete-fill.svg", 16));
    protected final JButton positionButton = new JButton(UiUtil.getSvgIcon("icon/position.svg", 16));

    public CheckItemOptionTableCellRender() {
        setBorder(new EmptyBorder(0, 10, 0, 10));
        setOrientation(HORIZONTAL);
        add(startOrKillButton);
        add(settingButton);
        add(deleteButton);
        add(positionButton);
    }

    public Component getTableCellRendererComponent(JTable table, Object value,
                                                   boolean isSelected, boolean hasFocus,
                                                   int row, int column) {
        // ..... 
         if (isSelected) {
            setBackground(table.getSelectionBackground());
        } else {
            setBackground(table.getBackground());
        }
        return this;

    }

}

Editor 代码

在Editor 中内置一个 render,通过render显示界面

public class CheckItemOptionTableCellEditor extends AbstractCellEditor implements TableCellEditor, ActionListener {

    private JTable table;
    private int row;
    private Object item;

    private final CheckItemOptionTableCellRender render;


    public CheckItemOptionTableCellEditor() {
        this.render = new CheckItemOptionTableCellRender();
        // 给按钮设置点击事件
        this.render.getDeleteButton().addActionListener(this);
        this.render.getStartOrKillButton().addActionListener(this);
        this.render.getPositionButton().addActionListener(this);
        this.render.getSettingButton().addActionListener(this);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == this.render.getSettingButton()) {
            // do something
        }
        if (e.getSource() == this.render.getDeleteButton()) {
            // do something
        }
        if (e.getSource() == this.render.getStartOrKillButton() && checkItem instanceof LocalCheckItem) {
             // do something
        }
        if (e.getSource() == this.render.getPositionButton()) {
            // do something
        }
        ((AbstractTableModel) table.getModel()).fireTableRowsUpdated(row, row);
         // 关键点,告诉Jtable你完成了编辑,否则,在你鼠标未离开这个Cell的时候,swing不会调用你的
         // getTableCellEditorComponent 方法,导致所选的行页面不会刷新
        stopCellEditing();
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
        this.table = table;
        // 拿到所选的行
        this.row = row;
        // 拿到需要编辑的值
        this.item= value;
        // 利用Render渲染界面
        return this.render.getTableCellRendererComponent(table, value, isSelected, isSelected, row, column);
    }

    @Override
    public Object getCellEditorValue() {
        return null;
    }
}

在TableColumn中使用

TableColumn optionColumn = table.getColumnModel().getColumn(2);
optionColumn.setCellRenderer(new CheckItemOptionTableCellRender());
optionColumn.setCellEditor(new CheckItemOptionTableCellEditor());

可以看到

上面尽量复用了Render的代码,而且以后如果需要修改界面的话,只需要修改Render的代码即可,只有在涉及到交互变化的时候才需要修改Editor的代码。

3. TableCellRender + TableCellEditor 所选Cell内容不更新

在上面的Editor中,我调用了一个 stopCellEditing() 的函数,这个是为了解决Cell内容不更新问题的,什么情况下会出现这个问题呢?

我以上面的界面举例,我在 render 的 getTableCellEditorComponent 中渲染了界面,在Editor中调用了这个函数,但是你会发现,如果你没有调用 stopCellEditing() 这个函数,在你点击了按钮,修改了Cell内容后,你会发现Cell的内容没有变化,必须在你选中其他cell以后内容才会改变。

看了一下swing的源码后发现,你在编辑cell的时候是不会调用 getTableCellEditorComponent 这个函数的。如下

private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
      // 判断当前cell是否正在被编辑
        if (table.isEditing() && table.getEditingRow()==row &&
                                 table.getEditingColumn()==column) {
             // 被编辑的情况下不会重新绘制界面,而是只会更新编辑的界面
            Component component = table.getEditorComponent();
            component.setBounds(cellRect);
            component.validate();
        }
        else {
            TableCellRenderer renderer = table.getCellRenderer(row, column);
            Component component = table.prepareRenderer(renderer, row, column);
            rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
                                        cellRect.width, cellRect.height, true);
        }
    }

原因是什么呢?是因为在编辑过程中的编辑的值可能还没有提交,那么cell中的值可能还是提交前的值,如果这个时候调用了 getTableCellEditorComponent 这个方法,就会返回一个用原来的值渲染的界面把当前编辑了还没有提交的页面给覆盖掉。所以这个机制相当于保证了你编辑过程中不会被界面刷新影响,也意味着如果你自己实现Editor你必须告诉swing你什么时候编辑完成了。 这也就是要在点击按钮之后调用 stopCellEditing() 的原因。