【问题标题】:How do I make Java jtables, cell editors and undo work together w/o creating extranious undo event?如何使 Java jtable、单元格编辑器和撤消一起工作而无需创建无关的撤消事件?
【发布时间】:2016-02-17 02:55:38
【问题描述】:

在下面的代码中,我为第一列创建了一个带有自定义单元格编辑器的 jtable,然后将撤消功能添加到表中。当您运行该程序时,该程序允许您更改第一列中的值(通过将“d”和“e”附加到已经存在的“abc”中进行测试)。现在输入 control-z(撤消)并再次输入 control-z。它按预期工作。但现在再次输入 control-z(撤消)。这次删除了“abc”。看起来摆动系统正在设置列的初始值并为该操作创建一个撤消事件,然后用户可以撤消该操作。我的问题 - 我如何编写代码以便用户只能撤消用户所做的操作?

import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.DefaultCellEditor;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JRootPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;

public class UndoExample extends JFrame {

private static final long serialVersionUID = 1L;;   
static Boolean objEnableUndoRedoActions = true; 
UndoExample rootFrame;

public UndoExample() {
    // This procedure starts the whole thing off.

    //Create table
    final String[] tableColumns = {"Column 1", "Column 2"};
    JTable tabUndoExample = new JTable(
            new DefaultTableModel(null, tableColumns) {
                private static final long serialVersionUID = 1L;                
    });
    final DefaultTableModel tabUndoExampleModel = (DefaultTableModel) tabUndoExample
            .getModel();
    tabUndoExampleModel.addRow(new Object[]{"abc", true});
    tabUndoExampleModel.addRow(new Object[]{"zyw", false});

    // Create the undo/redo manager
    UndoManager objUndoManager = new UndoManager();

    // Create a cell editor
    JTextField tfTabField = new JTextField();
    TableCellEditor objEditor = new DefaultCellEditor(tfTabField);

    // Make the cell editor the default editor for this table's first column
    tabUndoExample.getColumnModel().getColumn(0)
        .setCellEditor(objEditor);

    // Create the undo action on the field's document for the column
    tfTabField.getDocument().addUndoableEditListener(
            new uelUndoRedoTableCellField(objUndoManager, tabUndoExample));

    // Allow undo and redo to be entered by the user
    UndoRedoSetKeys(this, "Example", objUndoManager);
    tabUndoExample.setInheritsPopupMenu(true);

     //Add the table to the frame and show the frame         
    this.add(tabUndoExample);
    this.pack();
    setLocationRelativeTo(null);
    }

public static void main(final String[] args) {
    // Launches the application. This is required syntax.

    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            try {       
                final UndoExample rootFrame = new UndoExample();
                rootFrame.setVisible(true);             
            } catch (final Exception e) {

            }
        }
    });
}

@SuppressWarnings("serial")
static class aueUndoRedoTableCellField extends AbstractUndoableEdit {
    // Wrap the text edit action item as we need to add the table
    // row and column information.  This code is invoked when the
    // code sees an undo event created and then later when the 
    // user requests an undo/redo.

    JTable objTable = null;
    UndoableEdit objUndoableEdit;
    int objCol = -1;
    int objRow = -1;

    public aueUndoRedoTableCellField(UndoableEdit undoableEdit,
            JTable table, int row, int col) {
        super();
        objUndoableEdit = undoableEdit;
        objTable = table;
        objCol = col;
        objRow = row;
    }

    public void redo() throws CannotRedoException {
        // When the user enters redo (or undo), this code sets
        // that we are doing an redo (or undo), sets the cursor
        // to the right location, and then does the undo (or redo)
        // to the table cell.  
        UndoRedoManagerSetEnabled(false);
        super.redo();
        @SuppressWarnings("unused")
        boolean success = objTable.editCellAt(objRow, objCol);
        objTable.changeSelection(objRow, objCol, false, false);
        objUndoableEdit.redo();
        UndoRedoManagerSetEnabled(true);
    }

    public void undo() throws CannotUndoException {
        super.undo();
        UndoRedoManagerSetEnabled(false);
        @SuppressWarnings("unused")
        boolean success = objTable.editCellAt(objRow, objCol);
        objTable.changeSelection(objRow, objCol, false, false);
        objUndoableEdit.undo();
        UndoRedoManagerSetEnabled(true);
    }
}

static class aUndoRedo extends AbstractAction {
    // This code is bound to the undo/redo keystrokes and tells
    // Java what commands to run when the keys are later entered
    // by the user.

    private static final long serialVersionUID = 1L;
    Boolean objUndo = true;
    UndoManager objUndoManager = null;
    final String objLocation;

    public aUndoRedo(Boolean Undo, UndoManager undoManager, String location) {
        super();
        objUndo = Undo;
        objUndoManager = undoManager;
        objLocation = location;
    }

