【问题标题】:Using a single JTextArea with multiple UndoManagers将单个 JTextArea 与多个 UndoManager 一起使用
【发布时间】:2014-07-15 23:46:03
【问题描述】:

我有一个JTextArea 和一个JComboBox 允许我循环浏览各种打开的文件 - 当我选择不同的文件时JTextArea 的内容会发生变化。我正在尝试为每个文件维护一个不同的撤消缓冲区,并为每个文件定义了一个单独的 UndoManager

我创建了一个更简单的 SSCCE 来演示我的问题,使用两个缓冲区,我称之为“一”和“二” - 用一个简单的按钮在它们之间切换。一旦UndoableEdit 发生,它会检查活动缓冲区并在相应的UndoManager 上执行addEdit()。当按下“撤消”按钮时,它会检查canUndo() 并在相应的UndoManager 上执行undo()。我有一个名为ignoreEdit 的标志,用于在缓冲区之间切换以忽略这些编辑被记录。

如果我从不在缓冲区之间切换,那么我没有问题,撤消按预期工作。只有当我在缓冲区之间切换并似乎“破坏”文档时,它才会失败。以下步骤可用于重现问题:

在缓冲区“One”中,键入:

THIS
IS ONE
EXAMPLE

切换到缓冲区“二”,输入:

THIS
IS ANOTHER
EXAMPLE

切换到缓冲“One”并多次按下“Undo”按钮。经过几次撤消操作后,缓冲区看起来像这样(光标无法选择前两行)。但是,textArea.getText() 的内容根据 System.out.println() 是正确的 - 所以,看起来像是渲染问题?

THIS

THISIS ONE

这不是第一次有人尝试为每个文件实现独立的撤消缓冲区吗?我显然在 Document 模型上做错了事并且天生就破坏了它,但我正在寻找一些关于如何最好地解决这个问题的建议?

SSCCE 的代码如下:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.undo.*;

public class SSCCE extends JFrame implements ActionListener, UndoableEditListener {
  private final JLabel labTextArea;
  private final JTextArea textArea;
  private final JScrollPane scrollTextArea;
  private final Document docTextArea;
  private final JButton bOne, bTwo, bUndo;
  private final UndoManager uOne, uTwo;
  private String sOne, sTwo;
  private boolean ignoreEdit = false;

  public SSCCE(String[] args) {
    setTitle("SSCCE - Short, Self Contained, Correct Example");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setSize(300, 200);
    setLocationRelativeTo(null);

    labTextArea = new JLabel("One");
    getContentPane().add(labTextArea, BorderLayout.PAGE_START);

    uOne = new UndoManager();
    uTwo = new UndoManager();
    sOne = new String();
    sTwo = new String();

    textArea = new JTextArea();
    docTextArea = textArea.getDocument();
    docTextArea.addUndoableEditListener(this);
    scrollTextArea = new JScrollPane(textArea);
    getContentPane().add(scrollTextArea, BorderLayout.CENTER);

    JPanel pButtons = new JPanel();
    bOne = new JButton("One");
    bOne.addActionListener(this);
    bOne.setFocusable(false);
    pButtons.add(bOne, BorderLayout.LINE_START);
    bTwo = new JButton("Two");
    bTwo.addActionListener(this);
    bTwo.setFocusable(false);
    pButtons.add(bTwo, BorderLayout.LINE_END);
    bUndo = new JButton("Undo");
    bUndo.addActionListener(this);
    bUndo.setFocusable(false);
    pButtons.add(bUndo, BorderLayout.LINE_END);
    getContentPane().add(pButtons, BorderLayout.PAGE_END);

    setVisible(true);
  }

  @Override
  public void actionPerformed(ActionEvent e) {
    if (e.getSource().equals(bOne)) {
      if (!labTextArea.getText().equals("One")) {
        sTwo = textArea.getText();
        ignoreEdit = true;
        textArea.setText(sOne);
        ignoreEdit = false;
        labTextArea.setText("One");
      }
    }
    else if (e.getSource().equals(bTwo)) {
      if (!labTextArea.getText().equals("Two")) {
        sOne = textArea.getText();
        ignoreEdit = true;
        textArea.setText(sTwo);
        ignoreEdit = false;
        labTextArea.setText("Two");
      }
    }
    else if (e.getSource().equals(bUndo)) {
      if (labTextArea.getText().equals("One")) {
        try {
          if (uOne.canUndo()) {
            System.out.println("Performing Undo for One");
            uOne.undo();
            System.out.println("Buffer One is now:\n" + textArea.getText() + "\n");
          }
          else {
            System.out.println("Nothing to Undo for One");
          }
        }
        catch (CannotUndoException ex) {
          ex.printStackTrace();
        }
      }
      else if (labTextArea.getText().equals("Two")) {
        try {
          if (uTwo.canUndo()) {
            System.out.println("Performing Undo for Two");
            uTwo.undo();
            System.out.println("Buffer Two is now:\n" + textArea.getText() + "\n");
          }
          else {
            System.out.println("Nothing to Undo for Two");
          }
        }
        catch (CannotUndoException ex) {
          ex.printStackTrace();
        }
      }
    }
  }

