출처 :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
이다. 이 라인이 없으면 회전이 계산될 수는 있지만, 스크린에 나타나지는 않는다. 좀 전에,spinner
는objRoot
의 자녀객체로 추가되었고,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; }
newRotationInterpolator
는spinner
를 넘겨받기 위해 생성되었다. -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
,DirectionalLight
와PointLight
가 있는데 여기서는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프로그램으로 이야기를 시작해 보자.Drop
은JEditorPane
를 포함하는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
가 수락하는 것들을 보여주는 메소드인데, 가령, 다음과 같은 예에서는JComponent
가JComponent
에 드롭된 모든 것들을 수락하고 있다.
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
를 연결해야 하는데, 모든JComponents
는setTransferHandler
()메소드를 통해서TransferHandler
을 부를 수가 있다. 다음과 같이 이 호출을Drop.java
의createTextArea
()메소드에 추가한다.
private void createTextArea() { jText = new JEditorPane(); getContentPane().add(jText, BorderLayout.CENTER); jText.setTransferHandler(new DropHandler()); }
이 예제를 실행하기 전에 2가지 중요한 사항을 기억해야 한다. 하나는 컴포넌트의 디폴트TransferHandler
를 오버라이드하면, 디폴트 속성이 더이상 유지되지 않는다는 것이다. 예를 들어JEditorPane
에String
를 드롭하면, 예상하는대로String
이 나타나지 않을 수 있다. 다른 하나는,canImport
()을 항상 트루(true)로 리턴되도록 오버라이드 해놓았기 때문에,JEditorPane
는 어떤 드롭이던지 수락한다는 것이다. 하지만 어디에서 드래그했느냐에 따라서, 소스 컴포넌트의 데이터를 잃을 수도 있다. 가령, 텍스트 에디터로부터 드래그한 데이더는 성공적으로 드롭을 하더라도 그 에디터로부터 삭제된다는 것이다.
TransferHandler
를File
객체만 수락하도록 사용자정의 해보자. 이를 위해, 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
이 존재하면 메소드는File
의toURL
()메소드를 이용해서 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
를 보여주고,Files
와Strings
를 수락하기 위해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 |