Java Swing サンプル集

Top Page
* JTableでのCut/Copy & Paste
-
・説明
-

デフォルトのままだとJTable上ではコピー&ペーストを行うことが出来ません。
ここでは、ショートカットキーと右クリックで実現する方法を示します。

大ざっぱに言うと、以下3種類の設定を行うことにより実現出来ます。
  1. キーボードショートカット設定
  2. 右クリックメニュー設定
  3. Actionクラス内での処理を記述
この中で肝は 3.Actionクラス内での処理となります。
app
サンプルソース : src_jtable_copy_paste.zip
サンプルアプリ : sample_jtable_copy_paste.jar

以下の手順により、キーボードショートカットを設定します。
※キーボードショートカットを使用しない場合は省略可。

  1. AbstractActionを継承したクラスをショートカット分生成する。
  2. テーブルのInputMapに対してショートカットキーとそれを表す文字列(=オブジェクトであれば何でも良い)のバインディングを追加。
  3. テーブルのActionMapに対して1.2で追加した文字列(オブジェクト)と対応する上記1.で生成したクラスのバインディングを追加。
    KeyStroke CUT
        = KeyStroke.getKeyStroke(KeyEvent.VK_X,ActionEvent.CTRL_MASK,false);
    table.getInputMap().put(CUT, "Cut");
    table.getActionMap().put("Cut", Actionクラス);
    上記例ではCtrl+Xで3行目で指定しているActionクラスが呼び出される。

以下の手順により、右クリックメニューを設定します。
※右クリックメニューを使用しない場合は省略可。

  1. 右クリック時に表示させるJMenuItemのインスタンスをメニュー数分生成する。
    このJMenuItemのパラメータが右クリック時に表示される文字列になる。
  2. 上記の各メニューが選択された際の処理をActionListenerを実装したクラスに定義し、インスタンスをそれぞれ生成、JMenuItemに追加しておく。
    メニュー選択時は、このクラス内のactionPerformed()イベントがコールされる。
  3. JMenuItemが貼り付くJPopupMenuのインスタンスを生成し、上から表示する順番でJMenuItemインスタンスをaddする。
  4. 右クリックを表示させたいコンポーネントにマウスリスナーを追加し、マウス押下イベント発生時に上記JPopupMenuインスタンスを表示するように実装しておく。
    JMenuItem jmenuitem1 = new JMenuItem("Cut    Ctrl+X");
    jmenuitem1.addActionListener(Actionリスナー);
    JPopupMenu jpopup.add(jmenuitem1);
    jTable.addMouseListener(new XXXMouseListener(jTable, jpopup));
    jFrame.add(jpopup);
    

サンプルコードでは上記ショートカットキー用Actionクラスと右クリック用Actionリスナーは同一のクラスとしています。
実行する内容は同じなので、不都合が無ければ同一クラスとした方が良いと思われます。

上記の実装方法では、ショートカットキー押下時、右クリック時ともにActionクラス内の

void actionPerformed(ActionEvent e)

がコールされます。

右クリック時、どのJMenuItemが選択されたのかは、e.getSource()で判別可能。
ショートカットキーについては判別が出来ないため、Actionクラス生成時に内部にショートカットキーを示す文字列(数値でも良い)を保持しておき、コールバック時にどの文字列を抱えているかで判別すると良いでしょう。

以下の手順により、キーボードショートカットを設定します。
※キーボードショートカットを使用しない場合は省略可。

  1. Cut/Copy

    CutとCopyはほとんど差異はありません。
    違いは、元データを削除する(Cut)、残す(Copy)のみ。
    そのため実体メソッドはCut/Copy共通で実現可能です。

    基本的な構造としては、以下の通りです。
    1. 選択されている範囲を左から右の方向に1セルずつ取得し、StringBufferにため込む。
      この間、セルとセルの間に'\t'(タブ文字)を挟ませる。
    2. 行が変れば、'\n'(改行文字)をStringBufferに入れる。
    3. 上記要領で選択範囲のデータすべてをStringBufferにため込んだら、文字列としてクリップボードに転送する。
      StringSelection ss = new StringSelection(buffer.toString());
      clipboard.setContents(ss, ss);
  2. Paste

    クリップボード内のデータはセルとセルの間には'\t'、行が変わるごとに'\n'が埋め込まれた状態となっています。
    これを単純にセル単位で設定していきます。

    この場合StringTokenizerを使用したいところですが、StringTokenizerを使用して行単位、セル単位で分割すると、未入力のセルをTokenとして切りだしてはくれません。
    そのため、未入力のセルのデータを貼り付けると貼り付け先のセルは本来長さ0の文字列が設定されるべきですが、未入力のセルデータは飛ばされ次のセルのデータが設定されてしまいます。

    ※String.split() を使用すればある程度は未入力のセルも認識可能だが、末尾に'\t''\n'が存在するとやはり無視されてしまいます。

    正しく未入力セルを認識するようにするためには、自前で'\n''\t'を検索し、行単位、セル単位の分割を行う方が良いでしょう。

    これにより、正しくセル単位でデータを取り出すことが出来ます。

    コード例では、
    • コピー時に複数範囲を設定していて複写先がテーブルの端などではみ出る場合
    • 複写先として元データ以上の範囲が選択されている場合、等
    の処理も入れているため多少複雑に見えるますが、基本的にはただセル単位で分割されたデータをセル単位に設定しているだけです。
