Java

Java3D

_침묵_ 2007. 3. 9. 07:59

출처 :http://kr.sun.com/developers/techtips/core_10_14.html

 

 

Core Java Technologies Tech Tips (2003년 10월 14일)에 오신 여러분을 환영합니다. Java 2 Platform, Standard Edition (J2SE)에 기반한 core Java technologies 와 APIs의 사용에 관한 최신 정보를 얻어 가시기 바랍니다. 이 글에서는 Java 2 SDK, Standard Edition, v 1.4를 사용합니다.

이번 호에서는,

사용자 삽입 이미지
Java3D API로의 초대
사용자 삽입 이미지
TransferHandler 이용하기

 

저자 Daniel H. Steinberg

사용자 삽입 이미지
사용자 삽입 이미지

JAVA3D API의 소개

2003/9/9 Tech TipAffineTransform에서는 Java 2D API 변환에 기반해서 형상을 변형하는 방법을 설명했다. 이번 팁에서는 3가지 예를 통해Java3D API를 소개하고자 한다. 첫번째 예에서, Java3D API를 이용해서 객체를 스크린에 넣고 위치시키는 방법을, 두 번째 예에서는 물체를 계속 움직이게 만드는 방법을, 마지막 예에서는 API를 사용하여 장면에 빛을 삽입하는 방법을 이야기한다.

Java 3D API는 솔라리스와 윈도우뿐만 아니라 다른 플랫폼에서도 사용가능한 J2SE 패키지이다.Java 3D API 홈페이지에서 각자의 컴퓨터에 맞는 버전을 다운로드받을 수 있고, 튜토리얼과 데몬스트레이션, 샘플코드는 물론 Java 3D API를 이용 가능한 다른 플랫폼들의 링크도 볼 수 있다.

Java 3D API를 이용하면 3D 스트럭쳐를 만들고 다룰 수 있는 3D버츄얼 세계를 만들어낼 수가 있다. XML다큐먼트가 이와 비슷하다고 볼 수 있는데, 브라우저에 그려진 XML다큐먼트를 볼 때, 컨텐트에 주목하다 보면 밑줄 그어져 있는 트리 스트럭쳐를 알아차리지 못하는 경우가 있다. 유사하게 자바 3D 애플리케이션에서 공간안의 물체로 보여지는 3D 씬(scene)에 대한 정보는 씬 그래프로 알려진 노드들의 계층구조 안에 저장된다. 이 노드들은 물체들, 포지션이나 움직임에 관한 정보, 외형과 빛에 관한 정보를 나타낸다.

이렇게 트리처럼 생긴 스트럭쳐의 루트(root)는BranchGroup객체인데, 이를 씬을 그리는데 사용되는Canvas3D객체와 함께 관련지어 생각해 볼 필요가 있다. Java 3D와 함께 배포된 예들과 일관성 있도록, 이 글에서는 씬 그래프의 루트를objRoot라고 부른다.

3D세계를 만들기 위해서는 3가지 기초적인 과정이 필요하다.

  • Canvas3D객체를 생성한다.
  • 씬 그래프를 만든다.
  • 씬 그래프의 루트를 가르키는BranchGroup객체로Canvas3D객체를 연결시킨다.

이번 테크팁의 모든 예에서는 위의 3가지 과정을 통해 생성자의 보디(body)를 구성한다.

첫번째 예제 프로그램인Static3DWorld에서createCanvas3D()메소드는JFrame의 크기를 정한 후,canvas3D객체를 생성해서 그것을JFrame의 센터에 추가시킨다. Java 3D API의 주된 임무는 정적 메소드인getPreferredConfiguration()을 호출하고 결과를Canvas3D생성자의 인수로 넘기는 것이다.

이 예의 가장 중요한 동작은createSceneGraph()메소드 안에 포함되어 있다.BranchGroup객체인objRoot는 씬 그래프의 루트를 가리키고,rotator객체TransformGroup는 객체를 x방향으로 Pi/4, y방향으로 Pi/4 회전시키면서objRoot의 자녀객체(a child)로 추가된다.

다음으로, 6면에 각각 다른 색상이 입혀진 단위입방체를 생성하자.ColorCube는 Java 3D API 프리미티브 중에 하나이며,rotator객체의 자녀객체로 추가된다.createSceneGraph()는 그것의 루트에 핸들을 리턴한다.

