您的位置:首页 > 其它

利用swing的undo包实现Undo/Redo功能

2007-04-10 16:10 549 查看
利用swingundo包实现Undo/Redo功能

一、概述
javax.swing.undo包提供了一系列接口和类,利用它可以方便的在程序中加入Undo/Redo功能。

先说说undo包引入的一个概念——Edit。它是与Command模式中的Command类似的一个概念。Command模式将操作的执行逻辑封装到一个个Command对象中,解耦了操作发起者和操作执行逻辑之间的耦合关系:操作发起者要进行一个操作,不用关心具体的执行逻辑,只需创建一个相应的Command实例,调用它的执行接口即可。而在swing中,与界面交互的各种操作,比如插入,删除等被称之为Edit,实际上就是Command。

下面是undo包的几个主要接口和类:
UndoableEdit 顾名思义,它表示一个可以被Undo/Redo的操作;
AbstractUndoableEdit 实现UndoableEdit的抽象类;
UndoManager Undo/Redo管理器,各种UndoableEdit实例通过addEdit方法加入UndoManager,通过调用UndoManager的undo/redo方法来实现Undo/Redo功能。

使用undo包很简单,主要操作步骤如下:
1、创建UndoManager实例;
2、创建各种实现UndoableEdit的具体操作类;
3、调用某种操作时,创建一个具体操作类的实例,加入UndoManager;
4、在Undo/Redo时,直接调用UndoManager的undo/redo方法。

二、应用实例
下面就以一个比较常见的例子结合上述的操作步骤来进行说明。
现有一个产品列表界面,提供插入,删除,移动产品位置等操作,这些操作均要可以撤消和重做。如下是该界面的截图:



以上产品列表界面中,具有添加,删除,上移,下移四个操作按钮和Undo/Redo两个按钮,产品列表用一个JList实现。

第一步、创建UndoManager实例。
SamplePanel是我们的产品列表界面实现类,因此我们在SamplePanel类的初始化中加入:
...
private UndoManager undoManager=new UndoManager();
...

第二步、定义添加,删除,上移,下移的具体操作类。
AddEdit类负责添加操作;
DeleteEdit类负责删除操作;
UpDownEdit类负责上移和下移操作。

在swing的MVC体系中,JList是一个View类,操作内部数据的能力来自于它的数据模型类ListModel。因此我们的每个具体操作类,如添加,删除等均通过直接操纵ListModel来达到目的。这是swing界面开发中的惯用做法。

为此,我们先定义一个抽象的ListEdit类,含有一个ListModel成员,供其他具体操作类继承。
public abstract class ListEdit extends AbstractUndoableEdit{
//列表的数据模型
protected DefaultListModel model=null;
//操作的具体执行逻辑,留待子类实现
public abstract void execute();

//在这里,redo操作只是简单的执行一次execute。子类如无特殊需求,就不用覆盖它。
public void redo() throws CannotRedoException {
execute();
}
}

下面来分别实现AddEdit,DeleteEdit,UpDownEdit类,它们均继承自ListEdit类。
我们在execute方法中实现操作逻辑,在undo方法中实现Undo的逻辑。redo方法在ListEdit中已经实现,不用管它了。
这里提一下如下两个方法:
getUndoPresentationName()和getRedoPresentationName()方法可以为Undo/Redo操作提供描述。比如,如果要在菜单中提供“撤消删除”,“重做删除”菜单项而不是简单的无所指的“撤消”,“重做”菜单项,可以通过这两个方法来获得。

一个需要注意的问题是,在实现执行逻辑时要保留现场数据,以供Undo时按图索骥恢复现场。
比如,要执行Delete操作,我们要记住删除的元素和所在位置这两个现场数据,undo方法据此来在原位置插入被删除的元素。如果没有这两个现场数据,undo就无从下手了。
下面是DeleteEdit类的实现:
public class DeleteEdit extends ListEdit{
//被删除的元素
private Object element;
//删除发生的位置
private int index;

public DeleteEdit(DefaultListModel model,int index) {
this.model=model;
this.index=index;
}

public void execute() {
element=model.getElementAt(index);
if(element!=null){
model.removeElementAt(index);
}
}

public void undo() throws CannotUndoException {
if(element!=null){
model.insertElementAt(element,index);
}
}

public String getUndoPresentationName() {
return "撤消删除元素";
}

public String getRedoPresentationName() {
return "重做删除元素";
}
}
其他操作类的实现原理基本类似,这里不再赘述。

