IT_Programming/Java

JTree로 드래그-앤-드롭(Adding Drop Support with JTree) 추가

JJun ™ 2008. 1. 24. 21:20

시간이 지남에 따라 Swing 구성요소 세트로 끌어서 놓기 기능이 많이 변경되었습니다. 이전 버전에서는 java.awt.dnd 패키지에 기본 API가 있었지만(java.awt.datatransfer의 지원) 초기 사용자 클릭에서 놓기 조작까지 끌기 작업의 모든 측면을 정의해야 했습니다. J2SE 1.4는 기능 세트에 따라 향상되고 이전 팁 Dragging Text and Images with Swing에 설명된 API를 업데이트합니다.

이전 API의 변경으로 끌어서 놓기 작업이 훨씬 쉬워졌습니다. 수많은 구성요소가 끌어서 놓기 작업을 내장 지원하기 때문입니다. 예를 들어, JTextField에서 끌기 작업을 사용 가능하게 설정하려면 텍스트 구성요소에서 setDragEnabled(true)를 호출하기만 하면 됩니다. 그러면 사용자는 텍스트 구성요소의 텍스트를 끌어서, 놓기 지역 역할을 하는 다른 애플리케이션이나 텍스트 필드 자체 내에 놓을 수 있습니다.

텍스트 구성요소는 JColorChooser 구성요소와 같이 내장된 놓기 지원을 제공하지만 JList, JTable 또는 JTree와 같은 다른 Swing 구성요소에 놓기 지원을 추가하려면 약간의 추가 작업을 수행해야 합니다. 작업이 복잡하게 들릴 수 있겠지만 TransferHandler의 1.6 내부 DropLocation 클래스의 새로운 기능 덕분에 이 작업은 비교적 쉬워졌습니다. 놓기 가능한 데이터 종류 및 일단 놓은 후에 수행해야 하는 작업에 대해 정의하는 JTree에 대한 TransferHandler를 생성하기만 하면 됩니다. 이러한 작업은 각각 canImport!import!Data 메소드로 제공됩니다. TransferSupport 내부 클래스는 1.6의 새로운 기능으로, 전송 처리기를 정의하는 보다 간단한 방법을 제공해 줍니다.

이미지나 텍스트를 나뭇잎에 놓도록 허용하는 멋진 JTree를 작성할 수 있지만 다음 예제는 문자열만 수락합니다. 이미지도 수락하도록 예제를 활용해 보십시오. 문자열을 지원하려면 TransferHandler.TransferSupport 인수로 canImport! 메소드를 정의해서 지원되는 데이터 유형(문자열) 및 작업 유형을 확인해야 합니다. TransferSupport에는 작업의 TransferHandler.DropLocation을 가져오는 getDropLocation 메소드도 있습니다. 위치가 유효한 지점이면 canImport! 메소드는 참을 반환해야 합니다. 다음은 널이 아닌 트리 경로에서 문자열 유형의 놓기 전송에 대해 참을 반환하는 메소드입니다.

  public boolean canImport!(TransferHandler.TransferSupport support) {
    if (!support.isDataFlavorSupported(DataFlavor.stringFlavor) ||
        !support.isDrop()) {
      return false;
    }

    JTree.DropLocation dropLocation =
      (JTree.DropLocation)support.getDropLocation();

    return dropLocation.getPath() != null;
  }
 

JTree.DropLocationJTree 구성요소에 대한 TransferHandler.DropLocation의 사전 정의된 구현입니다. JList로 작업하기 위한 JList.DropLocationJTree.DropLocation이 있는 JTree에 대한 다른 것도 있습니다. 기본 텍스트 구성요소 놓기 처리 동작을 원하지 않을 경우 JTextComponent.DropLocation에 네 번째 구현이 있습니다.

JTree에 놓기 지원을 추가하는 나머지 역할은 import!Data 메소드가 수행합니다. import!Data 메소드의 이전 버전인 import!Data(JComponent comp, Transferable t)는 직접 호출되지 않지만 여전히 지원됩니다. 새로운 처리기는 실제로 import!Data(TransferHandler.TransferSupport support) 버전을 대신 구현해야 합니다. 이 메소드에서 전송된 데이터를 얻어 TreePath의 올바른 위치에 배치해야 합니다.

