10

事实上,我知道如何在一个 JTextField 中使用CTRL+ Z(Undo) 和CTRL+ (Redo) 来实现。Y但是我的 Swing 应用程序中有数百个文本组件,所以有没有办法将它应用于我的应用程序中的所有文本组件,所以当我在任何文本组件中单击CTRL+时Z,它会撤消该字段中的最后一个条目?

我试图在EventQueue中实现它,但它不起作用!

4

3 回答 3

7

要使所有文本组件“可撤消”,您可以使用自己的子类简单地创建它们,例如:

public class MyTextField extends JTextField {
    public MyTextField() {
        final UndoManager undoMgr = new UndoManager();

        // Add listener for undoable events
        getDocument().addUndoableEditListener(new UndoableEditListener() {
            public void undoableEditHappened(UndoableEditEvent pEvt) {
                undoMgr.addEdit(pEvt.getEdit());
            }
        });

        // Add undo/redo actions
        getActionMap().put(UNDO_ACTION, new AbstractAction(UNDO_ACTION) {
            public void actionPerformed(ActionEvent pEvt) {
                try {
                    if (undoMgr.canUndo()) {
                        undoMgr.undo();
                    }
                } catch (CannotUndoException e) {
                    e.printStackTrace();
                }
            }
        });
        getActionMap().put(REDO_ACTION, new AbstractAction(REDO_ACTION) {
            public void actionPerformed(ActionEvent pEvt) {
                try {
                    if (undoMgr.canRedo()) {
                        undoMgr.redo();
                    }
                } catch (CannotRedoException e) {
                    e.printStackTrace();
                }
            }
        });

        // Create keyboard accelerators for undo/redo actions (Ctrl+Z/Ctrl+Y)
        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK),
            UNDO_ACTION);
        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK),
            REDO_ACTION);
    }
}

然后不是创建JTextFields,而是创建MyTextFields。限制是您可能还想JTextArea为 other 创建另一个子类,依此类推JTextComponent。因此,也可以使用实用程序方法将撤消/重做功能添加到任何现有的JTextCompoent

public final static String UNDO_ACTION = "Undo";

public final static String REDO_ACTION = "Redo";

public static void makeUndoable(JTextComponent pTextComponent) {
    final UndoManager undoMgr = new UndoManager();

    // Add listener for undoable events
    pTextComponent.getDocument().addUndoableEditListener(new UndoableEditListener() {
        public void undoableEditHappened(UndoableEditEvent evt) {
            undoMgr.addEdit(evt.getEdit());
        }
    });

    // Add undo/redo actions
    pTextComponent.getActionMap().put(UNDO_ACTION, new AbstractAction(UNDO_ACTION) {
        public void actionPerformed(ActionEvent evt) {
            try {
                if (undoMgr.canUndo()) {
                    undoMgr.undo();
                }
            } catch (CannotUndoException e) {
                e.printStackTrace();
            }
        }
    });
    pTextComponent.getActionMap().put(REDO_ACTION, new AbstractAction(REDO_ACTION) {
        public void actionPerformed(ActionEvent evt) {
            try {
                if (undoMgr.canRedo()) {
                    undoMgr.redo();
                }
            } catch (CannotRedoException e) {
                e.printStackTrace();
            }
        }
    });

    // Create keyboard accelerators for undo/redo actions (Ctrl+Z/Ctrl+Y)
    pTextComponent.getInputMap().put(
        KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK), UNDO_ACTION);
    pTextComponent.getInputMap().put(
        KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK), REDO_ACTION);
}

最终且绝对透明的解决方案是使用Lnf features实现您自己的 UI 类,但您可能需要在使所有 TextComponents 都可撤消之前三思而后行,例如,如果您经常对这些组件执行大量文本修改...

于 2014-02-15T21:32:11.723 回答
3
  • 您可以凝胶化 API 中实现的内置 KeyBindings 快捷方式列表

  • 请注意,您必须检查或准备您的代码以获得所有可访问的外观和感觉

  • 您可以获得内置的 KeyBindings 快捷方式并按照您的预期替换它,系统剪贴板经常被修改,

  • 从未尝试过,但在这个论坛上你可以找到相关信息如何更改,替换 KeyBindings 快捷方式

内置 KeyBindings 快捷方式列表

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
import javax.swing.filechooser.*;

public class KeyBindings implements ItemListener {

    private static final String PACKAGE = "javax.swing.";
    private static final String[] COLUMN_NAMES = {"Action", "When Focused", "When In Focused Window", "When Ancestor"};
    private static String selectedItem;
    private JComponent contentPane;
    private JMenuBar menuBar;
    private JTable table;
    private JComboBox comboBox;
    private Hashtable<String, DefaultTableModel> models;

    /*
     *  Constructor
     */
    public KeyBindings() {
        models = new Hashtable<String, DefaultTableModel>();
        contentPane = new JPanel(new BorderLayout());
        contentPane.add(buildNorthComponent(), BorderLayout.NORTH);
        contentPane.add(buildCenterComponent(), BorderLayout.CENTER);
        resetComponents();
    }

    /*
     *  The content pane should be added to a high level container
     */
    public JComponent getContentPane() {
        return contentPane;
    }

    /*
     *  A menu can also be added which provides the ability to switch
     *  between different LAF's.
     */
    public JMenuBar getMenuBar() {
        if (menuBar == null) {
            menuBar = createMenuBar();
        }
        return menuBar;
    }

    /*
     *  This panel is added to the North of the content pane
     */
    private JComponent buildNorthComponent() {
        comboBox = new JComboBox();
        JLabel label = new JLabel("Select Component:");
        label.setDisplayedMnemonic('S');
        label.setLabelFor(comboBox);
        JPanel panel = new JPanel();
        panel.setBorder(new EmptyBorder(15, 0, 15, 0));
        panel.add(label);
        panel.add(comboBox);
        return panel;
    }

    /*
     *  Check the key name to see if it is the UI property
     */
    private String checkForUIKey(String key) {
        if (key.endsWith("UI") && key.indexOf('.') == -1) {
            String componentName = key.substring(0, key.length() - 2);//  Ignore these components
            if (componentName.equals("PopupMenuSeparator") || componentName.equals("ToolBarSeparator") || componentName.equals("DesktopIcon")) {
                return null;
            } else {
                return componentName;
            }
        }
        return null;
    }

    /*
     **  Build the emtpy table to be added in the Center
     */
    private JComponent buildCenterComponent() {
        DefaultTableModel model = new DefaultTableModel(COLUMN_NAMES, 0);
        table = new JTable(model) {

            private static final long serialVersionUID = 1L;

            @Override
            public boolean isCellEditable(int row, int column) {
                return false;
            }
        };
        table.setAutoCreateColumnsFromModel(false);
        table.getColumnModel().getColumn(0).setPreferredWidth(200);
        table.getColumnModel().getColumn(1).setPreferredWidth(200);
        table.getColumnModel().getColumn(2).setPreferredWidth(200);
        table.getColumnModel().getColumn(3).setPreferredWidth(200);
        Dimension d = table.getPreferredSize();
        d.height = 350;
        table.setPreferredScrollableViewportSize(d);
        table.getTableHeader().setFocusable(true);
        return new JScrollPane(table);
    }

    /*
     *  When the LAF is changed we need to reset all the items
     */
    private void resetComponents() {
        models.clear();
        ((DefaultTableModel) table.getModel()).setRowCount(0);
        Vector<String> comboBoxItems = new Vector<String>(50);//        buildItemsMap();
        UIDefaults defaults = UIManager.getLookAndFeelDefaults();
        for (Object key : defaults.keySet()) { //  All Swing components will have a UI property.
            String componentName = checkForUIKey(key.toString());
            if (componentName != null) {
                comboBoxItems.add(componentName);
            }
        }
        Collections.sort(comboBoxItems);
        comboBox.removeItemListener(this);
        comboBox.setModel(new DefaultComboBoxModel(comboBoxItems));
        comboBox.setSelectedIndex(-1);
        comboBox.addItemListener(this);
        comboBox.requestFocusInWindow();
        if (selectedItem != null) {
            comboBox.setSelectedItem(selectedItem);
        }
    }

    /**
     *  Create menu bar
     */
    private JMenuBar createMenuBar() {
        JMenuBar menuBar1 = new JMenuBar();
        menuBar1.add(createFileMenu());
        menuBar1.add(createLAFMenu());
        return menuBar1;
    }

    /**
     *  Create menu items for the Application menu
     */
    private JMenu createFileMenu() {
        JMenu menu = new JMenu("Application");
        menu.setMnemonic('A');
        menu.addSeparator();
        menu.add(new ExitAction());
        return menu;
    }

    /**
     *  Create menu items for the Look & Feel menu
     */
    private JMenu createLAFMenu() {
        ButtonGroup bg = new ButtonGroup();
        JMenu menu = new JMenu("Look & Feel");
        menu.setMnemonic('L');
        String lafId = UIManager.getLookAndFeel().getID();
        UIManager.LookAndFeelInfo[] lafInfo = UIManager.getInstalledLookAndFeels();
        for (int i = 0; i < lafInfo.length; i++) {
            String laf = lafInfo[i].getClassName();
            String name = lafInfo[i].getName();
            Action action = new ChangeLookAndFeelAction(laf, name);
            JRadioButtonMenuItem mi = new JRadioButtonMenuItem(action);
            menu.add(mi);
            bg.add(mi);
            if (name.equals(lafId)) {
                mi.setSelected(true);
            }
        }
        return menu;
    }

    /*
     *  Implement the ItemListener interface
     */
    @Override
    public void itemStateChanged(ItemEvent e) {
        String componentName = (String) e.getItem();
        changeTableModel(getClassName(componentName));
        selectedItem = componentName;
    }

    /*
     *  Use the component name to build the class name
     */
    private String getClassName(String componentName) {
        if (componentName.equals("TableHeader")) {//  The table header is in a child package
            return PACKAGE + "table.JTableHeader";
        } else {
            return PACKAGE + "J" + componentName;
        }
    }

    /*
     *  Change the TabelModel in the table for the selected component
     */
    private void changeTableModel(String className) {
        DefaultTableModel model = models.get(className); //  Check if we have already built the table model for this component
        if (model != null) {
            table.setModel(model);
            return;
        }
        model = new DefaultTableModel(COLUMN_NAMES, 0); //  Create an empty table to start with
        table.setModel(model);
        models.put(className, model);
        JComponent component = null; //  Create an instance of the component so we can get the default Action map and Input maps
        try {
            if (className.endsWith("JFileChooser")) {//  Hack so I don't have to sign the jar file for usage in  Java Webstart
                component = new JFileChooser(new DummyFileSystemView());
            } else {
                Object o = Class.forName(className).newInstance();
                component = (JComponent) o;
            }
        } catch (Exception e) {
            Object[] row = {e.toString(), "", "", ""};
            model.addRow(row);
            return;
        }
        ActionMap actionMap = component.getActionMap(); //  Not all components have Actions defined
        Object[] keys = actionMap.allKeys();
        if (keys == null) {
            Object[] row = {"No actions found", "", "", ""};
            model.addRow(row);
            return;
        }
        //  In some ActionMaps a key of type Object is found (I have no idea why)
        //  which causes a ClassCastExcption when sorting so we will ignore it
        //  by converting that entry to the empty string
        for (int i = 0; i < keys.length; i++) {
            Object key = keys[i];
            if (key instanceof String) {
                continue;
            } else {
                keys[i] = "";
            }
        }
        Arrays.sort(keys);
        for (int i = 0; i < keys.length; i++) { //  Create a new row in the model for every Action found
            Object key = keys[i];
            if (key != "") {
                Object[] row = {key, "", "", ""};
                model.addRow(row);
            }
        }
        //  Now check each InputMap to see if a KeyStroke is bound the the Action
        updateModelForInputMap(model, 1, component.getInputMap(JComponent.WHEN_FOCUSED));
        updateModelForInputMap(model, 2, component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW));
        updateModelForInputMap(model, 3, component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT));
    }

    /*
     *  The model is potentially update for each of the 3 different InputMaps
     */
    private void updateModelForInputMap(TableModel model, int column, InputMap inputMap) {
        if (inputMap == null) {
            return;
        }
        KeyStroke[] keys = inputMap.allKeys();
        if (keys == null) {
            return;
        }
        //  The InputMap is keyed by KeyStroke, however we want to be able to
        //  access the action names that are bound to a KeyStroke so we will create
        //  a Hashtble that is keyed by action name.
        //  Note that multiple KeyStrokes can be bound to the same action name.
        Hashtable<Object, String> actions = new Hashtable<Object, String>(keys.length);
        for (int i = 0; i < keys.length; i++) {
            KeyStroke key = keys[i];
            Object actionName = inputMap.get(key);
            Object value = actions.get(actionName);
            if (value == null) {
                actions.put(actionName, key.toString().replace("pressed ", ""));
            } else {
                actions.put(actionName, value + ", " + key.toString().replace("pressed ", ""));
            }
        }
        for (int i = 0; i < model.getRowCount(); i++) {
            //  Now we can update the model for those actions that have  KeyStrokes mapped to them
            String o = actions.get(model.getValueAt(i, 0));
            if (o != null) {
                model.setValueAt(o.toString(), i, column);
            }
        }
    }

    /*
     *  Change the LAF and recreate the UIManagerDefaults so that the properties
     *  of the new LAF are correctly displayed.
     */
    private class ChangeLookAndFeelAction extends AbstractAction {

        private static final long serialVersionUID = 1L;
        private String laf;

        protected ChangeLookAndFeelAction(String laf, String name) {
            this.laf = laf;
            putValue(Action.NAME, name);
            putValue(Action.SHORT_DESCRIPTION, getValue(Action.NAME));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            try {
                JMenuItem mi = (JMenuItem) e.getSource();
                JPopupMenu popup = (JPopupMenu) mi.getParent();
                JRootPane rootPane = SwingUtilities.getRootPane(popup.getInvoker());
                Component c = rootPane.getContentPane().getComponent(0);
                rootPane.getContentPane().remove(c);
                UIManager.setLookAndFeel(laf);
                KeyBindings bindings = new KeyBindings();
                rootPane.getContentPane().add(bindings.getContentPane());
                SwingUtilities.updateComponentTreeUI(rootPane);
                rootPane.requestFocusInWindow();
            } catch (Exception ex) {
                System.out.println("Failed loading L&F: " + laf);
                System.out.println(ex);
            }
        }
    }

    private class ExitAction extends AbstractAction {

        private static final long serialVersionUID = 1L;

        public ExitAction() {
            putValue(Action.NAME, "Exit");
            putValue(Action.SHORT_DESCRIPTION, getValue(Action.NAME));
            putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_X));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            System.exit(0);
        }
    }

    private class DummyFileSystemView extends FileSystemView {

        @Override
        public File createNewFolder(File containingDir) {
            return null;
        }

        @Override
        public File getDefaultDirectory() {
            return null;
        }

        @Override
        public File getHomeDirectory() {
            return null;
        }
    }

    private static void createAndShowGUI() {
        KeyBindings application = new KeyBindings();
        JFrame.setDefaultLookAndFeelDecorated(true);
        JFrame frame = new JFrame("Key Bindings");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setJMenuBar(application.getMenuBar());
        frame.getContentPane().add(application.getContentPane());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        //UIManager.put("swing.boldMetal", Boolean.FALSE);
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }
}
于 2012-05-10T12:10:44.953 回答
1

如果您想在您的应用程序上运行“全局事件”,无论您当前的焦点在哪里,您都需要使用 KeyboardFocusManager :

    KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
    kfm.addKeyEventDispatcher(new KeyEventDispatcher() {

        @Override
        public boolean dispatchKeyEvent(KeyEvent e) {
            // do your stuff here
            return done;
        }
    });

希望这可以帮助。

于 2012-05-10T11:25:33.403 回答