Static3DWorld의 코드를 보자.

import com.sun.j3d.utils.universe.SimpleUniverse;    import com.sun.j3d.utils.geometry.ColorCube;    import javax.media.j3d.BranchGroup;    import javax.media.j3d.Transform3D;    import javax.media.j3d.TransformGroup;    import javax.media.j3d.Canvas3D;    import javax.swing.JFrame;    import java.awt.BorderLayout;    import java.awt.GraphicsConfiguration;    public class Static3DWorld extends JFrame {        private Transform3D rotate1 = new Transform3D();        private Transform3D rotate2 = new Transform3D();       public Static3DWorld() {         super("Static3DWorld");         Canvas3D canvas3D = createCanvas3D();         BranchGroup scene = createSceneGraph();         connect(canvas3D, scene);       }       private Canvas3D createCanvas3D() {         setSize(300, 300);         getContentPane().setLayout(new BorderLayout());         GraphicsConfiguration config =           SimpleUniverse.getPreferredConfiguration();         Canvas3D canvas3D = new Canvas3D(config);         setSize(300, 300);         getContentPane().add(canvas3D);         return canvas3D;       }       private BranchGroup createSceneGraph() {         BranchGroup objRoot = new BranchGroup();         TransformGroup rotator = new TransformGroup(                                         rotateCube());         objRoot.addChild(rotator);         rotator.addChild(new ColorCube(0.3));         objRoot.compile();         return objRoot;       }       private Transform3D rotateCube() {         rotate1.rotX(Math.PI / 4.0d);         rotate2.rotY(Math.PI / 4.0d);         rotate1.mul(rotate2);         return rotate1;       }       private void connect(Canvas3D canvas3D,                                    BranchGroup scene) {         SimpleUniverse simpleU =                           new SimpleUniverse(canvas3D);         simpleU.getViewingPlatform().                           setNominalViewingTransform();         simpleU.addBranchGraph(scene);       }       public static void main(String[] args) {         new Static3DWorld().setVisible(true);       }    }

Static3DWorld를 컴파일하고 실행하면, 다음과 같은 화면을 보게 된다.

사용자 삽입 이미지

Java 3D API의 두번째 예제에서는 입방체를 움직이도록 하는Spinning3DWorld프로그램을 사용한다. 또 다시, 프로그램을 실행시키는 것은createSceneGraph() 메소드이다.

private BranchGroup createSceneGraph() {        BranchGroup objRoot = new BranchGroup();        TransformGroup spinner = new TransformGroup();        spinner.setCapability(                 TransformGroup.ALLOW_TRANSFORM_WRITE);        objRoot.addChild(spinner);        spinner.addChild(new ColorCube(0.3));        spinner.addChild(makeSpin(spinner));        return objRoot;   }

씬 그래프가 첫번째 예제에서보다 좀 더 복잡해졌다.spinner는 변환에 관한 정보를 업데이트할 수 있는TransformGroup이다. 이 라인이 없으면 회전이 계산될 수는 있지만, 스크린에 나타나지는 않는다. 좀 전에,spinnerobjRoot의 자녀객체로 추가되었고,ColorCube는 생성 후,spinner의 자녀객체로 추가되었다. 하지만, 이번에는spinner가 회전에 관여하는 두 번째 자녀객체를 갖게 된다.RotationInterpolator객체는makeSpin() 메소드로부터 리턴된다.

private RotationInterpolator makeSpin(TransformGroup                                             spinner) {        RotationInterpolator rotator =          new RotationInterpolator(new Alpha(-1, 3000),                                   spinner);        rotator.setAxisOfRotation(rotateCube());        BoundingSphere bounds = new BoundingSphere();        rotator.setSchedulingBounds(bounds);        return rotator;   }

newRotationInterpolatorspinner를 넘겨받기 위해 생성되었다. -1은 프로그램이 끝날 때까지 이 메소드가 실행된다는 것을 말한다. 두 번째 숫자를 3000보다 작은 수로 주면 회전의 속도가 빨라지고, 많이 주면 회전의 속도가 느려진다. 이전 예제에서 보았던rotateCube() 메소드는 여기에서는 회전의 축을 세팅하기 위해 사용된다.