第三步、在界面中调用添加,删除,上移,下移操作
以添加操作为例,在“添加”按钮的事件处理器中:
1、 准备好AddEdit所需的参数(这里除了ListModel外,还需要一个元素名称,通过弹出输入框来获取);
2、 创建AddEdit实例,调用其execute方法;
3、 将AddEdit实例加入UndoManager。

addButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
String s=JOptionPane.showInputDialog(null,"请输入条目:","添加",JOptionPane.PLAIN_MESSAGE);
if(s!=null&&!s.equals("")){
AddEdit edit=new AddEdit(model,s);
edit.execute();
undoManager.addEdit(edit);
}
}
});
其他调用操作见SamplePanel类中的相应代码,不再一一列出。

第四步、调用Undo/Redo
经过上面的步骤,现在要实现Undo/Redo就非常简单了:
在“撤消”按钮的事件处理器中,直接调用UndoManager的undo方法;
在“重做”按钮的事件处理器中,直接调用UndoManager的redo方法。

三、代码
以上实例包含6个java文件,分别是:
SampleFrame.java 示例的启动类
SamplePanel.java 产品列表界面类
ListEdit.java 列表操作抽象类
AddEdit.java 添加操作类
DeleteEdit.java 删除操作类
UpDownEdit.java 上移/下移操作类

不能贴附件,只好列出所有代码了:
sample/SampleFrame.java
/*
* SampleFrame 主窗口
* Created on 2007/03/16, EricXYY
*/
package sample;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.WindowConstants;

public class SampleFrame extends JFrame{
private SamplePanel sp=new SamplePanel();

public SampleFrame() {
setTitle("示例");
setSize(640,480);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setLocationRelativeTo(null); //使窗口居中
getContentPane().add(sp,BorderLayout.CENTER);
}

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new SampleFrame().setVisible(true);
}
});
}
}

sample/SamplePanel.java
/*
* SamplePanel 列表操作面板
* Created on 2007/03/16, EricXYY
*/
package sample;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.ListSelectionModel;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import sample.edit.AddEdit;
import sample.edit.DeleteEdit;
import sample.edit.UpDownEdit;

public class SamplePanel extends JPanel{
private JToolBar toolBar=new JToolBar();
private JButton addButton=new JButton("添加");
private JButton deleteButton=new JButton("删除");
private JButton upButton=new JButton("上移");
private JButton downButton=new JButton("下移");
private JButton undoButton=new JButton("撤消");
private JButton redoButton=new JButton("重做");

private JList list=new JList();
private JScrollPane scrollPane=new JScrollPane(list);
//列表的数据模型,各种操作均是针对model来进行
private DefaultListModel model=new DefaultListModel();

private UndoManager undoManager=new UndoManager();

public SamplePanel() {
setLayout(new BorderLayout());

toolBar.add(addButton);
toolBar.add(deleteButton);
toolBar.add(upButton);
toolBar.add(downButton);
toolBar.add(undoButton);
toolBar.add(redoButton);
toolBar.setFloatable(false);
add(toolBar,BorderLayout.NORTH);

list.setModel(model);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
add(scrollPane,BorderLayout.CENTER);

initSampleData();
initActions();
}

private void initActions(){
//对于每个操作,准备好Eidt所需的参数,创建Edit实例,执行之,然后加入UndoManager.
addButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
String s=JOptionPane.showInputDialog(null,"请输入条目:","添加",JOptionPane.PLAIN_MESSAGE);
if(s!=null&&!s.equals("")){
AddEdit edit=new AddEdit(model,s);
edit.execute();
undoManager.addEdit(edit);
}
}
});

deleteButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
int index=list.getSelectedIndex();
if(index>-1){
DeleteEdit edit=new DeleteEdit(model,index);
edit.execute();
undoManager.addEdit(edit);
}
}
});

upButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
int index=list.getSelectedIndex();
if(index>0){
UpDownEdit edit=new UpDownEdit(model,index,true);
edit.execute();
undoManager.addEdit(edit);
}
}
});

downButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
int index=list.getSelectedIndex();
if((index>-1)&&(index<model.getSize()-1)){
UpDownEdit edit=new UpDownEdit(model,index,false);
edit.execute();
undoManager.addEdit(edit);
}
}
});

//对于Undo/Redo操作,直接调用UndoManager的undo/redo方法
undoButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
try{
undoManager.undo();
}
catch(CannotUndoException ex){}
}
});

redoButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
try{
undoManager.redo();
}
catch(CannotRedoException ex){}
}
});

}

private void initSampleData(){
model.addElement("笔记本");
model.addElement("台式机");
model.addElement("数码相机");
model.addElement("移动硬盘");
}
}

sample/edit/ListEdit.java
/*
* ListEdit 在列表上进行的操作的抽象类
* Created on 2007/03/16, EricXYY
*/
package sample.edit;

import javax.swing.DefaultListModel;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;

public abstract class ListEdit extends AbstractUndoableEdit{
//列表的数据模型
protected DefaultListModel model=null;
//操作的具体执行逻辑,留待子类实现
public abstract void execute();

//在这里,redo操作只是简单的执行一次execute,子类如无特殊需求,就不用覆盖它了。
public void redo() throws CannotRedoException {
execute();
}
}

sample/edit/AddEdit.java
/*
* AddEdit 添加操作
* Created on 2007/03/16, EricXYY
*/
package sample.edit;

import javax.swing.DefaultListModel;
import javax.swing.undo.CannotUndoException;

public class AddEdit extends ListEdit{
//添加的元素
private Object element;
//添加的元素所在位置
private int index;

public AddEdit(DefaultListModel model,Object element) {
this.model=model;
this.element=element;
}

public void execute(){
model.addElement(element);
index=model.getSize()-1;
}

public void undo() throws CannotUndoException {
model.remove(index);
}

public String getUndoPresentationName() {
return "撤消添加元素";
}

public String getRedoPresentationName() {
return "重做添加元素";
}
}

sample/edit/DeleteEdit.java
/*
* DeleteEdit 删除操作
* Created on 2007/03/16, EricXYY
*/
package sample.edit;

import javax.swing.DefaultListModel;
import javax.swing.undo.CannotUndoException;

public class DeleteEdit extends ListEdit{
//被删除的元素
private Object element;
//删除发生的位置
private int index;

public DeleteEdit(DefaultListModel model,int index) {
this.model=model;
this.index=index;
}

public void execute() {
element=model.getElementAt(index);
if(element!=null){
model.removeElementAt(index);
}
}

public void undo() throws CannotUndoException {
if(element!=null){
model.insertElementAt(element,index);
}
}

public String getUndoPresentationName() {
return "撤消删除元素";
}

public String getRedoPresentationName() {
return "重做删除元素";
}
}

sample/edit/upDownEdit.java
/*
* UpDownEdit 上移下移操作
* Created on 2007/03/16, EricXYY
*/
package sample.edit;

import javax.swing.DefaultListModel;
import javax.swing.undo.CannotUndoException;

public class UpDownEdit extends ListEdit{

//是否上移
private boolean up;
//待移动的原始位置
private int index;
//待移动的原始位置上的元素
private Object element1;
//目标位置上的元素
private Object element2;

public UpDownEdit(DefaultListModel model,int index,boolean up) {
this.model=model;
this.index=index;
this.up=up;
}

public void execute() {
element1=model.getElementAt(index);
if(up){
element2=model.getElementAt(index-1);
model.setElementAt(element1,index-1);
model.setElementAt(element2,index);
}
else{
element2=model.getElementAt(index+1);
model.setElementAt(element1,index+1);
model.setElementAt(element2,index);
}
}

public void undo() throws CannotUndoException {
if(up){
model.setElementAt(element1,index);
model.setElementAt(element2,index-1);
}
else{
model.setElementAt(element1,index);
model.setElementAt(element2,index+1);
}
}

public String getUndoPresentationName() {
return up?"撤消上移元素":"撤消下移元素";
}

public String getRedoPresentationName() {
return up?"重做上移元素":"重做下移元素";
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: