以前选择的JTable单元在按键时触发编辑器,即使明确取消选择也是如此

时间:2013-01-22 23:58:46

标签: java swing user-interface jtable listener

按照trashgod的优秀示例here,我整理了一个小演示,以一种可能无可置疑的复杂方式完成一项简单的任务。下面显示的G​​UI显示一列代表真/假值的图标。如果单击图标,则会将值更改为与其相反的值。非常像一个复选框,但具有不同的外观,并且更具可扩展性(例如,我可以在将来更改它以循环使用十几个符号而不仅仅是两个布尔符号)。

enter image description here

我是通过使用自定义编辑器来实现的,该编辑器是JComponent的虚拟扩展。你甚至从未见过这个虚拟组件,因为一旦它收到MousePressed事件,就会导致编辑器fireEditingStopped()。除了我发现的一个奇怪的错误外,它的效果很好。如果你点击一个符号进行更改,然后将鼠标移动到屏幕上的其他位置并按下键盘键,它会在最后一个单元格中显示虚拟编辑器(这有效地将单元格清空),它会一直保持在那里直到你将鼠标移动到单元格中或单击其他单元格。

作为一个hacky修复此问题,我在渲染器中添加了一行,在渲染后总是取消选择整个表。这很好用,我已经确认整个表确实已被取消选择。但是,尽管如此,如果按键盘键,仍然将执行最后编辑的单元格的编辑器。我该如何防止这种行为?我的应用程序中有其他键盘监听器,如果没有选择单元格,我认为不应该执行任何编辑器。

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.imageio.ImageIO;
import javax.swing.AbstractCellEditor;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;

public class JTableBooleanIcons {

    private JFrame frame;
    private DefaultTableModel tableModel;
    private JTable table;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    JTableBooleanIcons window = new JTableBooleanIcons();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public JTableBooleanIcons() {
        initialize();
    }

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 450, 400);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        tableModel = new DefaultTableModel(new Object[]{"Words", "Pictures"},0);
        table = new JTable(tableModel);
        table.setRowHeight(40);
        tableModel.addRow(new Object[]{"click icon to change", false});
        tableModel.addRow(new Object[]{"click icon to change", true});
        tableModel.addRow(new Object[]{"click icon to change", false});
        tableModel.addRow(new Object[]{"click icon to change", true});
        tableModel.addRow(new Object[]{"click icon to change", false});
        tableModel.addRow(new Object[]{"click icon to change", true});
        tableModel.addRow(new Object[]{"click icon to change", false});
        tableModel.addRow(new Object[]{"click icon to change", true});

        frame.getContentPane().add(table, BorderLayout.CENTER);

        table.getColumn("Pictures").setCellRenderer(new BooleanIconRenderer());
        table.getColumn("Pictures").setCellEditor(new BooleanIconEditor());

    }

    @SuppressWarnings("serial")
    private class BooleanIconRenderer extends DefaultTableCellRenderer implements TableCellRenderer {

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                                                                        boolean hasFocus, int row, int col) {
            String iconFilename = null;
            if ((boolean) value) {
                iconFilename = "yes.png";
                value = true;
            } else {
                iconFilename = "no.png";
                value = false;
            }
            try {
                setIcon(new ImageIcon(ImageIO.read(BooleanIconRenderer.class.getResourceAsStream(iconFilename))));
            } catch (Exception e) {
                System.err.println("Failed to load the icon.");
            }
            if (isSelected) {
                this.setBackground(Color.white);
            } else {
                this.setBackground(Color.white);
            }
            table.getSelectionModel().clearSelection();
            return this;
        }
    }

    @SuppressWarnings("serial")
    private class BooleanIconEditor extends AbstractCellEditor implements TableCellEditor, MouseListener {

        private BooleanComponent boolComp;

        public BooleanIconEditor() {
            boolComp = new BooleanComponent(false);
            boolComp.addMouseListener(this);
        }

        @Override
        public Object getCellEditorValue() {
            return boolComp.getValue();
        }

        @Override
        public Component getTableCellEditorComponent(JTable table,
                Object value, boolean isSelected, int row, int column) {
            boolComp.setValue(! (boolean) value);
            return boolComp;
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            this.fireEditingStopped();
        }

        @Override
        public void mousePressed(MouseEvent e) {
            this.fireEditingStopped();

        }

        @Override
        public void mouseReleased(MouseEvent e) {
            this.fireEditingStopped();
        }

        @Override
        public void mouseEntered(MouseEvent e) {
            this.fireEditingStopped();
        }

        @Override
        public void mouseExited(MouseEvent e) {
            this.fireEditingStopped();
        }

    }

    @SuppressWarnings("serial")
    private class BooleanComponent extends JComponent {
        private boolean value;

        public BooleanComponent(boolean value) {
            this.value = value;
        }

        public boolean getValue() {
            return value;
        }

        public void setValue(boolean value) {
            this.value = value;
        }
    }
}

2 个答案:

答案 0 :(得分:5)

您遇到问题的原因是您实际上没有在编辑器中渲染任何内容。您依赖的事实是,用户只需要“单击”单元格即可更改其值。我认为这有点短视,但是,这是你的计划。

要立即修复,只需添加...

@Override
public boolean isCellEditable(EventObject e) {
    return (e instanceof MouseEvent);
}

给你BooleanIconEditor

旁注。

在单元格渲染器中,您不应加载图像。这些应该作为构造函数的一部分预先缓存,甚至更好,作为static字段变量。你可能会这样做,但以防万一。

<强>更新

我在谈论这个话题。您应该避免在渲染器中更改表的状态。这真的是不安全的,当桌子试图重新渲染你所做的改变时,可能会让你陷入无限的地狱循环......

如果你真的想要隐藏选择(我不确定你为什么会这样做),你可以设置表的选择以匹配表背景颜色或使其成为透明颜色。不要忘记更改选择前景;)

更新#2

支持键盘的示例;) - 无法抗拒......

public class JTableBooleanIcons {

    private JFrame frame;
    private DefaultTableModel tableModel;
    private JTable table;
    private ImageIcon yesIcon;
    private ImageIcon noIcon;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    JTableBooleanIcons window = new JTableBooleanIcons();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public JTableBooleanIcons() {
        try {
            yesIcon = (new ImageIcon(ImageIO.read(BooleanIconRenderer.class.getResourceAsStream("/yes.png"))));
            noIcon = (new ImageIcon(ImageIO.read(BooleanIconRenderer.class.getResourceAsStream("/no.png"))));
        } catch (Exception e) {
            e.printStackTrace();
        }
        initialize();
    }

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 450, 400);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        tableModel = new DefaultTableModel(new Object[]{"Words", "Pictures"}, 0);
        table = new JTable(tableModel);
        table.setRowHeight(40);
        tableModel.addRow(new Object[]{"click icon to change", false});
        tableModel.addRow(new Object[]{"click icon to change", true});
        tableModel.addRow(new Object[]{"click icon to change", false});
        tableModel.addRow(new Object[]{"click icon to change", true});
        tableModel.addRow(new Object[]{"click icon to change", false});
        tableModel.addRow(new Object[]{"click icon to change", true});
        tableModel.addRow(new Object[]{"click icon to change", false});
        tableModel.addRow(new Object[]{"click icon to change", true});

        frame.getContentPane().add(table, BorderLayout.CENTER);

        table.getColumn("Pictures").setCellRenderer(new BooleanIconRenderer());
        table.getColumn("Pictures").setCellEditor(new BooleanIconEditor());

    }

    @SuppressWarnings("serial")
    private class BooleanIconRenderer extends DefaultTableCellRenderer implements TableCellRenderer {

        public BooleanIconRenderer() {
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                boolean hasFocus, int row, int col) {
            super.getTableCellRendererComponent(table, null, isSelected, hasFocus, row, col);
            if ((boolean) value) {
                setIcon(yesIcon);
            } else {
                setIcon(noIcon);
            }
            return this;
        }
    }

    @SuppressWarnings("serial")
    private class BooleanIconEditor extends AbstractCellEditor implements TableCellEditor, MouseListener {

        private BooleanComponent boolComp;
        private boolean isMouseEvent;

        public BooleanIconEditor() {
            boolComp = new BooleanComponent(false);
            boolComp.addMouseListener(this);
            InputMap im = boolComp.getInputMap(JComponent.WHEN_FOCUSED);
            ActionMap am = boolComp.getActionMap();

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "click");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "click");

            am.put("click", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println("Clicked");
                    boolComp.setValue(!boolComp.getValue());
                }
            });

        }

        @Override
        public boolean isCellEditable(EventObject e) {
            isMouseEvent = e instanceof MouseEvent;
            return true; //(e instanceof MouseEvent);
        }

        @Override
        public Object getCellEditorValue() {
            return boolComp.getValue();
        }

        @Override
        public Component getTableCellEditorComponent(JTable table,
                Object value, boolean isSelected, int row, int column) {
            boolean state = (boolean) value;
            if (isMouseEvent) {
                state = !state;
            }
            boolComp.setValue(state);
            boolComp.setOpaque(isSelected);
            boolComp.setBackground(table.getSelectionBackground());
            return boolComp;
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            this.fireEditingStopped();
        }

        @Override
        public void mousePressed(MouseEvent e) {
            this.fireEditingStopped();

        }

        @Override
        public void mouseReleased(MouseEvent e) {
            this.fireEditingStopped();
        }

        @Override
        public void mouseEntered(MouseEvent e) {
            this.fireEditingStopped();
        }

        @Override
        public void mouseExited(MouseEvent e) {
            this.fireEditingStopped();
        }
    }

    @SuppressWarnings("serial")
    private class BooleanComponent extends JLabel {

        private boolean value;

        public BooleanComponent(boolean value) {
            this.value = value;
        }

        public boolean getValue() {
            return value;
        }

        public void setValue(boolean value) {
            this.value = value;
            if (value) {
                setIcon(yesIcon);
            } else{
                setIcon(noIcon);
            }
        }
    }
}

答案 1 :(得分:4)

虽然@ Mad的answer显示了如何实现鼠标处理和键绑定,但另一种方法是使用JToggleButtonJCheckBox的父级和合适的Icon。切换按钮已经知道如何显示所选的Icon和处理事件。修改后的ValueRenderer如下所示; ValueEditor没有变化;文字是可选的。

image

private static class ValueRenderer extends JToggleButton
    implements TableCellRenderer {

    private static final Color hilite = new Color(0xE8E8E8);
    private static final Icon YES = UIManager.getIcon("InternalFrame.maximizeIcon");
    private static final Icon NO = UIManager.getIcon("InternalFrame.closeIcon");

    public ValueRenderer() {
        this.setOpaque(true);
        this.setIcon(NO);
        this.setSelectedIcon(YES);
    }
    ...
}