전송된 데이터를 얻어도 이전 import!Data 메소드에서 새 메소드로 이동하는 데는 실제로 영향을 주지 않습니다. 메소드에 Transferable 인수를 가지는 대신 support.getTransferable 메소드로 TransferSupport에서 가져옵니다. 그런 다음 적절한 유형의 데이터를 가져오기만 하면 됩니다.

  Transferable transferable = support.getTransferable();

  String transferData;
  try {
    transferData = (String)transferable.getTransferData(
      DataFlavor.stringFlavor);
  } catch (IOException e) {
    return false;
  } catch (UnsupportedFlavorException e) {
    return false;
  }
 

놓기 작업의 위치를 결정하려면 JTree.DropLocation 클래스를 사용하십시오. DropLocationgetChildIndex 메소드를 호출하면 트리에 새 노드를 추가할 위치가 제공됩니다. -1의 하위 인덱스 값은 사용자가 트리의 빈 부분에 노드를 놓았다는 의미입니다. 이 예제에서는 노드가 끝에 추가됩니다. DropLocationgetPath 메소드를 호출하면 놓기 위치에 대해 TreePath를 반환합니다. 그런 다음 놓기 위치와 연관된 상위 노드를 찾으려면 경로의 getLastPathComponent 메소드를 호출하십시오.

  JTree.DropLocation dropLocation =
    (JTree.DropLocation)support.getDropLocation();

  TreePath path = dropLocation.getPath();

  int childIndex = dropLocation.getChildIndex();
  if (childIndex == -1) {
    childIndex = model.getChildCount(path.getLastPathComponent());
  }

  DefaultMutableTreeNode newNode =
    new DefaultMutableTreeNode(transferData);
  DefaultMutableTreeNode parentNode =
    (DefaultMutableTreeNode)path.getLastPathComponent();
  model.insertNodeInto(newNode, parentNode, childIndex);
 

새 경로 요소가 표시되는지 확인하는 것도 도움이 됩니다. 전체 import!Data 메소드는 다음과 같습니다.

  public boolean import!Data(TransferHandler.TransferSupport support) {
    if (!canImport!(support)) {
      return false;
    }

    JTree.DropLocation dropLocation =
      (JTree.DropLocation)support.getDropLocation();

    TreePath path = dropLocation.getPath();

    Transferable transferable = support.getTransferable();

    String transferData;
    try {
      transferData = (String)transferable.getTransferData(
        DataFlavor.stringFlavor);
    } catch (IOException e) {
      return false;
    } catch (UnsupportedFlavorException e) {
      return false;
    }

    int childIndex = dropLocation.getChildIndex();
    if (childIndex == -1) {
      childIndex = model.getChildCount(path.getLastPathComponent());
    }

    DefaultMutableTreeNode newNode =
      new DefaultMutableTreeNode(transferData);
    DefaultMutableTreeNode parentNode =
      (DefaultMutableTreeNode)path.getLastPathComponent();
    model.insertNodeInto(newNode, parentNode, childIndex);

    TreePath newPath = path.pathByAddingChild(newNode);
    tree.makeVisible(newPath);
    tree.scrollRectToVisible(tree.getPathBounds(newPath));

    return true;
  }
 

완전히 작동하는 놓기 가능한 JTree를 가지도록 충분한 정보를 자세히 보여 드렸습니다. 여기서 놓기 지원에 관련된 중요한 정보인 DropMode에 대해 알려드리겠습니다. DropMode는 구성요소에서 놓기 작업이 발생되는 위치를 표시하는 방식에 관한 모드를 열거한 것입니다. JTree에 대해 다음 네 가지 모드가 지원됩니다.

  • DropMode.USE_SELECTION
  • DropMode.ON
  • DropMode.INSERT
  • DropMode.ON_OR_INSERT

그러나 열거는 다른 구성요소의 특정 모드에 대해 더 커집니다(JTable로 작업할 경우 INSERT_COLS 또는 INSERT_ROWS).

놓기 모드는 어떻게 처리할까요? 기본적으로 이 모드는 USE_SELECTION입니다. 이것은 JTree에서 선택된 항목을 더 이상 강조 표시하지 않음을 의미합니다. 대신 선택 메커니즘을 사용하여 놓기 위치를 강조 표시하십시오. JTree가 놓기를 지원하면 기본값을 변경하는 것이 좋습니다. 더 나은 모드는 on입니다. 이 모드를 사용하여 JTree 및 잠재적 놓기 위치에서 현재 선택을 모두 확인할 수 있습니다. INSERT 모드를 사용하여 현재 선택 사항을 보면서 기존 노드 사이에 새 노드를 삽입할 수 있습니다. ON_OR_INSERT는 후자 두 개를 결합한 것입니다. 다음 그림 4개는 네 가지 옵션을 보여 줍니다. 완료된 프로그램은 다른 동작을 시도하도록 모드 콤보 상자를 제공합니다.

 

 

