• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:JTabbedPane間でタブのドラッグ&ドロップ移動
#navi(../)
#tags()
RIGHT:Posted by &author(aterai); at 2009-03-23
*JTabbedPane間でタブのドラッグ&ドロップ移動 [#yae9971e]
JTabbedPane間でタブのDrag&Dropによる移動を行います。
---
category: swing
folder: DnDExportTabbedPane
title: JTabbedPane間でタブのドラッグ&ドロップ移動
tags: [JTabbedPane, TransferHandler, DragAndDrop, GlassPane, Cursor]
author: aterai
pubdate: 2009-03-23T14:24:48+09:00
description: JTabbedPane間でタブのDrag&Dropによる移動を行います。
image: https://lh5.googleusercontent.com/_9Z4BYR88imo/TQTLW06ZMXI/AAAAAAAAAXc/vzeXm4pwhVY/s800/DnDExportTabbedPane.png
hreflang:
    href: https://java-swing-tips.blogspot.com/2010/02/tabtransferhandler.html
    lang: en
---
* 概要 [#summary]
`JTabbedPane`間でタブの`Drag&Drop`による移動を行います。

-&jnlp;
-&jar;
-&zip;
#download(https://lh5.googleusercontent.com/_9Z4BYR88imo/TQTLW06ZMXI/AAAAAAAAAXc/vzeXm4pwhVY/s800/DnDExportTabbedPane.png)

//#screenshot
#ref(http://lh5.ggpht.com/_9Z4BYR88imo/TQTLW06ZMXI/AAAAAAAAAXc/vzeXm4pwhVY/s800/DnDExportTabbedPane.png)

**サンプルコード [#e06345a4]
* サンプルコード [#sourcecode]
#code(link){{
class TabTransferHandler extends TransferHandler {
  private final DataFlavor localObjectFlavor;
  public TabTransferHandler() {
    System.out.println("TabTransferHandler");
    localObjectFlavor = new ActivationDataFlavor(
      DnDTabbedPane.class, DataFlavor.javaJVMLocalObjectMimeType, "DnDTabbedPane");
  }
  //private static DnDTabbedPane source;
  //private synchronized static void setComponent(JComponent comp) {
  //  if(comp instanceof DnDTabbedPane) {
  //    source = (DnDTabbedPane)comp;
  //  }
  //}
  //@Override public void exportAsDrag(JComponent comp, InputEvent e, int action) {
  //  super.exportAsDrag(comp, e, action);
  //  setComponent(comp);
  //}
  private final DataFlavor localObjectFlavor = new DataFlavor(DnDTabData.class, "DnDTabData");
  private DnDTabbedPane source = null;

  @Override protected Transferable createTransferable(JComponent c) {
    System.out.println("createTransferable");
    if(c instanceof DnDTabbedPane) source = (DnDTabbedPane)c;
    return new DataHandler(c, localObjectFlavor.getMimeType());
    if (c instanceof DnDTabbedPane) source = (DnDTabbedPane) c;
    return new Transferable() {
      @Override public DataFlavor[] getTransferDataFlavors() {
        return new DataFlavor[] {localObjectFlavor};
      }

      @Override public boolean isDataFlavorSupported(DataFlavor flavor) {
        return Objects.equals(localObjectFlavor, flavor);
      }

      @Override public Object getTransferData(DataFlavor flavor) 
            throws UnsupportedFlavorException, IOException {
        if (isDataFlavorSupported(flavor)) {
          return new DnDTabData(source);
        } else {
           throw new UnsupportedFlavorException(flavor);
        }
      }
    };
  }

  @Override public boolean canImport(TransferSupport support) {
    //System.out.println("canImport");
    // System.out.println("canImport");
    if (!support.isDrop() || !support.isDataFlavorSupported(localObjectFlavor)) {
      return false;
    }
    support.setDropAction(MOVE);
    support.setDropAction(TransferHandler.MOVE);
    DropLocation tdl = support.getDropLocation();
    Point pt = tdl.getDropPoint();
    DnDTabbedPane target = (DnDTabbedPane)support.getComponent();
    DnDTabbedPane target = (DnDTabbedPane) support.getComponent();
    target.autoScrollTest(pt);
    DnDTabbedPane.DropLocation dl =
      (DnDTabbedPane.DropLocation)target.dropLocationForPoint(pt);
      (DnDTabbedPane.DropLocation) target.dropLocationForPoint(pt);
    int idx = dl.getIndex();
    boolean isDropable = false;
    boolean isDroppable = false;

    if (target==source) {
      isDropable = target.getTabAreaBounds().contains(pt) && idx>=0 &&
                   idx!=target.dragTabIndex && idx!=target.dragTabIndex+1;
    if (target == source) {
      isDroppable = target.getTabAreaBounds().contains(pt) && idx >= 0 &&
                   idx != target.dragTabIndex && idx != target.dragTabIndex + 1;
    } else {
      if (source!=null && target!=source.getComponentAt(source.dragTabIndex)) {
        isDropable = target.getTabAreaBounds().contains(pt) && idx>=0;
      if (source != null && target != source.getComponentAt(source.dragTabIndex)) {
        isDroppable = target.getTabAreaBounds().contains(pt) && idx >= 0;
      }
    }

    Component c = target.getRootPane().getGlassPane();
    c.setCursor(isDropable?DragSource.DefaultMoveDrop:DragSource.DefaultMoveNoDrop);
    if (isDropable) {
    c.setCursor(isDroppable?DragSource.DefaultMoveDrop:DragSource.DefaultMoveNoDrop);
    if (isDroppable) {
      support.setShowDropLocation(true);
      dl.setDropable(true);
      dl.setDroppable(true);
      target.setDropLocation(dl, null, true);
      return true;
    } else {
      support.setShowDropLocation(false);
      dl.setDropable(false);
      dl.setDroppable(false);
      target.setDropLocation(dl, null, false);
      return false;
    }
  }

  private BufferedImage makeDragTabImage(DnDTabbedPane tabbedPane) {
    Rectangle rect = tabbedPane.getBoundsAt(tabbedPane.dragTabIndex);
    BufferedImage image = new BufferedImage(
      tabbedPane.getWidth(), tabbedPane.getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics g = image.getGraphics();
    tabbedPane.paint(g);
    g.dispose();
    if (rect.x<0) {
      rect.translate(-rect.x,0);
    if (rect.x < 0) {
      rect.translate(-rect.x, 0);
    }
    if (rect.y<0) {
      rect.translate(0,-rect.y);
    if (rect.y < 0) {
      rect.translate(0, -rect.y);
    }
    if (rect.x+rect.width>image.getWidth()) {
    if (rect.x + rect.width > image.getWidth()) {
      rect.width = image.getWidth() - rect.x;
    }
    if (rect.y+rect.height>image.getHeight()) {
    if (rect.y + rect.height > image.getHeight()) {
      rect.height = image.getHeight() - rect.y;
    }
    return image.getSubimage(rect.x,rect.y,rect.width,rect.height);
    return image.getSubimage(rect.x, rect.y, rect.width, rect.height);
  }

  private static GhostGlassPane glassPane;
  @Override public int getSourceActions(JComponent c) {
    System.out.println("getSourceActions");
    DnDTabbedPane src = (DnDTabbedPane)c;
    if (glassPane==null) {
      c.getRootPane().setGlassPane(glassPane = new GhostGlassPane(src));
    if (c instanceof DnDTabbedPane) {
      DnDTabbedPane src = (DnDTabbedPane) c;
      c.getRootPane().setGlassPane(new GhostGlassPane(src));
      if (src.dragTabIndex < 0) {
        return TransferHandler.NONE;
      }
      setDragImage(makeDragTabImage(src));
      c.getRootPane().getGlassPane().setVisible(true);
      return TransferHandler.MOVE;
    }
    if (src.dragTabIndex<0) return NONE;
    glassPane.setImage(makeDragTabImage(src));
    source = src;
    //setDragImage(img); //java 1.7.0-ea-b84
    glassPane.setVisible(true);
    return MOVE;
    return TransferHandler.NONE;
  }

  @Override public boolean importData(TransferSupport support) {
    System.out.println("importData");
    if (!canImport(support)) return false;

    DnDTabbedPane target = (DnDTabbedPane)support.getComponent();
    DnDTabbedPane target = (DnDTabbedPane) support.getComponent();
    DnDTabbedPane.DropLocation dl = target.getDropLocation();
    try {
      DnDTabbedPane source = (DnDTabbedPane)support.getTransferable()
      DnDTabbedPane source = (DnDTabbedPane) support.getTransferable()
        .getTransferData(localObjectFlavor);
      int index = dl.getIndex(); //boolean insert = dl.isInsert();
      if (target==source) {
      if (target == source) {
        source.convertTab(source.dragTabIndex, index);
      } else {
        source.exportTab(source.dragTabIndex, target, index);
      }
      return true;
    } catch (UnsupportedFlavorException ufe) {
      ufe.printStackTrace();
    } catch (java.io.IOException ioe) {
    } catch (IOException ioe) {
      ioe.printStackTrace();
    }
    return false;
  }

  @Override protected void exportDone(JComponent c, Transferable data, int action) {
    System.out.println("exportDone");
    DnDTabbedPane src = (DnDTabbedPane)c;
    DnDTabbedPane src = (DnDTabbedPane) c;
    c.getRootPane().getGlassPane().setVisible(false);
    src.setDropLocation(null, null, false);
  }
}
}}

**解説 [#o96b9e3f]
上記のサンプルでは、JDK 6 で導入された、TransferHandler.DropLocationを継承するDnDTabbedPane.DropLocationなどを作成して、JTabbedPane間でタブの移動ができるように設定しています。
* 解説 [#explanation]
上記のサンプルでは、`JDK 6`で導入された、`TransferHandler.DropLocation`を継承する`DnDTabbedPane.DropLocation`などを作成して、`JTabbedPane`間でタブの移動ができるように設定しています。

-注意点
--自身の子であるJTabbedPaneにタブを移動することはできない
--子コンポーネントがnull(例えばaddTab("Tab",null))のタブは移動不可
--タブが選択不可(例えばsetEnabledAt(idx, false))の場合は移動不可
-バグ?
--%%子コンポーネントがJTableの場合、マウスカーソルが点滅する%%
---%%Windows環境のみ?%%
--子コンポーネントがJTextAreaなどの場合、ドラッグ中のタブゴーストが表示できない
---%%JTable、JTextAreaどちらも、JScrollPaneが影響している?%%
---java 1.7.0-ea-b84 で、TransferHandler#setDragImage(Image) を使用するとちゃんと表示される
---textArea.setTransferHandler(null);とすれば、1.6.0でも正常に表示される。
--SCROLL_TAB_LAYOUTの場合、タブゴーストにスクロールボタンが表示される場合がある
>
//#screenshot(,screenshot1.png)
#ref(http://lh6.ggpht.com/_9Z4BYR88imo/TQTLZe_UIkI/AAAAAAAAAXg/bCzrlm037N8/s800/DnDExportTabbedPane1.png)
- 注意点
-- 自身の子として配置されている`JTabbedPane`にタブを移動することはできない
-- 子コンポーネントが`null`(例えば`addTab("Tab",null)`)のタブは移動不可
-- タブが選択不可(例えば`setEnabledAt(idx, false)`)の場合は移動不可
- バグ?
-- %%子コンポーネントが`JTable`の場合、マウスカーソルが点滅する%%
--- %%`Windows`環境のみ?%%
-- 子コンポーネントが`JTextArea`などの場合、ドラッグ中のタブゴーストが表示できない
--- %%`JTable`、`JTextArea`どちらも`JScrollPane`が影響している?%%
---`Java 1.7.0-ea-b84`以上で、`TransferHandler#setDragImage(Image)`を使用すると正常に表示される
--- `textArea.setTransferHandler(null);`とすれば、`1.6.0`でも正常に表示される
-- `SCROLL_TAB_LAYOUT`の場合、タブゴーストにスクロールボタンが表示される場合がある

**参考リンク [#y58f4e18]
-[[JTabbedPaneのタブをドラッグ&ドロップ>Swing/DnDTabbedPane]]
-[[JLayerを使ってJTabbedPaneのタブの挿入位置を描画する>Swing/DnDLayerTabbedPane]]
#ref(https://lh6.googleusercontent.com/_9Z4BYR88imo/TQTLZe_UIkI/AAAAAAAAAXg/bCzrlm037N8/s800/DnDExportTabbedPane1.png)

**コメント [#gcad5394]
- タブのドラッグ中、JTable上などでCursorが点滅するのを修正。 -- [[aterai]] &new{2009-05-19 (火) 17:32:38};
- 点滅の原因は? -- [[Dad]] &new{2010-01-16 (土) 01:48:13};
-- おそらく、[http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6700748 Cursor flickering during D&D when using CellRendererPane with validation]が原因。現在は、canImport メソッド内で、一々GlassPane#setCursor(isDropable?DragSource.DefaultMoveDrop:DragSource.DefaultMoveNoDrop);として回避中。 -- [[aterai]] &new{2010-01-16 (土) 12:25:38};
- 6u20ぐらいからWebStartで、java.security.AccessControlException: access denied (java.awt.AWTPermission accessClipboard)? -- [[aterai]] &new{2010-06-17 (木) 01:57:34};
-- チュートリアルのデモ[http://docs.oracle.com/javase/tutorial/uiswing/dnd/dropmodedemo.html Demo - DropDemo (The Java™ Tutorials > Creating a GUI With JFC/Swing Drag and Drop and Data Transfer)]でも同様のエラーが発生するので多分Java 1.6.0_??のバグ。 -- [[aterai]] &new{2010-06-17 (木) 14:31:14};
-- 6u21では、修正されている?: [http://download.java.net/jdk6/6u21/promoted/b05/changes/JDK6u21.b05.list.html 6945178 2-High Defect SecurityException upon drag-and-drop(Bug/RFE fixed in JDK 6u21 build)] -- [[aterai]] &new{2010-06-17 (木) 14:32:45};
-- 修正されたようです。[http://java.sun.com/javase/6/webnotes/BugFixes6u21.html Java SE 6 Update 21 Bug Fixes] -- [[aterai]] &new{2010-07-08 (木) 21:54:27};
- getTabPlacement()==RIGHTなどの場合に、DropTargetの描画がおかしくなるのを修正。 -- [[aterai]] &new{2012-02-05 (日) 11:13:40};
* 参考リンク [#reference]
- [[JTabbedPaneのタブをドラッグ&ドロップ>Swing/DnDTabbedPane]]
- [[JLayerを使ってJTabbedPaneのタブの挿入位置を描画する>Swing/DnDLayerTabbedPane]]

* コメント [#comment]
#comment
- タブのドラッグ中、`JTable`上などで`Cursor`が点滅するのを修正。 -- &user(aterai); &new{2009-05-19 (火) 17:32:38};
- 点滅の原因は? -- &user(Dad); &new{2010-01-16 (土) 01:48:13};
-- おそらく、[https://bugs.openjdk.org/browse/JDK-6700748 Cursor flickering during D&D when using CellRendererPane with validation]が原因。現在は、`canImport`メソッド内で、一々`GlassPane#setCursor(isDroppable ? DragSource.DefaultMoveDrop : DragSource.DefaultMoveNoDrop);`として回避中。 -- &user(aterai); &new{2010-01-16 (土) 12:25:38};
- `6u20`ぐらいから`Web Start`で、`java.security.AccessControlException: access denied (java.awt.AWTPermission accessClipboard)`? -- &user(aterai); &new{2010-06-17 (木) 01:57:34};
-- チュートリアルのデモ[https://docs.oracle.com/javase/tutorial/uiswing/dnd/dropmodedemo.html Demo - DropDemo (The Java™ Tutorials > Creating a GUI With JFC/Swing Drag and Drop and Data Transfer)]でも同様のエラーが発生するので多分`Java 1.6.0_??`のバグ。 -- &user(aterai); &new{2010-06-17 (木) 14:31:14};
-- `6u21`では、修正されている?: [http://download.java.net/jdk6/6u21/promoted/b05/changes/JDK6u21.b05.list.html 6945178 2-High Defect SecurityException upon drag-and-drop(Bug/RFE fixed in JDK 6u21 build)] -- &user(aterai); &new{2010-06-17 (木) 14:32:45};
-- 修正されたようです。[http://www.oracle.com/technetwork/java/javase/bugfixes6u21-156339.html Java SE 6 Update 21 Bug Fixes] -- &user(aterai); &new{2010-07-08 (木) 21:54:27};
- `getTabPlacement()==RIGHT`などの場合に、`DropTarget`の描画がおかしくなるのを修正。 -- &user(aterai); &new{2012-02-05 (日) 11:13:40};

#comment