  @Override
  public void undoableEditHappened(UndoableEditEvent e) {
    if (!ignoreEdit) {
      if (labTextArea.getText().equals("One")) {
        System.out.println("Adding Edit for One");
        uOne.addEdit(e.getEdit());
      }
      else if (labTextArea.getText().equals("Two")) {
        System.out.println("Adding Edit for Two");
        uTwo.addEdit(e.getEdit());
      }
    }
  }

  public static void main(final String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        new SSCCE(args);
      }
    });
  }
}

【问题讨论】:

  • +1 for SSCCE :-),搜索覆盖 UndoManager 的 AbstractUndoableEdit
  • 不确定我是否做错了什么,但对我来说似乎没问题。您是否考虑过使用单独的 UndoableEditListeners 包装 UndoManager 并简单地切换这些实例??
  • can to start with@aterai
  • 为什么每个文件不使用一个 JTextArea?只需在 CardLayout 中布局这些区域 - 用户每次只能看到一个 JTextArea。
  • 我已经考虑过这一点,但我可以有很多潜在的缓冲区 - 它们是代码 sn-p 库的一部分,我可能有 10、20 或 30 个。

标签: java swing jtextarea undo redo


【解决方案1】:

之前我曾尝试创建Document 类的新实例(每个都引用同一个撤消侦听器),并且打算使用JTextArea.setDocument() 而不是JTextArea.setText()。但是,Document 是一个接口,无法实例化,但在阅读了 mKorbel 发布的参考资料后,我尝试使用 PlainDocument 类代替,它有效。

我决定维护一个HashMap<String, Document> 来包含我的Document 类并在它们之间切换。当我切换 Document 时,我看不到撤消/重做问题 - 我想我不再破坏 Document

更新了下面的 SSCCE,现在使用 JTextArea.setDocument() 而不是 JTextArea.setText()。这还具有不需要ignoreEdit 布尔值的优点,因为setDocument() 不会触发UndoableEditEvent,而setText() 会触发。然后每个Document 引用本地类UndoableEditListener

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.undo.*;

public class SSCCE extends JFrame implements ActionListener, UndoableEditListener {
  private final JLabel labTextArea;
  private final JTextArea textArea;
  private final JScrollPane scrollTextArea;
  private final Document docTextArea;
  private final JButton bOne, bTwo, bUndo;
  private final UndoManager uOne, uTwo;
  private Document dOne, dTwo;

  public SSCCE(String[] args) {
    setTitle("SSCCE - Short, Self Contained, Correct Example");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setSize(300, 200);
    setLocationRelativeTo(null);

    labTextArea = new JLabel("One");
    getContentPane().add(labTextArea, BorderLayout.PAGE_START);

    uOne = new UndoManager();
    uTwo = new UndoManager();
    dOne = new PlainDocument();
    dTwo = new PlainDocument();
    dOne.addUndoableEditListener(this);
    dTwo.addUndoableEditListener(this);

    textArea = new JTextArea();
    docTextArea = textArea.getDocument();
    docTextArea.addUndoableEditListener(this);
    textArea.setDocument(dOne);
    scrollTextArea = new JScrollPane(textArea);
    getContentPane().add(scrollTextArea, BorderLayout.CENTER);

    JPanel pButtons = new JPanel();
    bOne = new JButton("One");
    bOne.addActionListener(this);
    bOne.setFocusable(false);
    pButtons.add(bOne, BorderLayout.LINE_START);
    bTwo = new JButton("Two");
    bTwo.addActionListener(this);
    bTwo.setFocusable(false);
    pButtons.add(bTwo, BorderLayout.LINE_END);
    bUndo = new JButton("Undo");
    bUndo.addActionListener(this);
    bUndo.setFocusable(false);
    pButtons.add(bUndo, BorderLayout.LINE_END);
    getContentPane().add(pButtons, BorderLayout.PAGE_END);

    setVisible(true);
  }