また、サンプルコードで注意すべき点は以下の2点です。
  • このコードは文字列にのみ適用可能であること。
  • セル内に改行('\n')がある場合は正しく認識出来ない。
※サンプルコードのActionクラスは汎用的に使用可能としたつもりですが、これらのケースが想定される場合はカスタマイズが必要となります。
・補足
-

各セルデータが複数行入力されることが想定される場合、セル内でどのように改行されるかにより上記処理も多少修正が必要となります。

具体的には、
  1. HTMLタグの<br>で実現させる。
  2. 改行を'\n'"\r\n"で実現させる。
の2通りが主に想定される方法だと思います。

JTableの各セルはデフォルトではそれぞれJLabelを継承したのRendererが使用されています。
このため、デフォルトのRendererのままではHTMLタグの<br>を使用することにより改行を実現することになります。

対して独自のJtextAreaを継承したRendererを設定すれば'\n'でも改行されます。

もし b. のケースで実現させるとなると、上で説明した処理ではセル内での改行とセル自体の改行で区別がつかなくなってしまうため、実装には少々工夫が必要です。

・コード例
-
<メインクラス内>
    private final static KeyStroke CUT
        = KeyStroke.getKeyStroke(KeyEvent.VK_X,ActionEvent.CTRL_MASK,false);
    
    private final static KeyStroke COPY 
        = KeyStroke.getKeyStroke(KeyEvent.VK_C,ActionEvent.CTRL_MASK,false);
    
    private final static KeyStroke PASTE 
        = KeyStroke.getKeyStroke(KeyEvent.VK_V,ActionEvent.CTRL_MASK,false);

    /**
     * キーボードショートカットの登録を行います。
     * 
     * @param table キーボードショートカットを登録するテーブル
     */
    private void initializeKeyShortCut(JTable table) {

        // キーイベント用アクション生成(ショートカットキー毎に別々に生成)
        SampleActionListener actionCut = new SampleActionListener(table, "Cut");
        SampleActionListener actionCopy = new SampleActionListener(table, "Copy");
        SampleActionListener actionPaste = new SampleActionListener(table, "Paste");

        // キーの登録
        table.getInputMap().put(CUT, "Cut");                // 切り取り
        table.getActionMap().put("Cut", actionCut);
        table.getInputMap().put(COPY, "Copy");              // コピー
        table.getActionMap().put("Copy", actionCopy);
        table.getInputMap().put(PASTE, "Paste");            // 貼り付け
        table.getActionMap().put("Paste", actionPaste);
    }
    
    /**
     * 右クリックメニュー初期化を行います。
     * 
     * @param table 右クリックを行うテーブル
     */
    private void initializeRightClickMenu(JTable table) {

        // 右クリック用のメニューを定義
        JMenuItem jmenuitem1, jmenuitem2, jmenuitem3;
        jmenuitem1 = new JMenuItem("Cut    Ctrl+X");            // 切り取り
        jmenuitem2 = new JMenuItem("Copy   Ctrl+C");            // コピー
        jmenuitem3 = new JMenuItem("Paste  Ctrl+V");            // 貼り付け

        // 右クリック用アクションリスナー生成(各メニューアイテム共通で使用)
        SampleActionListener listener
            = new SampleActionListener(table, jmenuitem1, jmenuitem2, jmenuitem3);

        // 各メニュー選択時の処理
        jmenuitem1.addActionListener(listener);
        jmenuitem2.addActionListener(listener);
        jmenuitem3.addActionListener(listener);
        
        // 右クリック時に表示するポップアップを定義
        JPopupMenu jpopup = new JPopupMenu();
        jpopup.add(jmenuitem1);             // Cut    Ctrl+X
        jpopup.add(jmenuitem2);             // Copy   Ctrl+C
        jpopup.add(jmenuitem3);             // Paste  Ctrl+V
        
        // マウスイベント用リスナー追加
        table.addMouseListener(new SampleMouseListener(
                table, jpopup));
        // ポップアップをフレームに設定
        this.add(jpopup);
    }
<Actionリスナー>
public class SampleActionListener extends AbstractAction implements ActionListener {
    
    private JTable jTable;
    
    private JMenuItem jmenuitem1, jmenuitem2, jmenuitem3;
    
    private String command;

    private Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();

    /**
     * コンストラクタ
     *
     * @param jTable コピー、ペーストを行うテーブル
     * @param jmenuitem1 1つ目のメニューアイテム
     * @param jmenuitem2 2つ目のメニューアイテム
     * @param jmenuitem3 3つ目のメニューアイテム
     */
    public SampleActionListener(JTable jTable,
            JMenuItem jmenuitem1, JMenuItem jmenuitem2, JMenuItem jmenuitem3) {
        this.jTable = jTable;
        this.jmenuitem1 = jmenuitem1;
        this.jmenuitem2 = jmenuitem2;
        this.jmenuitem3 = jmenuitem3;
        this.command = "";
    }

    /**
     * コンストラクタ
     *
     * @param jTable コピー、ペーストを行うテーブル
     * @param command コマンド文字列
     */
    public SampleActionListener(JTable jTable, String command) {
        this.jTable = jTable;
        this.jmenuitem1 = null;
        this.jmenuitem2 = null;
        this.jmenuitem3 = null;
        this.command = command;
    }
    
    /**
     * アクション(右クリックからメニュー選択、またはショートカットキー)発生時の処理
     * 
     */
    public void actionPerformed(ActionEvent e) {
        
        /** Cut **/
        if(e.getSource() == jmenuitem1 || command.equals("Cut")) {
            // カット処理
            processCopy(jTable, true);
        }
        /** Copy **/
        else if(e.getSource() == jmenuitem2 || command.equals("Copy")) {
            // コピー処理
            processCopy(jTable, false);
            return;
        }
        /** Paste **/
        else if(e.getSource() == jmenuitem3 || command.equals("Paste")) {
            // ペースト処理
            processPaste(jTable);
        }
        /** その他 **/
        else {
            // 未処理
        }
    }

    /**
     * 切り取り/コピー処理を行います。
     * 
     * @param jTable 操作するテーブル
     * @param delete コピー元データを削除する場合はtrue、そうでない場合はfalse
     */
    private void processCopy(JTable jTable, boolean delete) {

        StringBuffer buffer = new StringBuffer();           // コピーするデータ用バッファ
        int numCols = jTable.getSelectedColumnCount();      // 選択されている列数
        int numRows = jTable.getSelectedRowCount();         // 選択されている行数
        int[] rowsSelected = jTable.getSelectedRows();      // 選択されている行のインデックス
        int[] colsSelected = jTable.getSelectedColumns();   // 選択されている列のインデックス
        
        /* セルからデータ取得 */
        for(int i = 0; i < numRows; i++) {
            for(int j = 0; j < numCols; j++) {
                // 1セルずつバッファにデータをコピー
                buffer.append(jTable.getValueAt(rowsSelected[i], colsSelected[j]));
                /** 削除指定あり **/
                if(delete) {
                    // コピーした箇所に""を設定することにより、データ消去
                    jTable.setValueAt("", rowsSelected[i], colsSelected[j]);
                }
                // 1データ間毎にタブを挿入
                if(j < numCols-1) buffer.append("\t");
            }
            // 行が変わるとLFを挿入
            buffer.append("\n");
        }
        
        // 文字列としてバッファのデータをクリップボードに転送する
        StringSelection ss = new StringSelection(buffer.toString());
        clipboard.setContents(ss, ss);
        
        // JTableを再描画
        jTable.repaint();
    }
    
    /**
     * ペースト処理を行います。   
     * ※行、セル分割時にStringTokenizerを使用すると未入力のセルが飛ばされてしまうため、危険。 
     *    
     * @param jTable 操作するテーブル   
     */   
    private void processPaste(JTable jTable) {   
           
        String  rowStr;                                     // 1行全体を連結した文字列   
        int startRow = jTable.getSelectedRows()[0];         // 選択された先頭行のインデックス 
        int startCol = jTable.getSelectedColumns()[0];      // 選択された先頭列のインデックス 
        int endRow = jTable.getSelectedRows()[jTable.getSelectedRows().length-1];   
                                                            // 選択された最終行のインデックス 
        int endCol = jTable.getSelectedColumns()[jTable.getSelectedColumns().length-1];   
                                                            // 選択された先頭列のインデックス 
           
        // クリップボードの内容を転送可能なオブジェクトとして取得する   
        Transferable transferable = clipboard.getContents(jTable);   
           
        /** クリップボードにデータがあれば **/   
        if(transferable != null) {   
            try{   
                // クリップボードのデータを文字列として取得   
                String str = (String)transferable.getTransferData(DataFlavor.stringFlavor); 
                   
                /*----------*/   
                /* 1行分割 */   
                /*----------*/   
                // LFの位置を取得する   
                ArrayList<Integer> listLf = new ArrayList<Integer>();   
                for(int i = 0; i < str.length(); i++) {   
                    if(str.charAt(i) == '\n')   listLf.add(i);   
                }   
                // 1行単位にする   
                ArrayListt<Stringt> listLine = new ArrayList<String>();   
                for(int i = 0, pos = 0; i < listLf.size(); i++) {   
                    listLine.add(str.substring(pos, listLf.get(i)));   
                    pos = listLf.get(i) + 1;   
                }   
   
                // 選択したセルよりも複写先セルの数が多い場合は、同じものを繰り返し貼り付ける   
                for(int i = startRow; (i == startRow) || ((i + listLine.size() - 1) <= endRow);
                    i += listLine.size()) {   
                       
                    Iterator<String> iteratorLine = listLine.iterator();   
                    for(int j = 0; iteratorLine.hasNext(); j++) {   
                        // 1行の文字列   
                        rowStr = iteratorLine.next();   
                           
                        /*----------*/   
                        /* セル分割 */   
                        /*----------*/   
                        // タブの位置を取得する   
                        ArrayList<Integer> listTab = new ArrayList<Integer>();   
                        for(int k = 0; k < rowStr.length(); k++) {   
                            if(rowStr.charAt(k) == '\t')    listTab.add(k);   
                        }   
                        // セル単位にする   
                        ArrayList<String> listCell = new ArrayList<String>();   
                        int pos = 0;   
                        for(int k = 0; k < listTab.size(); k++) {   
                            listCell.add(rowStr.substring(pos, listTab.get(k)));   
                            pos = listTab.get(k) + 1;   
                        }   
                        // 最後のセル   
                        if(pos <= (rowStr.length()-1)) {   
                            listCell.add(rowStr.substring(pos, rowStr.length()));   
                        }   
                        else {   
                            // 最後のセルが何も無い場合   
                            listCell.add("");   
                        }   
                           
                        // 選択したセルよりも複写先セルの数が多い場合は、
                        // 同じものを繰り返し貼り付ける
                        for(int x = startCol;
                            (x == startCol) || ((x + listCell.size() - 1) <= endCol); 
                            x += listCell.size()) {   
                               
                            Iterator<String> iteratorCell = listCell.iterator();   
                            for(int y = 0; iteratorCell.hasNext(); y++) {   
                                // 1セル分のデータを取得   
                                String value = iteratorCell.next();   
                                // 貼り付け対象セルがテーブルからはみ出していなければ   
                                if(i + j < jTable.getRowCount() &&   
                                    x + y < jTable.getColumnCount()) {   
                                    // 1セル分のデータを貼り付け   
                                    jTable.setValueAt(value, i + j, x + y);   
                                }   
                            }   
                        }   
                    }   
                }   
            }   
            catch(IOException e1){   
                e1.printStackTrace();   
            }   
            catch(UnsupportedFlavorException e2){   
                e2.printStackTrace();   
            }   
            catch(NullPointerException e3){   
                e3.printStackTrace();   
            }   
            catch(Throwable t){   
                t.printStackTrace();   
            }   
               
            // JTableを再描画   
            jTable.repaint();   
        }   
    }   
}


inserted by FC2 system