• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:JTableのセルエディタにJPopumMenuを設定
#navi(../)
RIGHT:Posted by [[terai]] at 2010-04-26
*JTableのセルエディタにJPopumMenuを設定 [#be1eecdb]
JTableのセルエディタに、Copy、Paste、Undo、Redoなどを行うJPopumMenuを設定します。
---
category: swing
folder: CellEditorPopupMenu
title: JTableのセルエディタにJPopupMenuを設定
tags: [JTable, TableCellEditor, UndoManager, JPopupMenu, AncestorListener]
author: aterai
pubdate: 2010-04-26T16:54:26+09:00
description: JTableのセルエディタに、Copy、Paste、Undo、Redoなどを行うJPopupMenuを設定します。
image: https://lh4.googleusercontent.com/_9Z4BYR88imo/TQTIn7Rc6TI/AAAAAAAAATE/drRaDYiUB1w/s800/CellEditorPopupMenu.png
hreflang:
    href: https://java-swing-tips.blogspot.com/2010/11/jtable-celleditor-popupmenu.html
    lang: en
---
* 概要 [#summary]
`JTable`のセルエディタに、`Copy`、`Paste`、`Undo`、`Redo`などを行う`JPopupMenu`を設定します。

-&jnlp;
-&jar;
-&zip;
#download(https://lh4.googleusercontent.com/_9Z4BYR88imo/TQTIn7Rc6TI/AAAAAAAAATE/drRaDYiUB1w/s800/CellEditorPopupMenu.png)

//#screenshot
http://lh4.ggpht.com/_9Z4BYR88imo/TQTIn7Rc6TI/AAAAAAAAATE/drRaDYiUB1w/s800/CellEditorPopupMenu.png
* サンプルコード [#sourcecode]
#code(link){{
class TextComponentPopupMenu extends JPopupMenu {
  protected TextComponentPopupMenu(JTextComponent tc) {
    super();
    Action cutAction = new DefaultEditorKit.CutAction();
    add(cutAction);
    Action copyAction = new DefaultEditorKit.CopyAction();
    add(copyAction);
    Action pasteAction = new DefaultEditorKit.PasteAction();
    add(pasteAction);
    Action deleteAction = new DeleteAction();
    add(deleteAction);
    addSeparator();

**サンプルコード [#nb35d2a6]
#code{{
public static JPopupMenu installTextComponentPopupMenu(final JTextComponent tc) {
  final UndoManager manager = new UndoManager();
  final Action undoAction   = new UndoAction(manager);
  final Action redoAction   = new RedoAction(manager);
  final Action cutAction    = new DefaultEditorKit.CutAction();
  final Action copyAction   = new DefaultEditorKit.CopyAction();
  final Action pasteAction  = new DefaultEditorKit.PasteAction();
  final Action deleteAction = new AbstractAction("delete") {
    public void actionPerformed(ActionEvent e) {
      JPopupMenu pop = (JPopupMenu)e.getSource();
      ((JTextComponent)pop.getInvoker()).replaceSelection(null);
    }
  };
  tc.addAncestorListener(new AncestorListener() {
    public void ancestorAdded(AncestorEvent e) {
      manager.discardAllEdits();
      tc.requestFocusInWindow();
    }
    public void ancestorMoved(AncestorEvent e) {}
    public void ancestorRemoved(AncestorEvent e) {}
  });
  tc.getDocument().addUndoableEditListener(manager);
  tc.getActionMap().put("undo", undoAction);
  tc.getActionMap().put("redo", redoAction);
  InputMap imap = tc.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.CTRL_MASK), "undo");
  imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, Event.CTRL_MASK), "redo");
    UndoManager manager = new UndoManager();
    Action undoAction = new UndoAction(manager);
    add(undoAction);

  JPopupMenu popup = new JPopupMenu();
  popup.add(cutAction);
  popup.add(copyAction);
  popup.add(pasteAction);
  popup.add(deleteAction);
  popup.addSeparator();
  popup.add(undoAction);
  popup.add(redoAction);
    Action redoAction = new RedoAction(manager);
    add(redoAction);

  popup.addPopupMenuListener(new PopupMenuListener() {
    public void popupMenuCanceled(PopupMenuEvent e) {}
    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
      undoAction.setEnabled(true);
      redoAction.setEnabled(true);
    }
    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
      JPopupMenu pop = (JPopupMenu)e.getSource();
      JTextField field = (JTextField)pop.getInvoker();
      boolean flg = field.getSelectedText()!=null;
      cutAction.setEnabled(flg);
      copyAction.setEnabled(flg);
      deleteAction.setEnabled(flg);
      undoAction.setEnabled(manager.canUndo());
      redoAction.setEnabled(manager.canRedo());
    }
  });
  tc.setComponentPopupMenu(popup);
  return popup;
    tc.addAncestorListener(new AncestorListener() {
      @Override public void ancestorAdded(AncestorEvent e) {
        manager.discardAllEdits();
        e.getComponent().requestFocusInWindow();
      }

      @Override public void ancestorMoved(AncestorEvent e) {
        /* not needed */
      }

      @Override public void ancestorRemoved(AncestorEvent e) {
        /* not needed */
      }
    });
    tc.getDocument().addUndoableEditListener(manager);
    tc.getActionMap().put("undo", undoAction);
    tc.getActionMap().put("redo", redoAction);
    InputMap im = tc.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
    // Java 10: int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, mask), "undo");
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, mask), "redo");

    addPopupMenuListener(new PopupMenuListener() {
      @Override public void popupMenuCanceled(PopupMenuEvent e) {
        /* not needed */
      }

      @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
        undoAction.setEnabled(true);
        redoAction.setEnabled(true);
      }

      @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
        JTextComponent tc = (JTextComponent) getInvoker();
        boolean hasSelectedText = Objects.nonNull(tc.getSelectedText());
        cutAction.setEnabled(hasSelectedText);
        copyAction.setEnabled(hasSelectedText);
        deleteAction.setEnabled(hasSelectedText);
        undoAction.setEnabled(manager.canUndo());
        redoAction.setEnabled(manager.canRedo());
      }
    });
  }
}
}}

