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!!!
Try wrapping
manager.undo();
like this:I suspect the default UndoListener implementation of
JTextField
doesn't add system updates (ie. callingsetText(""+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 theUndoManager
, 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 yourUndoManager
, the problem is probably happening somewhere else in the code.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, sayKeyEvent.VK_Z
, as shown in How to Use Actions. For cross-platform compatibility, usegetMenuShortcutKeyMask()
, mentioned here.