Spinning3DWorld의 코드이다.

import com.sun.j3d.utils.universe.SimpleUniverse;    import com.sun.j3d.utils.geometry.ColorCube;    import javax.media.j3d.BranchGroup;    import javax.media.j3d.Transform3D;    import javax.media.j3d.TransformGroup;    import javax.media.j3d.Canvas3D;    import javax.media.j3d.Alpha;    import javax.media.j3d.RotationInterpolator;    import javax.media.j3d.BoundingSphere;    import javax.swing.JFrame;    import java.awt.BorderLayout;    import java.awt.GraphicsConfiguration;    public class Spinning3DWorld extends JFrame {        private Transform3D rotate1 = new Transform3D();        private Transform3D rotate2 = new Transform3D();       public Spinning3DWorld() {         super("Spinning3DWorld");         Canvas3D canvas3D = createCanvas3D();         BranchGroup scene = createSceneGraph();         connect(canvas3D, scene);       }       private Canvas3D createCanvas3D() {         setSize(300, 300);         getContentPane().setLayout(new BorderLayout());         GraphicsConfiguration config =           SimpleUniverse.getPreferredConfiguration();         Canvas3D canvas3D = new Canvas3D(config);         setSize(300, 300);         getContentPane().add(canvas3D);         return canvas3D;       }       private BranchGroup createSceneGraph() {         BranchGroup objRoot = new BranchGroup();         TransformGroup spinner = new TransformGroup();         spinner.setCapability(                  TransformGroup.ALLOW_TRANSFORM_WRITE);         objRoot.addChild(spinner);         spinner.addChild(new ColorCube(0.3));         spinner.addChild(makeSpin(spinner));         return objRoot;       }       private RotationInterpolator makeSpin(                               TransformGroup spinner) {         RotationInterpolator rotator =           new RotationInterpolator(new Alpha(-1, 3000),                                    spinner);         rotator.setTransformAxis(rotateCube());         BoundingSphere bounds = new BoundingSphere();         rotator.setSchedulingBounds(bounds);         return rotator;       }       private Transform3D rotateCube() {         rotate1.rotX(Math.PI / 4.0d);         rotate2.rotY(Math.PI / 3.0d);         rotate1.mul(rotate2);         return rotate1;       }       private void connect(Canvas3D canvas3D,                            BranchGroup scene) {         SimpleUniverse simpleU =                           new SimpleUniverse(canvas3D);         simpleU.getViewingPlatform().                           setNominalViewingTransform();         simpleU.addBranchGraph(scene);       }       public static void main(String[] args) {         new Spinning3DWorld().setVisible(true);       }    }

Spinning3DWorld를 컴파일하고 실행시키면Static3DWorld프로그램을 실행시켰을 때 보았던ColorCube과 같은 화면을 보게 되는데, 이번에는 입방체가 회전하고 있을 것이다. 회전중의 한 장면을 캡쳐한 그림이다.

사용자 삽입 이미지

마지막 예제Text3Dworld에서는, 회전하는ColorCube를 3차원 문자열로 대체한다.Text3D객체를 구성할 때에는 폰트로 작업할 때보다 좀 더 많은 디테일이 필요하기 때문에 약간 복잡하다고 할 수 있다. 하지만 상대적으로Shape3D객체의 구조와 재질을 지정해서 형태를 정의할 수 있기 때문에 선택의 폭이 넓다. 다음의 예는 대부분 디폴트 값을 이용했다.

private Shape3D createTextShape() {        Appearance textAppear = new Appearance();        textAppear.setMaterial(new Material());        Font3D font3D = new Font3D(new Font("Helvetica",                                         Font.PLAIN, 1),                                   new FontExtrusion());        Text3D textGeom = new Text3D(font3D,                             new String("Text3DWorld"));        textGeom.setAlignment(Text3D.ALIGN_CENTER);        Shape3D textShape = new Shape3D();        textShape.setGeometry(textGeom);        textShape.setAppearance(textAppear);        return textShape;   }