그림 1.
Use Selection

 

 

그림 2.
On

 

 

그림 3.
Insert

 

 

그림 4.
on or Insert

다음은 놓기 가능한 전체 트리 프로그램입니다. 프로그램에는 중간에 선택하여 JTree에 놓을 수 있는 텍스트 항목의 맨 위에 텍스트 영역이 있습니다. 놓기 모드는 맨 아래의 콤보 상자에서 설정할 수 있습니다. 트리에 대한 데이터 모델은 JTree를 작성할 때 지정되지 않은 경우 작성된 기본 모델에서 나옵니다.

 

import! java.awt.*;
import! java.awt.datatransfer.*;
import! java.awt.event.*;
import! java.io.*;
import! javax.swing.*;
import! javax.swing.tree.*;

public class DndTree {
  public static void main(String args[]) {
    Runnable runner = new Runnable() {
      public void run() {
        JFrame f = new JFrame("D-n-D JTree");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel top = new JPanel(new BorderLayout());
        JLabel dragLabel = new JLabel("Drag me:");
        JTextField text = new JTextField();
        text.setDragEnabled(true);
        top.add(dragLabel, BorderLayout.WEST);
        top.add(text, BorderLayout.CENTER);
        f.add(top, BorderLayout.NORTH);

        final JTree tree = new JTree();
        final DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
        tree.setTransferHandler(new TransferHandler() {
          public boolean canImport!(TransferHandler.TransferSupport support) {
            if (!support.isDataFlavorSupported(DataFlavor.stringFlavor) ||
                !support.isDrop()) {
              return false;
            }

            JTree.DropLocation dropLocation =
              (JTree.DropLocation)support.getDropLocation();

            return dropLocation.getPath() != null;

          }

          public boolean import!Data(TransferHandler.TransferSupport support) {
            if (!canImport!(support)) {
              return false;
            }

            JTree.DropLocation dropLocation =
              (JTree.DropLocation)support.getDropLocation();

            TreePath path = dropLocation.getPath();

            Transferable transferable = support.getTransferable();

            String transferData;
            try {
              transferData = (String)transferable.getTransferData(
                DataFlavor.stringFlavor);
            } catch (IOException e) {
              return false;
            } catch (UnsupportedFlavorException e) {
              return false;
            }

            int childIndex = dropLocation.getChildIndex();
            if (childIndex == -1) {
              childIndex = model.getChildCount(path.getLastPathComponent());
            }

            DefaultMutableTreeNode newNode =
              new DefaultMutableTreeNode(transferData);
            DefaultMutableTreeNode parentNode =
              (DefaultMutableTreeNode)path.getLastPathComponent();
            model.insertNodeInto(newNode, parentNode, childIndex);

            TreePath newPath = path.pathByAddingChild(newNode);
            tree.makeVisible(newPath);
            tree.scrollRectToVisible(tree.getPathBounds(newPath));

            return true;
          }
        });

        JScrollPane pane = new JScrollPane(tree);
        f.add(pane, BorderLayout.CENTER);

        JPanel bottom = new JPanel();
        JLabel comboLabel = new JLabel("DropMode");
        String options[] = {"USE_SELECTION",
                on", "INSERT", on_OR_INSERT"
        };
        final DropMode mode[] = {DropMode.USE_SELECTION,
                DropMode.ON, DropMode.INSERT, DropMode.ON_OR_INSERT};
        final JComboBox combo = new JComboBox(options);
        combo.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            int selectedIndex = combo.getSelectedIndex();
            tree.setDropMode(mode[selectedIndex]);
          }
        });
        bottom.add(comboLabel);
        bottom.add(combo);
        f.add(bottom, BorderLayout.SOUTH);
        f.setSize(300, 400);
        f.setVisible(true);
      }
    };
    EventQueue.invokeLater(runner);
  }
}
 

끌어서 놓기 지원 및 데이터 전송 API에 대한 자세한 내용은

자바 온라인 자습서의 Introduction to Drag and Drop and Data Transfer 내역을 참조하십시오.