**解説 [#occ20915]
上記のサンプルでは、JTableのセルエディタに、Cut,Copy,Paste,Delete,Undo,Redoを行うJPopumMenuを設定しています。セルエディタとして使用しているJTextFieldは、おなじものを使い回しているので、別のセルでの編集が持ち越されないよう、AncestorListenerを使って表示されるたびに、UndoManagerを空(UndoManager#discardAllEdits()を呼び出す)にしています。
* 解説 [#explanation]
上記のサンプルでは、`JTable`のセルエディタ内の文字列に対して、`Cut`、`Copy`、`Paste`、`Delete`、`Undo`、`Redo`を行うための`JPopupMenu`を設定しています。

//**参考リンク
**コメント [#we846b54]
----
- セルエディタとして使用する`JTextField`は、同一インスタンス使い回しているため、別セルでの編集が持ち越されないよう`AncestorListener`を使って表示されるたびに`UndoManager#discardAllEdits()`を呼び出してリセット
-- もしくは`DefaultCellEditor#isCellEditable(...)`などをオーバーライドしてリセット

#code{{
DefaultCellEditor ce = new DefaultCellEditor(new JTextField()) {
  @Override public boolean isCellEditable(EventObject e) {
    boolean b = super.isCellEditable(e);
    if (b) {
      manager.discardAllEdits();
    }
    return b;
  }
};
table.setDefaultEditor(Object.class, ce);
}}

* 参考リンク [#reference]
- [https://www.ne.jp/asahi/hishidama/home/tech/java/swing/JTable.html Java Swing「JTable」メモ(Hishidama's Swing-JTable Memo)]
-- セルエディタ内だけではなく、行の追加、削除などを`Undo`、`Redo`するサンプルが参考になる

* コメント [#comment]
#comment
#comment