이 예가 이전의 예제와 비교해서 크게 다른점은 물체에 빛을 비춰야 한다는 것이다. 빛을 주지 않으면 회전하는 텍스트가 보이지 않을 것이다. 빛을 비추는 종류에는AmbientLight,DirectionalLightPointLight가 있는데 여기서는DirectionalLight를 사용한다. 사용자는 float의 벡터로서 빛의 방향을 명시하고, 이와 유사하게 0과 1사이 값으로 red, green, blue 세개의 float 값의 조합을 사용해서 반드시 색을 명시해줘야 한다. 물체에 하나 이상의 빛을 주고자 한다면, 빛의 값은 합쳐지겠지만 각각의 색은 1에 고정된다.

private void setLighting(TransformGroup objMove) {       DirectionalLight light =                                 new DirectionalLight();       light.setInfluencingBounds(                                 new BoundingSphere());       light.setDirection(                        new Vector3f(0.0f,0.0f,-1.0f));       light.setColor(                        new Color3f(0.0f, 1.0f, 1.0f));       objMove.addChild(light);   }

Text3DWorld의 코드이다.

import com.sun.j3d.utils.universe.SimpleUniverse;    import javax.media.j3d.BranchGroup;    import javax.media.j3d.Transform3D;    import javax.media.j3d.TransformGroup;    import javax.media.j3d.Canvas3D;    import javax.media.j3d.Alpha;    import javax.media.j3d.RotationInterpolator;    import javax.media.j3d.BoundingSphere;    import javax.media.j3d.Appearance;    import javax.media.j3d.Material;    import javax.media.j3d.Font3D;    import javax.media.j3d.FontExtrusion;    import javax.media.j3d.Text3D;    import javax.media.j3d.Shape3D;    import javax.media.j3d.DirectionalLight;    import javax.swing.JFrame;    import javax.vecmath.Vector3f;    import javax.vecmath.Color3f;    import java.awt.BorderLayout;    import java.awt.GraphicsConfiguration;    import java.awt.Font;    public class Text3DWorld extends JFrame {       private Transform3D rotate1 = new Transform3D();       private  Transform3D rotate2 = new Transform3D();       public Text3DWorld() {         super("Text3DWorld");         Canvas3D canvas3D = createCanvas3D();         BranchGroup scene = createSceneGraph();         connect(canvas3D, scene);       }       private Canvas3D createCanvas3D() {         setSize(300, 300);         getContentPane().setLayout(new BorderLayout());         GraphicsConfiguration config =           SimpleUniverse.getPreferredConfiguration();         Canvas3D canvas3D = new Canvas3D(config);         setSize(300, 300);         getContentPane().add(canvas3D);         return canvas3D;       }       public BranchGroup createSceneGraph() {         BranchGroup objRoot = new BranchGroup();         TransformGroup mover = moveTextBack();         TransformGroup spinner = createSpinner();         objRoot.addChild(mover);         mover.addChild(spinner);         spinner.addChild( createTextShape());         spinner.addChild(makeSpin(spinner));         setLighting(mover);         return objRoot;       }       private TransformGroup createSpinner() {         TransformGroup spinner = new TransformGroup();         spinner.setCapability(TransformGroup.                                 ALLOW_TRANSFORM_WRITE);         return spinner;       }       private TransformGroup moveTextBack() {         Transform3D transform3D = new Transform3D();         transform3D.setTranslation(                       new Vector3f(0.0f, 0.0f, -5.0f));         return new TransformGroup(transform3D);       }       private Shape3D createTextShape() {         Appearance textAppear = new Appearance();         textAppear.setMaterial(new Material());         Font3D font3D = new Font3D(                  new Font("Helvetica", Font.PLAIN, 1),                                   new FontExtrusion());         Text3D textGeom = new Text3D(font3D,                             new String("Text3DWorld"));         textGeom.setAlignment(Text3D.ALIGN_CENTER);         Shape3D textShape = new Shape3D();         textShape.setGeometry(textGeom);         textShape.setAppearance(textAppear);         return textShape;      }       private void setLighting(                               TransformGroup objMove) {         DirectionalLight light =                                 new DirectionalLight();        light.setInfluencingBounds(                                  new BoundingSphere());        light.setDirection(                         new Vector3f(0.0f,0.0f,-1.0f));        light.setColor(new Color3f(                                     0.0f, 1.0f, 1.0f));        objMove.addChild(light);       }       private RotationInterpolator makeSpin(                               TransformGroup spinner) {         RotationInterpolator rotator =           new RotationInterpolator(                          new Alpha(-1, 3000), spinner);            rotator.setTransformAxis(rotateCube());            BoundingSphere bounds =                                   new BoundingSphere();         rotator.setSchedulingBounds(bounds);         return rotator;       }       private Transform3D rotateCube() {         rotate1.rotX(Math.PI / 4.0d);         rotate2.rotY(Math.PI / 3.0d);         rotate1.mul(rotate2);         return rotate1;       }       private void connect(Canvas3D canvas3D,                            BranchGroup scene) {         SimpleUniverse simpleU =           new SimpleUniverse(canvas3D);         simpleU.getViewingPlatform().           setNominalViewingTransform();         simpleU.addBranchGraph(scene);       }       public static void main(String[] args) {         new Text3DWorld().setVisible(true);       }    }