    @Override
    public void actionPerformed(ActionEvent ae) {
        try {
            // See if operation allowed
            if (!objUndoManager.canUndo() && objUndo
                    || !objUndoManager.canRedo() && !objUndo)
                return;
            UndoRedoManagerSetEnabled(false);
            if (objUndo) {
                objUndoManager.undo();
            } else {
                objUndoManager.redo();
            }
            UndoRedoManagerSetEnabled(true);
            // Catch errors and let user know
        } catch (Exception e) {

            UndoRedoManagerSetEnabled(true);
        }
    }
}

static class uelUndoRedoTableCellField implements UndoableEditListener {
    // This action is called when the user changes the table's
    // text cell.  It saves the change for later undo/redo.

    private UndoManager objUndoManager = null;
    private JTable objTable = null;

    public uelUndoRedoTableCellField(UndoManager undoManager,
            JTable table) {
        objUndoManager = undoManager;
        objTable = table;
    }

    @Override
    public void undoableEditHappened(UndoableEditEvent e) {
        // Remember the edit but only if the code isn't doing
        // an undo or redo currently.
        if (UndoRedoManagerIsEnabled()) {
            objUndoManager.addEdit(new aueUndoRedoTableCellField(e
                    .getEdit(), objTable, objTable.getSelectedRow(),
                    objTable.getSelectedColumn()));
        }
    }
}

static public Boolean UndoRedoManagerIsEnabled() {
    // See if we are currently doing an undo/redo.
    // Return true if so.
    return objEnableUndoRedoActions;
}

static public void UndoRedoManagerSetEnabled(Boolean state) {
    // Set the state of whether we are in undo/redo code.
    objEnableUndoRedoActions = state;
}


static void UndoRedoSetKeys(JFrame frame, final String location, UndoManager undoManager) {
    // Allow undo and redo to be called via these keystrokes for this dialog
    final String cntl_y = "CNTL_Y";
    final KeyStroke ksCntlY = KeyStroke.getKeyStroke("control Y");
    final String cntl_z = "CNTL_Z";
    final KeyStroke ksCntlZ = KeyStroke.getKeyStroke("control Z");

    JRootPane root = frame.getRootPane();
    root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
            .put(ksCntlZ, cntl_z);
    root.getActionMap().put(
            cntl_z,
            new aUndoRedo(true, undoManager, location));
    root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
            .put(ksCntlY, cntl_y);
    root.getActionMap().put(
            cntl_y,
            new aUndoRedo(false, undoManager, location));
}

}

【问题讨论】:

  • 点击 ctrl+z 将表格发送到编辑模式并发送 ctrl+z 击键
  • 我不相信是这样的。您将在 UndoRedoSetKeys 过程中看到我映射 ctrl+z 以实现撤消事件。此外,如果您运行该程序,该程序会执行撤消操作。我还使用调试器证明,一旦用户输入 ctrl+z,就会调用执行撤消的代码。您是说 ctrl+z 事件也被发送到表中吗?
  • 是的,您可以应用this 来停止它,并停止撤消工作。 JTextField 开始时没有文本,然后提供单元格的值并放在屏幕上,启动编辑模式的击键将发送到字段(因此,如果您键入 a,它将被附加到文本的结尾),而是触发撤消,将字段发送回初始的空白状态:P
  • “你是说 ctrl+z 事件也被发送到桌子上吗?” - 不多说,是的,JTable 响应事件,但它会在编辑器在屏幕上建立之后将关键事件重新分配给编辑器。尝试在键盘上输入任何字符,该字符将附加到字段的文本中;)

标签: java swing jtable undo tablecelleditor


【解决方案1】:

当你按下一个键时,会发生一系列事情。 JTable,处理击键,检查单元格是否可编辑(如TableModel),然后询问编辑器当前选定的单元格是否应该编辑单元格(CellEditor#isCellEditable(EventObject))。

如果此方法返回true,则准备好编辑器,将TableModel中的值应用于编辑器(即调用setText),并将编辑器添加到JTable,最后,触发编辑模式的事件被重新分派给编辑器,在你的情况下是 Ctrl+Z,然后触发和撤消事件,返回编辑器的初始状态(在调用 setText 之前)。

您可以尝试使用类似...

TableCellEditor objEditor = new DefaultCellEditor(tfTabField) {
    @Override
    public boolean isCellEditable(EventObject anEvent) {
        boolean isEditable = super.isCellEditable(anEvent); //To change body of generated methods, choose Tools | Templates.
        if (isEditable && anEvent instanceof KeyEvent) {
            KeyEvent ke = (KeyEvent) anEvent;
            if (ke.isControlDown() && ke.getKeyCode() == KeyEvent.VK_Z) {
                isEditable = false;
            }
        }
        return isEditable;
    }

};

防止在发生特定击键时将JTable 置于编辑状态

更新

因此,根据 Andrew 从JTextArea setText() & UndoManager 的回答,我设计了一个“可配置”UndoableEditListener,可以设置为忽略可撤消的操作,例如...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;

public class FixedField {

    public static void main(String[] args) {
        new FixedField();
    }

    public FixedField() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public static class UndoableEditHandler implements UndoableEditListener {

        private static final int MASK
                = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();

        private UndoManager undoManager = new UndoManager();

        private boolean canUndo = true;

        public UndoableEditHandler(JTextField field) {
            Document doc = field.getDocument();
            doc.addUndoableEditListener(this);
            field.getActionMap().put("Undo", new AbstractAction("Undo") {
                @Override
                public void actionPerformed(ActionEvent evt) {
                    try {
                        if (undoManager.canUndo()) {
                            undoManager.undo();
                        }
                    } catch (CannotUndoException e) {
                        System.out.println(e);
                    }
                }
            });
            field.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, MASK), "Undo");
            field.getActionMap().put("Redo", new AbstractAction("Redo") {
                @Override
                public void actionPerformed(ActionEvent evt) {
                    try {
                        if (undoManager.canRedo()) {
                            undoManager.redo();
                        }
                    } catch (CannotRedoException e) {
                        System.out.println(e);
                    }
                }
            });
            field.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, MASK), "Redo");
        }

        @Override
        public void undoableEditHappened(UndoableEditEvent e) {
            if (canUndo()) {
                undoManager.addEdit(e.getEdit());
            }
        }

        public void setCanUndo(boolean canUndo) {
            this.canUndo = canUndo;
        }

        public boolean canUndo() {
            return canUndo;
        }

    }

    public class TestPane extends JPanel {

        public TestPane() {
            JTextField field = new JTextField(10);
            UndoableEditHandler handler = new UndoableEditHandler(field);

            handler.setCanUndo(false);
            field.setText("Help");
            handler.setCanUndo(true);
            add(field);
        }

    }

}

现在,您将不得不使用自己的设备 TableCellEditor 来支持此功能,例如...

public static class MyCellEditor extends AbstractCellEditor implements TableCellEditor {

    private JTextField editor;
    private UndoableEditHandler undoableEditHandler;

    public MyCellEditor(JTextField editor) {
        this.editor = editor;
        undoableEditHandler = new UndoableEditHandler(editor);
        editor.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireEditingStopped();
            }
        });
    }

    @Override
    public Object getCellEditorValue() {
        return editor.getText();
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
        undoableEditHandler.setCanUndo(false);
        editor.setText(value == null ? null : value.toString());
        undoableEditHandler.setCanUndo(true);
        return editor;
    }

}

【讨论】:

  • 首先 - 感谢您的时间和帮助。我在我提供的第一个代码的明显位置添加了上面的代码(如果有用,我可以在这里给你整个代码)并测试它。它仍然具有相同的行为。第三次撤消导致值“abc”被擦除。另外,为了确保我们是同步的。当我编辑单元格时,我想要的行为是第一个撤消删除,然后是“e”,然后是“d”,第三个撤消什么都不做,因为当时没有撤消事件。
  • 是的,这是我一直在说的部分内容。当调用DefaultCellEditor#getTableCellEditorComponent 时,它会调用JTextField 中的setText,将整个String 值传递给一个单一的、可撤消的操作中的单元格。因此,一旦编辑器处于编辑模式,如果按 ctrl+z,整个值将被撤消。我能做的最好的就是停止,如果在您处于编辑模式之前按 ctrl+z 导致编辑器出现空白
  • 考虑一下如果将文本块粘贴到字段中会发生什么。当您按 ctrl+z 时应该删除整个块还是只删除一个字符?
  • 再次 - 谢谢。但该解决方案对我的用户来说不是一个好的解决方案。即使在编辑模式下,人们也应该能够撤消 - 事实上 - 那是撤消最有用的时候!对我来说,这听起来像是一个 Swing 错误。该表不应该生成第一个撤消事件。使用标准单元格编辑器时我不明白。不幸的是,我需要默认的单元格编辑器,因为我希望单元格是 JTextAreas,因此它们可以是多行。我唯一的另一个想法是以某种方式以编程方式吃掉第一个撤消。怎么做看起来很痛苦。
  • 这不是错误,而是它的工作原理。如果您真的,真的,真的想满足用户的期望,您可以将单元格值的每个字符单独应用于编辑器
猜你喜欢
  • 1970-01-01
  • 2011-05-11
  • 1970-01-01
  • 2011-09-29
  • 2014-04-12
  • 2017-07-26
  • 2019-08-22
  • 1970-01-01
  • 2023-03-23
相关资源
最近更新 更多