  @Override
  public void actionPerformed(ActionEvent e) {
    if (e.getSource().equals(bOne)) {
      if (!labTextArea.getText().equals("One")) {
        textArea.setDocument(dOne);
        labTextArea.setText("One");
      }
    }
    else if (e.getSource().equals(bTwo)) {
      if (!labTextArea.getText().equals("Two")) {
        textArea.setDocument(dTwo);
        labTextArea.setText("Two");
      }
    }
    else if (e.getSource().equals(bUndo)) {
      if (labTextArea.getText().equals("One")) {
        try {
          if (uOne.canUndo()) {
            System.out.println("Performing Undo for One");
            uOne.undo();
            System.out.println("Buffer One is now:\n" + textArea.getText() + "\n");
          }
          else {
            System.out.println("Nothing to Undo for One");
          }
        }
        catch (CannotUndoException ex) {
          ex.printStackTrace();
        }
      }
      else if (labTextArea.getText().equals("Two")) {
        try {
          if (uTwo.canUndo()) {
            System.out.println("Performing Undo for Two");
            uTwo.undo();
            System.out.println("Buffer Two is now:\n" + textArea.getText() + "\n");
          }
          else {
            System.out.println("Nothing to Undo for Two");
          }
        }
        catch (CannotUndoException ex) {
          ex.printStackTrace();
        }
      }
    }
  }

  @Override
  public void undoableEditHappened(UndoableEditEvent e) {
    if (labTextArea.getText().equals("One")) {
      System.out.println("Adding Edit for One");
      uOne.addEdit(e.getEdit());
    }
    else if (labTextArea.getText().equals("Two")) {
      System.out.println("Adding Edit for Two");
      uTwo.addEdit(e.getEdit());
    }
  }

  public static void main(final String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        new SSCCE(args);
      }
    });
  }
}

【讨论】:

  • 我不知道为什么,但是今天早上我在办公室 PC 上运行您的代码时遇到了与您相同的问题。那时我没有足够的时间查看代码。所以当我回到家后,我在家里的电脑上运行了代码,它工作正常!!我无法重现该问题。所以这可能是一个渲染问题。
  • 感谢您的尝试。我可以在运行 Java 1.8 的工作 PC 上始终如一地生成它,但我将尝试在运行 Java 1.6 的 Mac 上尝试是否得到相同的结果。我也注意到偶尔,它也会产生javax.swing.text.StateInvariantError: Can't render line: 0异常。
  • 我在 Mac 上的 Java 1.6 下也看到了同样的情况。
【解决方案2】:

本身并不是一个答案,而是展示了解决同一问题的不同方法。

这样做是将UndoableEditListener 包装在一个自我管理的代理中,该代理有自己的UndoManagerDocument

使用 Java 7 和 Java 8 对此进行了测试:

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
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.JTextComponent;
import javax.swing.text.PlainDocument;
import javax.swing.undo.UndoManager;

public class UndoExample {

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

    private int index = 0;
    private Map<String, Undoer> mapUndoers;
    private JTextArea ta;
    private Undoer current;

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

                mapUndoers = new HashMap<>(2);
                mapUndoers.put("One", new Undoer());
                mapUndoers.put("Two", new Undoer());

                ta = new JTextArea(4, 20);
                ta.setWrapStyleWord(true);
                ta.setLineWrap(true);

                JButton btnOne = new JButton("One");
                JButton btnTwo = new JButton("Two");
                ActionListener al = new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        install(e.getActionCommand());
                    }
                };
                btnOne.addActionListener(al);
                btnTwo.addActionListener(al);

                JButton undo = new JButton("Undo");
                undo.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        if (current != null) {
                            current.undo();
                        }
                    }
                });

                JPanel panel = new JPanel(new GridBagLayout());
                panel.add(btnOne);
                panel.add(btnTwo);
                panel.add(undo);

                install("One");

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(ta));
                frame.add(panel, BorderLayout.SOUTH);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    protected void install(String name) {
        Undoer undoer = mapUndoers.get(name);
        if (undoer != null) {
            current = undoer;
            undoer.install(ta);
        }
    }

    public class Undoer implements UndoableEditListener {

        private UndoManager undoManager;
        private Document doc;

        public Undoer() {
            undoManager = new UndoManager();
            doc = createDocument();
            doc.addUndoableEditListener(this);
        }

        public void undo() {
            undoManager.undo();
        }

        public void undoOrRedo() {
            undoManager.undoOrRedo();
        }

        protected Document createDocument() {
            return new PlainDocument();
        }

        public void install(JTextComponent comp) {
            comp.setDocument(doc);
        }

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

    }

}

【讨论】:

    猜你喜欢
    • 2014-08-17
    • 2013-06-30
    • 1970-01-01
    • 2011-09-24
    • 1970-01-01
    • 1970-01-01
    • 2011-05-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多