Text3DWorld을 컴파일하고 실행하면, 회전하는 텍스트Text3DWorld을 볼 수 있다. 이 화면 또한 순간 포착한 것이다.

사용자 삽입 이미지

이 팁에서는 복잡성을 조금씩 추가시키며 3가지 예제를 실행해 보았다. 첫번째 예에서 3차원 물체를 만들고 디스플레이 했다면 두 번째에서는 물체를 계속해서 회전하도록 했고, 마지막에는 빛을 필요로 하는 물체를 회전시켰다. 이처럼 작은 모듈러 메소드들을 사용해서 복잡한 예제들을 쉽게 만들 수가 있다.

Java 3D API에 대한 자세한 정보는Java 3D API Tutorial를 참고한다..

사용자 삽입 이미지
사용자 삽입 이미지

TRANSFERHANDLER 사용하기

2003/3/18 Tech Tip "Dragging Text and Images with Swing"에서는 여러 스윙 컴포넌트간에 텍스트와 이미지들의 드래그와 드롭(drag&drop)이 가능하도록 프로그램에 이를 지원하는 방법을 소개했다. 하지만 지난 팁에서는 컴포넌트가 수락(accept)하는 것들과 드롭된 물체를 처리하는 방법을 이미 알고 있었다. 그렇지만 그러한 정보를 알고 있지 못하면 어떨까? 이 글이 후자의 케이스를 설명한다.

3월의 팁에서javax.swingTransferHandler클래스를 소개하면서TransferHandler객체와 관련된 모든 스윙 컴포넌트들은 자동적으로 드롭될 대상을 받을 수 있다고 했다. 그때는 디폴트TransferHandler객체를 사용했었다. 이번 팁에서는,TransferHandler객체를 사용자 정의하는데, 이 때, 어떤 타입의 객체가 컴포넌트에 드롭될 수 있는지 지정하고 드롭이 가능한 이벤트에서 충족되어야 할 것들을 명시해야 한다.

첫번째로 텍스트를 드래그하고 드롭할 수 있게끔 하는 Drop프로그램으로 이야기를 시작해 보자.DropJEditorPane를 포함하는JFrame를 생성하고, 이를 실행하면,JEditorPane에 텍스트를 입력할 수가 있다. 그리고 나면 텍스트를 선택하여 드래그하고 해제(release)하는 것이 가능해진다. 또한 다른 애플리케이션으로부터 텍스트를 선택해와서 텍스트 입력부분에 그것을 드래그할 수도 있다. 텍스트를 선택하고 드래그하면, 아이템을 드래그하고 있다는 것을 표시하는 친숙한 사각형 표시를 볼 수 있다. 또한 타겟 플러스 사인으로 드롭이 가능한 지역을 판단할 수도 있다.

import javax.swing.text.JTextComponent;   import javax.swing.JFrame;   import javax.swing.JEditorPane;   import javax.swing.JButton;   import java.awt.event.ActionListener;   import java.awt.event.ActionEvent;   import java.awt.BorderLayout;   class Drop extends JFrame {      private JTextComponent jText;      public Drop() {        super("Drag n Drop Demo");        setDefaultCloseOperation(EXIT_ON_CLOSE);        setSize(300, 400);        createTextArea();        createClearButton();      }      private void createTextArea() {        jText = new JEditorPane();        getContentPane().add(                           jText, BorderLayout.CENTER);      }      private void createClearButton() {        JButton clear = new JButton("Clear");        clear.addActionListener(new Clearer());        getContentPane().add(                            clear, BorderLayout.SOUTH);      }      private class Clearer implements        ActionListener {        public void actionPerformed(ActionEvent e) {          jText.setText("");        }      }      public static void main(String[] args) {        new Drop().setVisible(true);      }   }

