using undo/redo in Swing for an MVC model?

2019-08-12 03:25发布

问题:

I have an MVC model,and Im trying to add an undo/redo feature to it. User can place a number in a JTextField, and when they click the UNDO button, it should be erased. I have provided the code for my classes that implement this. The error I get is at the bottom.

The Control package:

ButtonnGUIControl.java

package control;

/** * This class will control the button actions of the GUI.It implements the actionListener() method * and based on the button that was clicked, it initializes the * corresponding component in the model class. * */

public class ButtonGUIControl implements ActionListener{
private SudokuModel model;
private ButtonGUI bgui;
final UndoManager manager = new UndoManager();



public ButtonGUIControl(SudokuModel model) {
    this.model = model;
}

/*This method will listen for user events, and act accordingly
 * */

public void actionPerformed(ActionEvent e) {
    try {   
         if(e.getActionCommand().equals("Check My Answer!")){

            model.checkGame();
        }


         else if (e.getActionCommand().equals("Undo")){

             try {

                    manager.undo();
                } catch (CannotUndoException ex) { ex.printStackTrace(); }
                finally {
                    //updateButtons();
                }                   
         }
         else if (e.getActionCommand().equals("Redo")){

             manager.redo();
             System.out.println("e.getActionCommand().equals redo");
         }

        //otherwise, it simply places the selected number in the grid
        else{

            model.setNumber(Integer.parseInt(e.getActionCommand()));
        }

    } catch (NumberFormatException e1) {}

    catch (IOException e1) {}
    catch (CannotUndoException ex) {
        System.out.println("Unable to undo: " + ex);
        ex.printStackTrace();
        }
    catch (CannotRedoException ex) {
        System.out.println("Unable to redo: " + ex);
        ex.printStackTrace();
    }    

}

public void undoableEditHappened(UndoableEditEvent evt) {

    manager.addEdit(evt.getEdit());

    //bgui.updateButtons();

}

}


In the view package: ButtonGUI.java

I add an actionListner to the buttons:

RedoBttn.addActionListener(buttonController);

UndoBttn.addActionListener(buttonController);


In the Grid.java, which extends the JTextField:

public class Grid extends JTextField {
private int x;       
private int y; 

final UndoManager manager = new UndoManager();


public Grid(int x, int y) {
    super("");
    this.x = x;
    this.y = y;
    getDocument().addUndoableEditListener(manager);



    setPreferredSize(new Dimension(40, 40));
    setBorder(BorderFactory.createLineBorder(Color.GREEN));
    setOpaque(true);
}


public void setNumber(int number, boolean userInput)  {


    if(number > 0 & !userInput){
        setText(""+number);
        setEditable(false);


    }

    //These set of numbers are the only ones that the user can undo/redo
    else if (number > 0 & userInput){
        setText(""+number);
        setEditable(true);
}
    else
    setText("");

I also added a mouse listenr to each of the JTextFields in another class in the view package. So I think I have the undo/redo structure right. When I run the program, I can select a number and place it in a JTextField(grid), but as soon as I hit "undo" I get the following exception:

    javax.swing.undo.CannotUndoException
at javax.swing.undo.UndoManager.undo(UndoManager.java:411)
at control.ButtonGUIControl.actionPerformed(ButtonGUIControl.java:76)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2018)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2341)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:402)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:252)
at java.awt.Component.processMouseEvent(Component.java:6505)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3321)
at java.awt.Component.processEvent(Component.java:6270)
at java.awt.Container.processEvent(Container.java:2229)
at java.awt.Component.dispatchEventImpl(Component.java:4861)
at java.awt.Container.dispatchEventImpl(Container.java:2287)
at java.awt.Component.dispatchEvent(Component.java:4687)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4832)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4492)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4422)
at java.awt.Container.dispatchEventImpl(Container.java:2273)
at java.awt.Window.dispatchEventImpl(Window.java:2713)
at java.awt.Component.dispatchEvent(Component.java:4687)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:707)
at java.awt.EventQueue.access$000(EventQueue.java:101)
at java.awt.EventQueue$3.run(EventQueue.java:666)
at java.awt.EventQueue$3.run(EventQueue.java:664)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:87)
at java.awt.EventQueue$4.run(EventQueue.java:680)
at java.awt.EventQueue$4.run(EventQueue.java:678)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:677)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:211)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:128)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:117)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:113)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

and line 76 is where I call:

    manager.undo() 

in a try/catch block.

Any idea whats wrong? Is it my undo/redo implementation or something else?

Thanks!!!

回答1:

Try wrapping manager.undo(); like this:

if(manager.canUndo())
    manager.undo();

I suspect the default UndoListener implementation of JTextField doesn't add system updates (ie. calling setText(""+number); to the undo queue) - only updates from the UI.

EDIT

After doing some testing, it seems that the setText("") function does add undos to the UndoManager, so that's not the problem you're running into. So you can prevent the error by making sure there's any undo-able actions to undo; But if you're not getting any undo-able actions in your UndoManager, the problem is probably happening somewhere else in the code.



回答2:

As an alternative, consider JFormattedTextField. By default, the reset-field-edit action is bound to the ESC key, but you can bind the action to another key as required, say KeyEvent.VK_Z, as shown in How to Use Actions. For cross-platform compatibility, use getMenuShortcutKeyMask(), mentioned here.