자,JEditorPane에 파일을 드래그하고 드롭하는 것을 해보자.JeditorPane은 드롭된 파일을 수락하지 않기 때문에 이러한 속성(behavior)을 바꾸고자 한다면 customTransferHandler를 만들어야 한다. 만드는 방법은TransferHandler클래스에importData()와canImport()메소드를 오버라이드하는 것이다.canImport() 메소드는JComponent가 수락하는 것들을 보여주는 메소드인데, 가령, 다음과 같은 예에서는JComponentJComponent에 드롭된 모든 것들을 수락하고 있다.

public boolean canImport(JComponent c,                            DataFlavor[] flavors) {      return true;    }

importData() 메소드는 드롭에 대한 응답으로 동작이 실행되는 장소이고 주로JComponent에 드롭된Transferable객체와 상호작용한다.

다음은 사용자정의된TransferHandler,DropHandler이다.canImport()메소드는 컴포넌트에 대한 드롭의 수를 단순히 계산한다.

import javax.swing.TransferHandler;   import javax.swing.JComponent;   import javax.swing.text.JTextComponent;   import java.awt.datatransfer.Transferable;   import java.awt.datatransfer.DataFlavor;   class DropHandler extends TransferHandler {      private int numberOfDrops;      public boolean importData(JComponent component,                            Transferable transferable){        JTextComponent source =                             (JTextComponent) component;        source.setText(                    "Drop Number: " + ++numberOfDrops);        return true;      }      public boolean canImport(JComponent c,                               DataFlavor[] flavors) {        return true;      }   }

위의 예를 실행하려면, 대상 컴포넌트에TransferHandler를 연결해야 하는데, 모든JComponentssetTransferHandler()메소드를 통해서TransferHandler을 부를 수가 있다. 다음과 같이 이 호출을Drop.javacreateTextArea()메소드에 추가한다.

private void createTextArea() {        jText = new JEditorPane();        getContentPane().add(jText, BorderLayout.CENTER);        jText.setTransferHandler(new DropHandler());   }

이 예제를 실행하기 전에 2가지 중요한 사항을 기억해야 한다. 하나는 컴포넌트의 디폴트TransferHandler를 오버라이드하면, 디폴트 속성이 더이상 유지되지 않는다는 것이다. 예를 들어JEditorPaneString를 드롭하면, 예상하는대로String이 나타나지 않을 수 있다. 다른 하나는,canImport()을 항상 트루(true)로 리턴되도록 오버라이드 해놓았기 때문에,JEditorPane는 어떤 드롭이던지 수락한다는 것이다. 하지만 어디에서 드래그했느냐에 따라서, 소스 컴포넌트의 데이터를 잃을 수도 있다. 가령, 텍스트 에디터로부터 드래그한 데이더는 성공적으로 드롭을 하더라도 그 에디터로부터 삭제된다는 것이다.

TransferHandlerFile객체만 수락하도록 사용자정의 해보자. 이를 위해, 3월의 팁에서 소개된DataFlavors의 개념을 적용해 볼 수가 있다. 전송 중인 객체는 객체가 지원하는 모든DataFlavor객체들의 배열을 갖는다.canImport() 메소드를 다음과 같이 변경함으로써 지원을 받는DataFlavors중의 하나가File인지 아닌지를 테스트해 볼 수가 있다.

public boolean canImport(JComponent c,                               DataFlavor[] flavors) {         return Arrays.asList(flavors).          contains(DataFlavor.javaFileListFlavor);   }      public void drop(DropTargetDropEvent dtde) {           Transferable transferable =                        dtde.getTransferable();           if (transferable.isDataFlavorSupported(                     DataFlavor.javaFileListFlavor)) {             dtde.acceptDrop(DnDConstants.ACTION_COPY);             List files = null;             try {               files =                  (List) transferable.getTransferData(                        DataFlavor.javaFileListFlavor);             } //exceptions omitted here             for (int i = 0; i < files.size(); i++) {               File file = (File) files.get(i);               jText.setText(                        jText.getText() + "\n" + file);             }           }         }

DataFlavor객체의 배열을List로 변환해서 리스트가DataFlavor.javaFileListFlavor를 포함하고 있는지 여부를 알 수가 있다.JEditorPane에 드롭되는File을 처리하기 위해서는,importData()메소드 또한 변경해야 한다. 이 메소드를 이용하면 드롭되는 객체가File인지 아닌지를 알아낼 수가 있다. 만약 객체가File이 아니라면 false를 리턴하고,File이면 이를 처리할 수 있을 것이다. 적절한 예외처리를 해줘야 함은 물론이다.

파일을 다루는 실제적인 코드는importFiles()메소드이다. 다음의 예에서DropHandler의 업데이트된 버전인importFiles() 메소드는 드롭된File의 경로를 각각의 라인에 나타낸다.DropHandler는 다음과 같다.

import javax.swing.TransferHandler;   import javax.swing.JComponent;   import javax.swing.text.JTextComponent;   import java.util.List;   import java.util.Arrays;   import java.io.File;   import java.io.IOException;   import java.awt.datatransfer.Transferable;   import java.awt.datatransfer.DataFlavor;   import java.awt.datatransfer.UnsupportedFlavorException;   class DropHandler extends TransferHandler {      public boolean importData(JComponent component,                           Transferable transferable) {        if (!canImport(component,          transferable.getTransferDataFlavors())) {          return false;        } else {          JTextComponent source =                             (JTextComponent) component;          try {            importFiles(transferable, source);            return true;          } catch (UnsupportedFlavorException e) {            source.setText(e.getMessage());          } catch (IOException e) {            source.setText(e.getMessage());          }          return false;        }      }      private void importFiles(Transferable transferable,                               JTextComponent source)        throws UnsupportedFlavorException, IOException {        List files =          (List) transferable.getTransferData(            DataFlavor.javaFileListFlavor);        for (int i = 0; i < files.size(); i++) {          source.setText(source.getText() + "\n"           + (File)files.get(i));        }      }      public boolean canImport(JComponent c,                               DataFlavor[] flavors) {         return Arrays.asList(flavors).          contains(DataFlavor.javaFileListFlavor);      }   }

Drop를 실행하면,JEditorPane는 오직 파일만을 수락할 것이다. 사용자는 한번에 원하는 만큼의 파일을 드롭할 수 있고, 각각의 라인에 나타나는 경로를 볼 수 있다.

코드의 최종 버전은 제임스 고슬링의 java.net blog entry "URLs are your friend"를 따른다. 제임스 고슬링은 드래그와 드롭을 이용할 때 URL로 변환되도록 하는 것을 선호한다고 한다. 이 마지막 예에서,JEditorPane에 드롭된 파일들의 경로를 보여주는 대신, 파일로부터 구성된 URLs이 디스플레이 된다.

고슬링은 또한 몇몇 브라우저와 애플리케이션들은Files을 드롭하려고 할 때Strings을 드롭한다고 지적하고 있는데,DropHandler의 이번 버전은canImport()를 재계산하고, 헬퍼 메소드인canImportFiles() 과canImportStrings()을 도입함으로써Files또는Strings을 수락한다.

public boolean canImport(JComponent c,                             DataFlavor[] flavors) {      return canImportFiles(flavors) ||        canImportStrings(flavors);    }    private boolean canImportFiles(                             DataFlavor[] flavors) {      return Arrays.asList(flavors).        contains(DataFlavor.javaFileListFlavor);    }    private boolean canImportStrings(      DataFlavor[] flavors) {      return Arrays.asList(flavors).        contains(DataFlavor.stringFlavor);    }

업데이트된 아래의DropHandler에서는,importFiles() 메소드가 경로대신 URL을 디스플레이하도록 변경되었고,importStrings()메소드가 첨가되었다.

private void importStrings(Transferable transferable,                               JTextComponent source)      throws UnsupportedFlavorException, IOException {      String string =        (String) transferable.getTransferData(          DataFlavor.stringFlavor);      String message = null;      File file = new File(string);      if (file.exists()) {        message = file.toURL().toString();      } else {        try {          URL url = new URL(string);          url.getContent();          message = url.toString();        } catch (IOException e) {          message = "Could not convert string to URL "            + string;        }      }      source.setText(source.getText() + "\n" + message);    }

importStrings()메소드는String으로부터File을 구성하려 한다. 만약File이 존재하면 메소드는FiletoURL()메소드를 이용해서 URL을 구성하지만,File이 존재하지 않으면importStrings()는String으로부터 URL을 직접 구성하려고 할 것이다. 그러면 메소드는 URL에 첨부된 리소스가 있는지를 확인한다. 웹 브라우저로부터 이미지를 드래그했을 때,DropHandler는 이것이 가능할 경우에는 적절하게 행동하겠지만, 불가능할 경우에는 에러를 낼 것이다.

importData() 메소드는 단순화될 수도 있다. 이 메소드는 전송된 객체가File인지String인지를 테스트한 후, 적절한 메소드를 호출한다. 재작성된DropHandler를 보자.

import javax.swing.TransferHandler;   import javax.swing.JComponent;   import javax.swing.text.JTextComponent;   import java.util.List;   import java.util.Arrays;   import java.io.File;   import java.io.IOException;   import java.awt.datatransfer.Transferable;   import java.awt.datatransfer.DataFlavor;   import java.awt.datatransfer.UnsupportedFlavorException;   import java.net.URL;   class DropHandler extends TransferHandler {      public boolean importData(JComponent component,                           Transferable transferable) {        DataFlavor[] flavors =                 transferable.getTransferDataFlavors();        JTextComponent source =                             (JTextComponent) component;        try {          if (canImportFiles(flavors)) {            importFiles(transferable, source);          } else if (canImportStrings(flavors)) {            importStrings(transferable, source);          } else            return false;          return true;        } catch (UnsupportedFlavorException e) {          source.setText(e.getMessage());        } catch (IOException e) {          source.setText(e.getMessage());        }        return false;      }      private void importFiles(Transferable transferable,                               JTextComponent source)        throws UnsupportedFlavorException, IOException {        List files =          (List) transferable.getTransferData(            DataFlavor.javaFileListFlavor);        for (int i = 0; i < files.size(); i++) {          source.setText(source.getText() + "\n" +            ((File) (files.get(i))).toURL());        }      }      private void importStrings(                            Transferable transferable,                             JTextComponent source)        throws UnsupportedFlavorException, IOException {        String string =          (String) transferable.getTransferData(            DataFlavor.stringFlavor);        String message = null;        File file = new File(string);        if (file.exists()) {          message = file.toURL().toString();        } else {          try {            URL url = new URL(string);            url.getContent();            message = url.toString();          } catch (IOException e) {            message = "Could not convert string to URL "              + string;          }        }        source.setText(                    source.getText() + "\n" + message);      }      public boolean canImport(JComponent c,                               DataFlavor[] flavors) {        return canImportFiles(flavors) ||          canImportStrings(flavors);      }      private boolean canImportFiles(                                DataFlavor[] flavors) {        return Arrays.asList(flavors).          contains(DataFlavor.javaFileListFlavor);      }      private boolean canImportStrings(        DataFlavor[] flavors) {        return Arrays.asList(flavors).          contains(DataFlavor.stringFlavor);      }   }

이 팁은 일반적인JTextComponent를 위한 디폴트TransferHandler로 시작했다. 그리고는 제한적으로DataFlavor를 취할 수 있는TransferHandler를 보여주고,FilesStrings를 수락하기 위해TransferHandler를 변경하는 방법과 유용한 URLs를 생성하는 방법을 설명했다. 두 가지 예 모두에서canImport() 와importData() 메소드를 필요에 맞게 조정해야 한다.

TransferHandler와 데이터 전송에 대한 좀 더 자세한 정보는 자바 튜토리얼의 "How to Use Data Transfer"를 참고한다.

 

'Java' 카테고리의 다른 글

[Java3D] 3D 화면(scene)에 빛 효과 주기  (0) 2007.03.09
Singletons and lazy loading  (0) 2007.02.02
Effective Java Exceptions  (0) 2007.01.26