반응형
< 원문: http://blog.sdnkorea.com/blog/683 >

기고인: JCO 양사열 부회장

필자는 작년부터 Web 2.0과 함께 부상한 RIA(Rich Internet Application)에 대해 귀가 따갑게 들어왔다. 필자가 현재 몸담고 있는 JCO내에서도 JCO가 직간접적으로 관여하는 각종 세미나 및 행사에서도 RIA는 큰 화두였으며 지금까지도 관심을 끄는 이슈로 부각되고 있다. 이런 이슈에 호응(?)하기 위해 Sun, Adobe, MS에서 JavaFX, Flex, Silverlight 라는 삼총사를 내놓았다. 세가지를 비교하는 것도 재미있는 일이겠지만 본인은 자바 프로그래머로만 10년 가까이 먹고 살았다. Silverlight는 어떤 것 이라는 것만 아는 편이고 Flex는 약간 끄적거려 보았다. 결국 가장 자신있는 Java 진영에서 나온 JavaFX 를 다루어 보기로 했다.

JavaFX를 처음 접한 것은 2007 JavaOne 이 끝난 후였다. 국내 유일의 자바 챔피언인 양수열 고문이(참고로 본인의 친형이다) JavaOne이 끝나고 귀국하고 만난 자리에서 JavaFX가 앞으로 엄청난 발전을 할 것이라고 흥분해서 말했던 기억이 있다. 그 후 몇 가지 관련된 글을 보았었고 2008년 JavaOne에 운이 좋게 참석하게 되어 다시 보게 된 JavaFX는 정말 놀라울 정도로 발전해 있었다. 예전에 간단한 Flash ActionScript 같았던 모습에서 Multimedia 쪽이 대폭 강화되어 수많은 동영상이 화면에 Display되면서 자유롭게 떠도는 Demo를 보고 그 놀라운 Performance에 놀라움을 금치 못했다. 그러면서도 ‘저 Demo가 일반적인 PC에서도 저만큼 가능할까?’ 라는 의문이 들었다. 그래서 결국 만들어 보게 되었으니…

시작하기
JavaFX를 처음 접하면서 느낀점은 JavaFX 문법이 JavaScript와 상당히 닮아 있다는 점이었다. JavaScript와 Java의 절묘한 짬뽕(?)이랄까? JavaFX Application을 물론 메모장으로도 개발할 수 있지만 IDE에 길들여진 개발자들이 메모장으로 개발할리가 없다. 그리고 JavaFX를 전면에 내세운 Sun에서도 개발자들이 편하고 능률적으로 개발할 수 있도록 NetBeans 최신버전에 JavaFX plugin을 추가해 놓았다. 이전에는 JavaFX Pad라는 응용 프로그램을 활용했었으나 좀 더 융통성있고 편하게 개발할 수 있도록 NetBeans에 통합해 놓은 것이다. JavaFX Pad에서와 같이 Source Edit 창에서 코드를 변경하면 위쪽 Preview 영역에서 바로 변경된 결과물을 볼 수 있고 오른쪽 팔레트를 이용해 각종 콤포넌트나 노드 이벤트 등을 원클릭으로 소스에 적용할 수 있다.
NetBeans JavaFX plugin 설치는 신상철 박사님이 운영하시는 JavaPassion을 참고하면 편하게 설정할 수 있다. (http://www.javapassion.com/handsonlabs/javafx_basics1/)

사용자 삽입 이미지
그림1: JavaFX plugin을 설치하고 실행중인 모습


본론으로
JavaFX를 이용해 Multimedia 파일을 컨트롤 하기 위해 필요한 정보와 샘플들을 우선 수집하였다. 일차적으로 Sample Code를 몇 개 받아 mp3 파일을 재생하는 테스트 코드를 만들었을 때 mp3 파일은 아주 잘 재생되었다. 하지만 동영상 파일로 바꾸었을 때는 아래와 같은 에러를 발생시켰다.

FX Media Object caught Exception com.sun.media.jmc.MediaUnsupportedException: Unsupported media: file:/D:/Projects/NetBeans/SimpleMovie/build/classes/simplemovie/SoHot.avi
source ='file:/D:/Projects/NetBeans/SimpleMovie/build/classes/simplemovie/SoHot.avi'


사용자 삽입 이미지
그림2 동영상 테스트시 발생한 에러메시지

Exception의 이름으로 인해 해당 동영상 파일이 FX 에서 지원하지 않는 Media 형식인걸 확인하였고 빠르게 손가락은 구글링을 하여 JavaFX가 지원하는 미디어 포멧을 찾아내었다.

JavaFX Media 지원타입
Container Types:
•  ASF (Advanced Systems Format),MPEG-1, AVI (Audio-Video Interleaved),WAVE, MIDI (Standard MIDI)

Encoding Types:
•  WMAUDIO8 (WindowsMedia Audio 2/7/8),WMSCREEN (Windows Media Screen), WMVIDEO9 (Windows Media Video 9), Voxware, MS MPEG-4, WMVIDEO7 (Windows Media Video 7), MPEG11 Video, MPEG-1 Audio, Uncompressed video (RGB/YUV/RLE), PCM, ADPCM, MP3, MIDI

Protocol Types:
•  HTTP, FILE ( Known not to work at present are media with DRM (Digital Rights Management), and media played directly from DVD or CD.)

Multimedia 파일을 컨트롤 하기 위해서는 항상 Codec이 문제인데 JavaFX에서는 생각보다 많은 종류의 Codec을 지원해주어 특별히 Codec에 신경 쓰지 않아도 되었다. (물론 가지고 있는 많은 수의 동영상은 실행되지 않았다.)

몇 가지 문제를 잡아나가면서 만들어 낸 결과물은 CustomNode를 사용해 재사용할 수 있는MediaViewNode를 만들었고 Timeline을 이용해 이 Node에 Animation 효과를 보여주는 프로그램을 만들어 보았다. 아래 소스를 실행하면 0.2 배율 스케일의 동영상 화면이 정지된 채로 실행되고 클릭시 회전하며 원래 비율로 커지고 실행되는 프로그램이다.

Main.fx

/*
* Main.fx
*
* Created on 2008. 11. 8, 오후 1:29:49
*/

package simplemovie;

import javafx.ext.swing.*;
import javafx.scene.*;
import javafx.scene.media.*;
import javafx.scene.effect.*;
import javafx.scene.paint.Color;

/**
* @author eclips
*/
SwingFrame {
title: "Movie Player"
width: 800
height: 600
closeAction: function() {
java.lang.System.exit( 0 );
}
visible: true

menus: [ ]

content: Canvas {
width:800
height:600
background: Color.WHITE
content: [
MediaViewNode {
// {__DIR__}은 클래스 파일이 있는 디렉토리이다
mediaURL: "{__DIR__}Nobody.avi"
viewX: 10
viewY: 10
}
]
}
}


MediaViewNode.fx

/*
* MediaViewNode.fx
*
* Created on 2008. 11. 8, 오후 6:48:47
*/

package simplemovie;

/**
* @author eclips
*/

import java.lang.System;

import javafx.scene.CustomNode;
import javafx.scene.*;
import javafx.input.*;
import javafx.scene.media.*;
import javafx.scene.effect.*;
import javafx.scene.paint.Color;
import javafx.scene.geometry.*;
import javafx.scene.transform.*;
import javafx.animation.Timeline;
import javafx.animation.KeyFrame;
import javafx.animation.Interpolator;

public class MediaViewNode extends CustomNode {
/**
* 동영상 화면의 X Sacle
*/
public attribute viewScaleX:Number = 0.2;

/**
* 동영상 화면의 Y Scale
*/
public attribute viewScaleY:Number = 0.2;

/**
* MediaViewNode의 회전 반경
*/
public attribute rotation:Number = 0.0;

public attribute viewX:Number = 50;
public attribute viewY:Number = 40;

/**
* 현재 활성화 여부
*/
private attribute actived:Boolean = false;
/**
* 현재 Animation 중인지 여부
*/
private attribute moving:Boolean = false;

private attribute media:Media;
private attribute mediaView:MediaView;

private attribute strokeColor:Color = Color.DARKGRAY;

// 동영상 미디어 객체 URL
public attribute mediaURL:String on replace {
media = Media {
source: mediaURL
};
}

// Media Player 객체
private attribute player =
MediaPlayer {
media: media,
autoPlay: false
}


private attribute choiceTimeLine =
Timeline {
keyFrames : [
KeyFrame {
time: 0ms
values: [
viewScaleX => 0.2,
viewScaleY => 0.2,
viewX => 10,
viewY => 10,
rotation => 0.0,
moving => true
]
},
KeyFrame {
time : 500ms
values: [
viewScaleX => 1.0 tween Interpolator.LINEAR,
viewScaleY => 1.0 tween Interpolator.LINEAR,
viewX => 40 tween Interpolator.LINEAR,
viewY => 40 tween Interpolator.LINEAR,
rotation => 360 tween Interpolator.LINEAR,
moving => false
]
action: function():Void {
player.play();
mediaView.toFront();
actived = true;
}
}
]
};

private attribute unchoiceTimeLine =
Timeline {
keyFrames : [
KeyFrame {
time: 0ms
values: [
viewScaleX => 1.0,
viewScaleY => 1.0,
viewX => 40,
viewY => 40,
rotation => 360,
moving => true
]
},
KeyFrame {
time : 500ms
values: [
viewScaleX => 0.2 tween Interpolator.LINEAR,
viewScaleY => 0.2 tween Interpolator.LINEAR,
viewX => 10 tween Interpolator.LINEAR,
viewY => 10 tween Interpolator.LINEAR,
rotation => 0.0 tween Interpolator.LINEAR,
moving => false
]
action: function():Void {
player.pause();
mediaView.toBack();
actived = false;
}
}
]
};

public function create():Node {
Group {
content: [
this.mediaView = MediaView {
mediaPlayer: player
scaleX: bind viewScaleX
scaleY: bind viewScaleY
translateX: bind viewX
translateY: bind viewY
transform: bind [Transform.rotate(rotation,200,150)]

onMouseEntered:
function(me:MouseEvent):Void {
strokeColor = Color.BLACK
}
onMouseExited:
function(me:MouseEvent):Void {
strokeColor = Color.DARKGRAY
}
onMouseClicked:
function(me:MouseEvent):Void {
System.out.println("clicked => " + actived);
if(moving == false) {
if(actived) {
unchoiceTimeLine.start();
System.out.println("inactive - " + viewScaleX);
}
else {
choiceTimeLine.start();
System.out.println("active - " + viewScaleX);
}
}
}
},
Rectangle {
width: 708
height: 472
scaleX: bind viewScaleX
scaleY: bind viewScaleY
translateX: bind viewX
translateY: bind viewY
stroke: strokeColor
strokeWidth: 10
transform: bind [Transform.rotate(rotation,200,150)]

onMouseEntered:
function(me:MouseEvent):Void {
strokeColor = Color.BLACK
}
onMouseExited:
function(me:MouseEvent):Void {
strokeColor = Color.DARKGRAY
}
}
]
}
}
}


실행 결과
사용자 삽입 이미지
그림3. 실행 결과
 

Clip을 클릭하면 해당 동영상 클립이 회전하면서 1:1 스케일로 커지면서 동영상이 실행된다.
 
사용자 삽입 이미지
그림4. 실행 결과


아쉬운 것들
-JavaFX Document의 부실함

작업을 위해 다운 받아 펼쳐본 Document는 한마디로 부실함이었다. 물론 아직 초기 버전이라 그런 것이라 생각되지만 현재의 Document는 그냥 어떤 것이 있다는 것을 설명하기 위해 만들어 놓은 수준이라고 봐야 할 것 같다. JDK SE 버전의 Document에 비하면 정말 완성도가 떨어졌다.
-NetBeans JavaFX plugin
미디어 이름에 한글이 들어가거나 공백이 있을 경우 실행시 에러가 발생
실시간으로 코딩시 에러 내용이 표시되는데 간혹 이런 에러 내용이 잘못 표시되는 문제
미디어의 이름을 바꿀 경우 간혹 미디어 적용이 안되 Rebuild 해야하는 불편함
소스에서 사용되는 클래스에 대해 자동으로 import 되지 않는 불편함
 
이런 문제들은 앞으로 JavaFX 1.0 정식 버전이 나오고 plugin이 업그레이드 되면서 점점 좋아질 것이라고 생각하지만 아직까지는 약간의 불편함을 감수하고 개발해야 한다.

JavaFX로 간단한 동영상 실행 Application을 만들면서 어떻게 이렇게 간단할 수 있지? 하는 생각이 들었다. 이해하기 쉬운 코드 몇 줄로 꽤 대단한 애니메이션 효과를 줄 수 있었고 동영상 플레이도 코드 몇 줄로 끝났다. 자바 Application으로 만들려 했으면 엄청난 소스 코드 속에서 헤메야 간신히 나올만한 프로그램을 JavaFX의 스크립트 몇 줄이 해낸것이다. 물론 이런 힘이 JavaFX 에만 있는 것이 아니다. JavaFX가 경쟁하고 있는 Flex나 Silverlight도 비슷한 수준으로 지원하고 있거나 더 월등히 앞선 기능과 툴을 지원하고 있기도 하다. 지금 현재도 간단한 Application은 만들기 너무 쉽지만 정식 버전이 나올 시점이 되면 지금보다 모든 면에서 더욱 편하고 강력해져야 할 거라고 생각한다. 위쪽에서 지적한대로 Document도 현재 JDK 만큼 완성도가 높아져야 하며 IDE의 발전과 JavaFX가 적용될 수 있는 Platform도 더욱 넓어진다면 다양한 분야에서 JavaFX 가 활약하는 것을 볼 수 있을거라 생각한다.

참조사이트
http://www.javapassion.com/javafx/
http://www.javafx.com
http://java.sun.com/javafx/index.jsp
http://openjfx.dev.java.net


글쓴이에 대해..
 


양사열

현재 JCO 부회장이며 프리랜서를 하고 있다. 주로 웹 프로젝트와 Database 동기화 솔루션을 개발했으며RIA 쪽에 관심을 갖고 있다. 10년째 개발자로 살면서 앞으로 남은 몇십년도 개발자로 살고 싶은 욕심을 갖고 있다.

반응형


이클립스를 에서 BUILD_PATH를 변경해서 사용하자.

- 왜?   자바 API 코드를 보고 싶으니까..

그리고 Java API Docs(도움말) 설정하는 방법도 알아보도록 하자.

-왜? 도움말을 한글 도움말로 설정도 할수 있고 외부라이브러리 도움말도 볼수 있게 할수 있으니까.

일단 설정을 위해 이클립스 프로젝트 구조를 보도록 하자.



일단 설정을 위해 주의깊게 봐야할 것은 JRE System Libray[jre1.6.0_03] 이부분이다.

일단 어느 부분때문에 설정을 하면 더 편리한지 이유나 알아보고 설정하자.



왼쪽은 Declaration 뷰의 모습이고 오른쪽에 떠있는 것은 Java API Docs (이하 도움말이라 칭하자) 의

출력 상태를 보여준것이다.

기본적으로 Java 에서 지원하는 클래스의 메소드의 도움말은 영문 도움말이 뜰것이고 Declaration은 볼수가 없다.

아.. 굳이 내가 영어가 약하기 때문에 한글로 된 도움말이 필요한게 아니다. ㅡㅡ;

단지 도움말이 한글로 떳으면 바랄 뿐이고 자바의 클래스나 메소드의 소스 코드를 볼수 있길 바랄 뿐이다.

원하는 메소드를 클릭하고 F3 을 눌러 소스코드를 한번 보라..

소스코드 대신 다른 것이 나온다. 소스코드를 찾을수 없다는 것이다.

소스코드를 보기 위해서는 먼저 플러그인을 추가해 줘야 한다.


방법1. 플러그인 추가.

<자바디컴파일러 추가부분 09년 1월 6일 >

http://www.pmguda.com/276 (Jad Decompiler 사용법)

이클립스 디컴파일러인 JAD를 설치하는 방법입니다.

이클립스로 개발을하다보면 F3버튼으로 열심히 따라가는 경우가생깁니다.

그러다 라이브러리로 묶여있는 클래스파일들을 로딩하게되면 읽긴읽되 내용을 분석하지못하죠~

그래서 디컴파일러 ~ 컴파일한클래스파일을 다시 자바파일로 보여주는 도구를 설치하게되면

클래스파일도 자바파일처럼 열수있게됩니다^^~

우선 jad.exe, jadclipse_3.1.0.jar를 다운로드 합니다.

jad.exe파일은 이클립스 폴더에 jadclipse_3.1.0.jar파일은 플러그인 폴더에 카피합니다.

window->preference->general->editors->file assosiationsd에서 *.class선택후

하단의 JadClipse Class File Viewr 를 선택후 default 를 선택합니다.

window->preference->java->JadClipse를 선택한후 ignore existing source를 체크해줍니다.

이렇게하시고 이클립스를 실행시키시면 *.class파일을 찾아갈경우 자동으로 디컴파일해줍니다~

http://sourceforge.net/projects/jadclipse

</자바디컴파일러>

방법2. JDK 설정

JRE System Libray[jre1.6.0_03] 이부분 문제인 것이다. JRE에는 소스코드가 포함되어 있지 않기 때문

Java Runtime Environment  이기 때문에 실행만을 위한 것이기 때문이다.

build path 를 JRE 가 아닌 JDK로 바꿔주면 된다. JRE 와 JDK 의 차이는 모두다 알고 있을꺼라 생각한다.^^

메뉴 항목에서 Window -> Preferences 를 선택한다.


Java -> Installed JREs

이클립스에서 인식하고 있는 JRE 목록이 출력된다.

나는 각 버전별로 설치가 되어있지만 나타나지 않는군.. Search로 찾아보자.

Java 가 설치된 경로를 찾아 지정해 준다.  그러면 지정한 위치에서 설치된 Java 목록을 추가해서 보여준다.


다 검색되서 나온다.. 여기서 JDK 버전을 원하는 것으로 선택하고 OK 하면 된다.

일단 여기까지가 F3키를 누르거나 Declaration 뷰를 통해서 소스코드를 볼수 있게 하는 설정방법이다.

이렇게 해도 소스코드가 보이지 않는다 하면 설정부분을 또 확인해 보자.


Explorer -> JRE System Libray -> rt.jar -> 마우스 오른쪽 클릭 -> Properties 선택

Location path에 설정한 JDK디렉토리에 가보면 src.zip 이라고 압축되어져 있는 소스 파일이 있다.

설정하도록 하자 기본적으로 PATH는 아래와 같다.

ex) C:\Program Files\Java\jdk1.6.0_10\src.zip  <-- 이런식으로 설정해주면 소스 내용을 볼수 있다. 

방법1은 라이브러리에 포함된 클래스 파일을 디컴파일해 보여주는 것이고
방법2는 JDK에 포함된 소스 코드를 보여주는 것이다. 

이젠 도움말이 한글로 뜨길 바랄 뿐이고.. ㅎㅎ 시작해보자.

이것 또한 Package Explorer(탐색창) 에 있는 JRE System Libray 를 보도록 하자.

JRE System Libray 옆 + 아이콘을 클릭하면 jar 파일들의 목록이 나온다.

여기서 rt.jar  runtime java를 나타내는 즉 jvm이 돌아갈 때 기본적인 자바 api를 담고 있는 파일이다.

Package Explorer -> JRE System Libray -> rt.jar -> 마우스 오른쪽 클릭 -> Properties 선택

Location path를 지워준다.
이런 이걸 지우면 Declaration 뷰에서 소스 코드가 보이지 않는다.
(방법1 플러그인 설치시) F3 을 이용하면 볼수 있으니 도움말을 한글로 보기 위해서 과감히 지워주도록 하자.


그리고 Javadoc Location 에 한글 api 경로를 입력해주자.
한글API가 파일시스템에 있다면 파일시스템의 경로를 입력하고 없다면 웹주소를 입력해주도록 하자.
(내가 사용하는 한글API URL이다. http://xrath.com/javase/ko/6/docs/ko/api/ )

JRE System Libray 설정은 위에서 하는 방법 외에도 프로젝트 단위로 설정할수도 있다.

이 부분은 앞에서 설명한 것과 유사하니 스샷만으로 감을 잡을수 있으리라 생각한다.

그리고 도움말 부분도 좀 더 응용하면 외부 라이브러리를 포함한 도움말도 Java Docs 경로를 설정하면 ㅎㅎ

해당 외부라이브러리의 JavaDoc Location 을 설정하자.



이제 이클립스 설정을 통해 더 편리해진 이클립스에서 Java 소스 코드와 도움말을 체험해 보도록 하자.

앞에서 설정했던 도움말 한글로 뜬다..떠 ㅋㅋ ^^;; 

F3키를 누르면 새로운 창이뜨고 Java 소스가 보인다 보여.. ㅎㅎ  열공되겠넹.. ㅎㅎ



이렇게 해서 이클립스에서 자바 API 도움말을 한글로 볼수 있게 하는 방법과
 
자바 API 소스를 볼수 있게 되었다.

정리를 해보자면 Explorer -> JRE System Libray -> rt.jar -> 마우스 오른쪽 클릭 -> Properties 에서

Java Source Attachment 항목에  src.zip 경로설정하면 자바소스를 볼수가 있고

Javadoc Location 항목에서는 자바 API 문서에 대한 설정을 할수 있다.

여기서 유의 할것은 한글 api 문서를 보기 위해서는 Java Source Attachment  항목을 공백으로 해야 된다는 것이다.

양자택일을 해야 한다는 말이 되는것이다. 소스를 볼수 있게 하던지 아니면 한글 API를 보던지...

둘다 택한다면 디컴파일러를 활용하는 방법1을 사용하도록 하자.

외부 라이브러리의 API 도움말도 설정 할수 있다는 것을 알게 되었으니 아주 편리하게 사용할 수 있다는것 ㅎㅎ

스샷을 남발하느라..글이 길어지고.. 장황해지고 .. 또 글재주가 없다보니 참 도움이 될런지 ㅠㅠ.

<참고 : http://byeonely.tistory.com/entry/이클립스에서-Java-API-Docs-도움말-한글화 , http://blog.kfmes.com/207 >

반응형

반응형

Java Swing Events

Events are an important part in any GUI program. All GUI applications are event-driven. An application reacts to different event types which are generated during it's life. Events are generated mainly by the user of an application. But they can be generated by other means as well. e.g. internet connection, window manager, timer. In the event model, there are three participants:

  • event source
  • event object
  • event listener

The Event source is the object whose state changes. It generates Events. The Event object (Event) encapsulates the state changes in the event source. The Event listener is the object that wants to be notified. Event source object delegates the task of handling an event to the event listener.

Event handling in Java Swing toolkit is very powerful and flexible. Java uses Event Delegation Model. We specify the objects that are to be notified when a specific event occurs.

An event object

When something happens in the application, an event object is created. For example, when we click on the button or select an item from list. There are several types of events. An ActionEvent, TextEvent, FocusEvent, ComponentEvent etc. Each of them is created under specific conditions.

Event object has information about an event, that has happened. In the next example, we will analyze an ActionEvent in more detail.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.text.DateFormat;

import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;


public class EventObject extends JFrame {


    private JList list;
    private DefaultListModel model;

    public EventObject() {

        setTitle("Event Object");

        JPanel panel = new JPanel();
        panel.setLayout(null);

        model = new DefaultListModel();
        list = new JList(model);
        list.setBounds(150, 30, 220, 150);

        JButton ok = new JButton("Ok");
        ok.setBounds(30, 35, 80, 25);

        ok.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {

                Calendar cal = Calendar.getInstance();
                cal.setTimeInMillis(event.getWhen());
                Locale locale = Locale.getDefault();
                Date date = new Date();
                String s = DateFormat.getTimeInstance(DateFormat.SHORT,
                    locale).format(date);

                if ( !model.isEmpty() )
                    model.clear();

                if (event.getID() == ActionEvent.ACTION_PERFORMED)
                    model.addElement(" Event Id: ACTION_PERFORMED");

                model.addElement(" Time: " + s);

                String source = event.getSource().getClass().getName();
                model.addElement(" Source: " + source);

                int mod = event.getModifiers();

                StringBuffer buffer = new StringBuffer(" Modifiers: ");

                if ((mod & ActionEvent.ALT_MASK) > 0)
                    buffer.append("Alt ");

                if ((mod & ActionEvent.SHIFT_MASK) > 0)
                    buffer.append("Shift ");

                if ((mod & ActionEvent.META_MASK) > 0)
                    buffer.append("Meta ");

                if ((mod & ActionEvent.CTRL_MASK) > 0)
                    buffer.append("Ctrl ");

                model.addElement(buffer);
            }
        });

        panel.add(ok);
        panel.add(list);
        add(panel);

        setSize(420, 250);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    public static void main(String[] args) {
        new EventObject();
    }
}

The code example shows a button and a list. If we click on the button, information about the event is displayed in the list. In our case, we are talking about an ActionEvent class. The data will be the time, when the event occured, the id of the event, the event source and the modifier keys.

 public void actionPerformed(ActionEvent event) {

Inside the action listener, we have an event parameter. It is the instance of the event, that has occured. In our case it is an ActionEvent.

 cal.setTimeInMillis(event.getWhen());

Here we get the time, when the event occured. The method returns time value in milliseconds. So we must format it appropriately.

 String source = event.getSource().getClass().getName();
 model.addElement(" Source: " + source);

Here we add the name of the source of the event to the list. In our case the source is a JButton.

 int mod = event.getModifiers();

We get the modifier keys. It is a bitwise-or of the modifier constants.

 if ((mod & ActionEvent.SHIFT_MASK) > 0)
     buffer.append("Shift ");

Here we determine, whether we have pressed a Shift key.


Event Object
Figure: Event Object

Implementation

There are several ways, how we can implement event handling in Java Swing toolkit.

  • Anonymous inner class
  • Inner class
  • Derived class

Anonymous inner class

We will illustrate these concepts on a simple event example.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;


public class SimpleEvent extends JFrame {


    public SimpleEvent() {

        setTitle("Simle Event");

        JPanel panel = new JPanel();
        panel.setLayout(null);

        JButton close = new JButton("Close");
        close.setBounds(40, 50, 80, 25);

        close.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                System.exit(0);
            }

        });

        panel.add(close);
        add(panel);

        setSize(300, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    public static void main(String[] args) {
        new SimpleEvent();
    }
}

In this example, we have a button that closes the window upon clicking.

 JButton close = new JButton("Close");

The button is the event source. It will generate events.

 close.addActionListener(new ActionListener() {
     public void actionPerformed(ActionEvent event) {
         System.exit(0);
     }
 });

Here we register an action listener with the button. This way, the events are sent to the event target. The event target in our case is ActionListener class. In this code, we use an anonymous inner class.

Inner class

Here we implement the example using an inner ActionListener class.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;


public class InnerClass extends JFrame {

    public InnerClass() {

        setTitle("Using inner class");

        JPanel panel = new JPanel();
        panel.setLayout(null);

        JButton close = new JButton("Close");
        close.setBounds(40, 50, 80, 25);

        ButtonListener listener = new ButtonListener();
        close.addActionListener(listener); 

        panel.add(close);
        add(panel);

        setSize(300, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    class ButtonListener implements ActionListener {
        public void actionPerformed(ActionEvent e)
        {
            System.exit(0);
        }
      }

    public static void main(String[] args) {
        new InnerClass();
    }
}
 ButtonListener listener = new ButtonListener();
 close.addActionListener(listener); 

Here we have a non anonymous inner class.

 class ButtonListener implements ActionListener {
    public void actionPerformed(ActionEvent e)
    {
        System.exit(0);
    }
 }

The button listener is defined here.

A derived class implementing the listener

The following example will derive a class from a component and implement an action listener inside the class.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;


public class UsingInterface extends JFrame {


    public UsingInterface() {

        setTitle("Using inner class");

        JPanel panel = new JPanel();
        panel.setLayout(null);

        MyButton close = new MyButton("Close");
        close.setBounds(40, 50, 80, 25);

        panel.add(close);
        add(panel);

        setSize(300, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    class MyButton extends JButton implements ActionListener {

        public MyButton(String text) {
            super.setText(text);
            addActionListener(this);
        }

        public void actionPerformed(ActionEvent e)
        {
            System.exit(0);
        }

    }

    public static void main(String[] args) {
        new UsingInterface();
    }
}

In this example, we create a MyButton class, which will implement the action listener.

 MyButton close = new MyButton("Close");

Here we create the MyButton custom class.

 class MyButton extends JButton implements ActionListener {

The MyButton class is extended from the JButton class. It implements the ActionListener interface. This way, the event handling is managed within the MyButton class.

 addActionListener(this);

Here we add the action listener to the MyButton class.

Multiple sources

A listener can be plugged into several sources. This will be explained in the next example.

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EtchedBorder;


public class MultipleSources extends JFrame {

    JLabel statusbar;

    public MultipleSources() {

        setTitle("Multiple Sources");
        JPanel panel = new JPanel();
        statusbar = new JLabel(" ZetCode");

        statusbar.setBorder(BorderFactory.createEtchedBorder(
                EtchedBorder.RAISED));

        panel.setLayout(null);

        JButton close = new JButton("Close");
        close.setBounds(40, 30, 80, 25);
        close.addActionListener(new ButtonListener());

        JButton open = new JButton("Open");
        open.setBounds(40, 80, 80, 25);
        open.addActionListener(new ButtonListener());

        JButton find = new JButton("Find");
        find.setBounds(40, 130, 80, 25);
        find.addActionListener(new ButtonListener());

        JButton save = new JButton("Save");
        save.setBounds(40, 180, 80, 25);
        save.addActionListener(new ButtonListener());

        panel.add(close);
        panel.add(open);
        panel.add(find);
        panel.add(save);

        add(panel);
        add(statusbar, BorderLayout.SOUTH);

        setSize(400, 300);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    class ButtonListener implements ActionListener {
        public void actionPerformed(ActionEvent e)
        {
            JButton o = (JButton) e.getSource();
            String label = o.getText();
            statusbar.setText(" " + label + " button clicked");
        }
      }


    public static void main(String[] args) {
        new MultipleSources();
    }
}

We create four buttons and a statusbar. The statusbar will display an informative message upon clicking on the button.

 close.addActionListener(new ButtonListener());
 ...
 open.addActionListener(new ButtonListener());
 ...

Each button will be registered against a ButtonListener class.

 JButton o = (JButton) e.getSource();
 String label = o.getText();

Here we determine, which button was pressed.

 statusbar.setText(" " + label + " button clicked")

We update the statusbar.


Multiple Sources
Figure: Multiple Sources

Multiple listeners

We can register several listeners for one event.

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.util.Calendar;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.border.EtchedBorder;


public class MultipleListeners extends JFrame {

    private JLabel statusbar;
    private JSpinner spinner;
    private static int count = 0;

    public MultipleListeners() {

        setTitle("Multiple Listeners");
        JPanel panel = new JPanel();
        statusbar = new JLabel("0");

        statusbar.setBorder(BorderFactory.createEtchedBorder(
                EtchedBorder.RAISED));

        panel.setLayout(null);

        JButton add = new JButton("+");
        add.setBounds(40, 30, 80, 25);
        add.addActionListener(new ButtonListener1());
        add.addActionListener(new ButtonListener2());

        Calendar calendar = Calendar.getInstance();
        int currentYear = calendar.get(Calendar.YEAR);

        SpinnerModel yearModel = new SpinnerNumberModel(currentYear,
                                       currentYear - 100,
                                       currentYear + 100,
                                       1);

        spinner = new JSpinner(yearModel);
        spinner.setEditor(new JSpinner.NumberEditor(spinner, "#"));

        spinner.setBounds(190, 30, 80, 25);

        panel.add(add);
        panel.add(spinner);

        add(panel);
        add(statusbar, BorderLayout.SOUTH);

        setSize(300, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    class ButtonListener1 implements ActionListener {
        public void actionPerformed(ActionEvent e)
        {
            Integer val = (Integer) spinner.getValue();
            spinner.setValue(++val);
        }
      }

    class ButtonListener2 implements ActionListener {
        public void actionPerformed(ActionEvent e)
        {
            statusbar.setText(Integer.toString(++count));
        }
      }


    public static void main(String[] args) {
        new MultipleListeners();
    }
}

In this example, we have a button, spinner and a statusbar. We use two button listeners for one event. One click of a button will add one year to the spinner component and update the statusbar. The statusbar will show, how many times we have clicked on the button.

 add.addActionListener(new ButtonListener1());
 add.addActionListener(new ButtonListener2());

We register two button listeners.

 SpinnerModel yearModel = new SpinnerNumberModel(currentYear,
                              currentYear - 100,
                              currentYear + 100,
                              1);
 spinner = new JSpinner(yearModel);

Here we create the spinner component. We use a year model for the spinner. The SpinnerNumberModel arguments are initial value, min, max values and the step.

 spinner.setEditor(new JSpinner.NumberEditor(spinner, "#"));

We remove the thousands separator.

 Integer val = (Integer) spinner.getValue();
 spinner.setValue(++val);

Here we increase the year number.


Multiple Listeners
Figure: Multiple Listeners

Removing listeners

The Java Swing toolkit enables us to remove the registered listeners.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;


public class RemoveListener extends JFrame {

    private JLabel text;
    private JButton add;
    private JCheckBox active;
    private ButtonListener buttonlistener;
    private static int count = 0;

    public RemoveListener() {

        setTitle("Remove listener");
        JPanel panel = new JPanel();

        panel.setLayout(null);

        add = new JButton("+");
        add.setBounds(40, 30, 80, 25);
        buttonlistener = new ButtonListener();

        active = new JCheckBox("Active listener");
        active.setBounds(160, 30, 140, 25);

        active.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent event) {
                if (active.isSelected()) {
                  add.addActionListener(buttonlistener);}
                else {
                  add.removeActionListener(buttonlistener);
                }
            }
        });

        text = new JLabel("0");
        text.setBounds(40, 80, 80, 25);

        panel.add(add);
        panel.add(active);
        panel.add(text);

        add(panel);

        setSize(310, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    class ButtonListener implements ActionListener {
        public void actionPerformed(ActionEvent e)
        {
            text.setText(Integer.toString(++count));
        }
      }

    public static void main(String[] args) {
        new RemoveListener();
    }
}

We have three components on the panel. A button, check box and a label. By toggling the check box, we add or remove the listener for a button.

 buttonlistener = new ButtonListener();

We have to create a non anonymous listener, if we want to later remove it. We need a reference to it.

 if (active.isSelected()) {
     add.addActionListener(buttonlistener);}
 else {
     add.removeActionListener(buttonlistener);
 }

We determine, whether the check box is selected. Then we add or remove the listener.


Remove listener
Figure: Remove listener

Moving a window

The following example will look for a position of a window on the screen.

import java.awt.Font;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;


public class MovingWindow extends JFrame implements ComponentListener {

    private JLabel labelx;
    private JLabel labely;

    public MovingWindow() {

        setTitle("Moving window");

        JPanel panel = new JPanel();
        panel.setLayout(null);

        labelx = new JLabel("x: ");
        labelx.setFont(new Font("Serif", Font.BOLD, 14));
        labelx.setBounds(20, 20, 60, 25);

        labely = new JLabel("y: ");
        labely.setFont(new Font("Serif", Font.BOLD, 14));
        labely.setBounds(20, 45, 60, 25);

        panel.add(labelx);
        panel.add(labely);

        add(panel);

        addComponentListener(this);

        setSize(310, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    public void componentResized(ComponentEvent e) {
    }

    public void componentMoved(ComponentEvent e) {
        int x = e.getComponent().getX();
        int y = e.getComponent().getY();
        labelx.setText("x: " + x);
        labely.setText("y: " + y);
    }

    public void componentShown(ComponentEvent e) {
    }

    public void componentHidden(ComponentEvent e) {
    }


    public static void main(String[] args) {
        new MovingWindow();
    }
}

The example shows the current window coordinates on the panel. To get the window position, we use the ComponentListener

 labelx.setFont(new Font("Serif", Font.BOLD, 14));

We make the font bigger, the default one is a bit small.

 int x = e.getComponent().getX();
 int y = e.getComponent().getY();

Here we get the x and the y positions.

Notice, that we have to implement all four methods, that are available in the ComponentListener. Even, if we do not use them.


Moving a window
Figure: Moving a window

Adapters

Adapters are convenient classes. In the previous code example, we had to implement all four methods of a ComponentListener class. Even if we did not use them. To avoid unnecessary coding, we can use adapters. Adapter is a class that implements all necessary methods. They are empty. We then use only those methods, that we actually need. There is no adapter for a button click event. Because there we have only one method to implement. The actionPerformed() method. We can use adapters in situations, where we have more than one method to implement.

The following example is a rewrite of the previous one, using a ComponentAdapter.

import java.awt.Font;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;


public class Adapter extends JFrame {

    private JLabel labelx;
    private JLabel labely;

    public Adapter() {

        setTitle("Adapter");

        JPanel panel = new JPanel();
        panel.setLayout(null);

        labelx = new JLabel("x: ");
        labelx.setFont(new Font("Serif", Font.BOLD, 14));
        labelx.setBounds(20, 20, 60, 25);

        labely = new JLabel("y: ");
        labely.setFont(new Font("Serif", Font.BOLD, 14));
        labely.setBounds(20, 45, 60, 25);


        panel.add(labelx);
        panel.add(labely);

        add(panel);
        addComponentListener(new MoveAdapter());

        setSize(310, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }


    class MoveAdapter extends ComponentAdapter {
      public void componentMoved(ComponentEvent e) {
          int x = e.getComponent().getX();
          int y = e.getComponent().getY();
          labelx.setText("x: " + x);
          labely.setText("y: " + y);
      }
     }

    public static void main(String[] args) {
        new Adapter();
    }
}

This example is a rewrite of the previous one. Here we use the ComponentAdapter.

 addComponentListener(new MoveAdapter());

Here we register the component listener.

 class MoveAdapter extends ComponentAdapter {
     public void componentMoved(ComponentEvent e) {
         int x = e.getComponent().getX();
	 int y = e.getComponent().getY();
	 labelx.setText("x: " + x);
         labely.setText("y: " + y);
     }
 }

Inside the MoveAdapter inner class, we define the componentMoved() method. All the other methods are left empty.

반응형

Substance look and feel - getting started

This document describes the steps to create a sample Swing application and run it under Substance look and feel.

Since Substance requires JDK 6.0 or higher, if you don't have such an installation on your machine, you need to download it from this site and install it. The command-prompt examples below assume that java executable is in the path. This executable is part of JRE (under bin folder) as well as part of JDK (under bin folder). Consult your OS manual on how to add the relevant folder to the path. Alternatively, you can put the entire path to the java executable in the scripts below.

After you have JDK 6.0+ installed on your machine, create the following Walkthrough.java class:

public class Walkthrough extends JFrame {
  public Walkthrough() {
    super("Sample app");
    this.setLayout(new FlowLayout());
    this.add(new JButton("button"));
    this.add(new JCheckBox("check"));
    this.add(new JLabel("label"));

    this.setSize(new Dimension(25080));
    this.setLocationRelativeTo(null);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  }

  public static void main(String[] args) {
    JFrame.setDefaultLookAndFeelDecorated(true);

    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        Walkthrough w = new Walkthrough();
        w.setVisible(true);
      }
    });
  }
}

This is a simple frame (that does nothing) with a button, a checkbox and a label. You can create this class in your favourite IDE or in any text editor. Once this class is created, compile it. If you're using an IDE, consult the IDE help on the compilation process. If you're using a simple text editor, you can compile this class by using:

javac Walktrough.java

If you have problems, consult the online help for javac compiler. The compiled Walkthrough.class will be created in the same folder as your Walkthrough.java. In order to run it, use:

java -cp . Walkthrough

You will see the following frame under the default Ocean look and feel:

In order to run the same frame under Substance look and feel, you first need to choose the Substance skin that you would like to use (see the links at the end of this document). Suppose that you choose the Business skin. Now you have the following options:

  • Start your VM with -Dswing.defaultlaf=org.jvnet.substance.skin.SubstanceBusinessLookAndFeel
  • UIManager.setLookAndFeel(new SubstanceBusinessLookAndFeel())
  • UIManager.setLookAndFeel("org.jvnet.substance.skin.SubstanceBusinessLookAndFeel");

The first option doesn't require any code changes in the application above. Run the following script:

java -Dswing.defaultlaf=org.jvnet.substance.skin.SubstanceBusinessLookAndFeel -cp . Walkthrough

You will see the following exception:

Exception in thread "AWT-EventQueue-0" java.lang.Error: Cannot load org.jvnet.substance.skin.SubstanceBusinessLookAndFeel
	at javax.swing.UIManager.initializeDefaultLAF(UIManager.java:1345)
	at javax.swing.UIManager.initialize(UIManager.java:1432)
	at javax.swing.UIManager.maybeInitialize(UIManager.java:1420)
	at javax.swing.UIManager.getUI(UIManager.java:1007)
	at javax.swing.JPanel.updateUI(JPanel.java:109)
	at javax.swing.JPanel.(JPanel.java:69)
	at javax.swing.JPanel.(JPanel.java:92)
	at javax.swing.JPanel.(JPanel.java:100)
	at javax.swing.JRootPane.createGlassPane(JRootPane.java:527)
	at javax.swing.JRootPane.(JRootPane.java:347)
	at javax.swing.JFrame.createRootPane(JFrame.java:260)
	at javax.swing.JFrame.frameInit(JFrame.java:241)
	at javax.swing.JFrame.(JFrame.java:208)
	at Walkthrough.(Walkthrough.java:10)
	at Walkthrough$1.run(Walkthrough.java:25)
	at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:284)
	at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
	at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)

This means that the org.jvnet.substance.skin.SubstanceBusinessLookAndFeel class in not found in the classpath. This class is located in the substance.jar that you need to download from the Documents & Files section of the Substance project page. For this example, we assume that the substance.jar is located under C:/temp folder. In order to run the frame under Substance, use the following script:

java -Dswing.defaultlaf=org.jvnet.substance.skin.SubstanceBusinessLookAndFeel -cp .;C:/temp/substance.jar Walkthrough

The result is the same frame under Substance look and feel:

The other two options for setting Substance require changing the code. Go back to your Java editor and replace the main() method by:

  public static void main(String[] args) {
    JFrame.setDefaultLookAndFeelDecorated(true);
    try {
      UIManager.setLookAndFeel(new SubstanceRavenGraphiteLookAndFeel());
    catch (Exception e) {
      System.out.println("Substance Raven Graphite failed to initialize");
    }
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        Walkthrough w = new Walkthrough();
        w.setVisible(true);
      }
    });
  }

Note that here we are using another Substance skin, Raven Graphite. In order to compile the new Walkthrough.java, you need to add the substance.jar to the build path. Consult your IDE help if you're using IDE. For command-prompt compilation, use the additional -cp flag:

javac -cp c:/temp/substance.jar Walktrough.java

Now you can run your application without the -Dswing.defaultlaf JVM flag, but you still need to specify the location of the substance.jar as before:

java -cp .;C:/temp/substance.jar Walkthrough

If you don't want to create an explicit dependency on the Substance classes in your code, change your main() method to:

  public static void main(String[] args) {
    JFrame.setDefaultLookAndFeelDecorated(true);
    try {
      UIManager.setLookAndFeel("org.jvnet.substance.skin.SubstanceRavenGraphiteLookAndFeel");
    catch (Exception e) {
      System.out.println("Substance Raven Graphite failed to initialize");
    }
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        Walkthrough w = new Walkthrough();
        w.setVisible(true);
      }
    });
  }

You can run the application the same way as before. Here is how it looks like:

Where to go from here?

  • Read the FAQ
  • Read about the VM flags
  • Read about the client properties
  • Read about the API
  • Read about using skins
  • Download the sources and study the test application in test/Check.java
  • Read the Javadocs of SubstanceLookAndFeel class.

<출처 : https://substance.dev.java.net/docs/getting-started.html >

반응형

Sun Developer Network 공식 블로그 <출처 : http://blog.sdnkorea.com/blog/239 >

대부분의 경우 개발자들은 자바 애플리케이션이 배포된 플랫폼에 딱 맞는 애플리케이션처럼 보이기를 원한다. Mac OS X와 같은 몇몇 플랫폼에서는 적절한 룩앤필이 디폴트값으로 정해지는 반면에 윈도우와 같은 플랫폼은 특정 룩앤필(look and feel)을 설정하기 위해서 다음과 같은 호출이 필요하다.

   UIManager.setLookAndFeel(
            UIManager.getSystemLookAndFeelClassName());

이번 테크팁에서는 탑재된 룩앤필을 플랫폼에 맞도록 결정하게 될 것이다. 새로이 이용 가능한 GTK+ 룩앤필을 추가하고, 라디오 버튼을 클릭해서 룩앤필을 바꿀 수 있는 애플리케이션을 만들게 될 것이다. 마지막으로, 애플리케이션에 사용자의 회사 마케팅 테마에 맞는 룩앤필을 적용하기 위해서 크로스 플랫폼 룩앤필을 사용자 정의하는 방법을 배우도록 하겠다.

인스톨된 룩앤필의 리스트를 생성하자. 정적 메소드 getInstalledLookAndFeels()를 호출하면 LookAndFeelInfo 타입의 객체의 배열을 리턴하게 된다. 인스톨된 LookAndFeels의 이름들을 배열로 얻어내려면 getName()를 이용하고, 구현된 각각의 클래스 이름을 알아내기 위해서는 getClassName()를 사용한다. 인스톨된 LookAndFeels의 이름을 배열로 디스플레이하는 다음 프로그램을 실행해 보자.

   import javax.swing.UIManager;

   public class AvailableLaF {
      public static void main(String[] args) {
        UIManager.LookAndFeelInfo[] installed =
          UIManager.getInstalledLookAndFeels();
        for (int i = 0; i < installed.length; i++) {
          System.out.println(installed[i].getName());
        }
        System.out.println(
          "\nThe current look and feel is "
          + UIManager.getLookAndFeel().getName());
      }
   }

솔라리스 운영환경이나 윈도우 2000에서 실행시켰다면 다음과 같은 출력 값을 보게 된다.

   Metal
   CDE/Motif
   Windows
   
   The current look and feel is Metal

Panther가 실행되는 Mac에서의 출력 값은 다음과 같다.

   Mac OS X
   Metal
   CDE/Motif

   The current look and feel is Mac OS X Aqua

J2SE 1.4.2에서는 GTK+ 2.0에 기반한 크로스 플랫폼 룩앤필도 존재한다. 크로스 플랫폼이 인스톨된 룩앤필의 리스트에 나타나지 않더라도, 이용 가능하다면 이를 인스톨할 수 있다. 크로스 플랫폼 룩앤필을 인스톨하기 위해서는 다음과 같은 메소드가 필요하다.

   private void installGTK() {
      try {
        String GTK =
          "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
        UIManager.setLookAndFeel(GTK);
        UIManager.installLookAndFeel("GTK", GTK);
      } catch (Exception e) {
        System.err.println("Could not install GTK");
      }
    }

GTK는 솔라리스나 Mac OS X와 같은 시스템에서 이용 가능하다. 윈도우에서는 "Could not install GTK" 과 같은 메시지를 보게 될 것이다. GTK를 지원하는 시스템에서 스윙 기반 프로그램을 실행하고자 한다면 커맨드 라인에서 룩앤필을 설정할 수도 있다. 다음은 룩앤필을 설정하는 커맨드 라인이다.

   java -Dswing.defaultlaf=
     com.sun.java.swing.plaf.gtk.GTKLookAndFeel 
     

이하의 프로그램 ChangingLaF는 라디오 버튼을 이용해서 룩앤필을 바꿀 수 있게 해준다. 만약 사용자의 시스템에서 GTK가 이용 가능하다면, 프로그램은 인스톨된 룩앤필의 리스트에 GTK를 추가하게 될 것이다. 그리고 나서 프로그램은 각각의 룩앤필을 위해 라디오 버튼을 생성하게 된다. 프로그램을 실행하고 라디오 버튼을 클릭하면 사용자 인터페이스는 지정한 룩앤필로 바뀌게 된다. 프로그램은 이너클래스의 actionPerformed() 메소드에 대한 응답으로 이를 실행하게 되는 것이다. 다음과 같은 2개의 과정이 수행된다.

   UIManager.setLookAndFeel(getText());
   SwingUtilities.updateComponentTreeUI(
     ChangingLaF.this);

룩앤필은 선택된 라디오 버튼의 텍스트 라벨로 바뀐다. 프로그램은 루트에 주어진 컴포넌트를 이용해서 트리를 새로 그리기 위해 updateComponentTreeUI()를 호출한다. updateComponentTreeUI()JFrame의 인스턴스인 객체를 호출하게 되는데, 이 때 JFrame 은 클래스 이름 뒤에 this가 붙은 형태로 식별된다. 위의 예제에서는, outer 클래스가 ChangingLaF이기 때문에 포함된 인스턴스는 ChangingLaF.this로 식별된다. 변경 가능한 룩앤필 옵션을 갖는 JFrame을 생성하기 위한 전체 ChangingLaF 프로그램을 보자.

   import javax.swing.JFrame;
   import javax.swing.JFileChooser;
   import javax.swing.JPanel;
   import javax.swing.UIManager;
   import javax.swing.JRadioButton;
   import javax.swing.ButtonGroup;
   import javax.swing.SwingUtilities;
   import javax.swing.UnsupportedLookAndFeelException;
   import java.awt.BorderLayout;
   import java.awt.GridLayout;
   import java.awt.event.ActionListener;
   import java.awt.event.ActionEvent;

   public class ChangingLaF extends JFrame {
      private static ButtonGroup group =
                                new ButtonGroup();

      public static void main(String[] args) {
        new ChangingLaF().getContentPane();
      }

      ChangingLaF() {
        JPanel myPanel = new JPanel();
        getContentPane().add(
          myPanel, BorderLayout.SOUTH);
        setLaFButtons(myPanel);
        getContentPane().add(new JFileChooser(),
          BorderLayout.CENTER);
        pack();
        setVisible(true);
      }

      private void setLaFButtons(JPanel choices) {
        installGTK();
        UIManager.LookAndFeelInfo[] laf =
          UIManager.getInstalledLookAndFeels();
        choices.setLayout(new GridLayout(laf.length, 1));
        for (int i = 0; i < laf.length; i++) {
          choices.add(new LaFButton(laf[i]));
        }
      }

      private void installGTK() {
        try {
          String GTK =
            "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
          UIManager.setLookAndFeel(GTK);
          UIManager.installLookAndFeel("GTK", GTK);
        } catch (Exception e) {
          System.err.println("Could not install GTK");
        }
      }

      private class LaFButton extends JRadioButton
        implements ActionListener {
        LaFButton(UIManager.LookAndFeelInfo laf) {
          super(laf.getClassName());
          group.add(this);
          addActionListener(this);
        }

       public void actionPerformed(ActionEvent event) {
         try {
           UIManager.setLookAndFeel(getText());
           SwingUtilities.updateComponentTreeUI(
             ChangingLaF.this);
           // call myFrame.pack() 
           // to resize frame for laf
         } catch (IllegalAccessException e) {
           // insert code to handle this exception
         } catch (UnsupportedLookAndFeelException e) {
           // insert code to handle this exception
         } catch (InstantiationException e) {
           // insert code to handle this exception
         } catch (ClassNotFoundException e) {
           // insert code to handle this exception
         }
       }
     }
   } 

ChangingLaF을 컴파일하고 실행해 보자. 플랫폼에 인스톨된 룩앤필을 쉽게 변경할 수가 있다. J2SE 1.4.2에서는 Windows XP 룩앤필도 적용할 수 있다.

사용자가 룩앤필을 사용자정의하고자 할 때가 종종 있다. 시스템이나 사용자 텍스트의 폰트를 바꾸거나, 컴포넌트를 해당 회사의 마케팅 테마에 맞는 색으로 그려야 하는 경우가 있다. 자, 룩앤필을 사용자 정의하고, 윈도우에서는 GTK가 지원되지 않는 관계로 Metal 테마를 이용하자. 이 예에서 사용하고 있는 DefaultMetalTheme는 J2SE 1.5에서는 더 이상 디폴트값으로 사용되지 않는다.

javax.swing.plaf.metal 패키지내의 클래스들을 살펴보자. MetalLookAndFeel 클래스는 Metal의 자바 구현(Java Implementation)이다. 서브클래스 DefaultMetalTheme는 기초적인 폰트 조절과, 메뉴 아이템, 시스템, 사용자 입력, 윈도우 타이틀 등을 변경하기 위해 사용된다. 이는 또한 3개의 원색과 3개의 2차 색으로 구별되는 6가지 기본 색을 변경하기 위해서도 쓰인다. 다음 CustomTheme프로그램은 DefaultMetalTheme의 서브 클래스를 생성한다. 이 프로그램은 접근 메소드들의 리턴값을 변경함으로써 오버라이드되는 색을 설정하는데, 이는 흰색을 파란색으로, 검은색을 빨강색으로 대체한다. 프로그램은 MetalLookAndFeel 클래스내의 setCurrentTheme() 메소드를 이용해서 이러한 비표준 색 테마를 선택하게 된다.

   import javax.swing.JFrame;
   import javax.swing.JFileChooser;
   import javax.swing.plaf.metal.MetalLookAndFeel;
   import javax.swing.UIManager;
   import javax.swing.UnsupportedLookAndFeelException;
   import javax.swing.plaf.metal.MetalLookAndFeel;
   import javax.swing.plaf.metal.DefaultMetalTheme;
   import javax.swing.plaf.ColorUIResource;
   import java.awt.Color;

   public class CustomTheme {

      public static void main(String[] args)
              throws UnsupportedLookAndFeelException{
        UIManager.setLookAndFeel(
          new MetalLookAndFeel());
        MetalLookAndFeel.setCurrentTheme(
          new CustomLaF());
        JFrame frame = new JFrame("Metal Theme");
        frame.getContentPane().add(new JFileChooser());
        frame.pack();
        frame.setVisible(true);
      }

      static class CustomLaF extends DefaultMetalTheme {
        protected ColorUIResource getPrimary1() {
          return new ColorUIResource(Color.MAGENTA);
        }

        public ColorUIResource getWhite() {
          return new ColorUIResource(Color.BLUE);
        }

        public ColorUIResource getBlack() {
          return new ColorUIResource(Color.RED);
        }

        public ColorUIResource getPrimaryControl() {
          return new ColorUIResource(Color.GREEN);
        }

        protected ColorUIResource getSecondary1() {
          return new ColorUIResource(Color.CYAN);
        }
      }
   }

변경된 테마를 보기 위해서는 Metal 룩앤필이 디폴트로 설정되었는지를 확인해 볼 필요가 있다. (윈도우에서는 필수적으로 확인해 보아야 한다.) 다음과 같이 커맨드 라인에서 애플리케이션을 실행할 수도 있다.

   java -Dswing.defaultlaf=
   javax.swing.plaf.metal.MetalLookAndFeel CustomTheme

스윙 룩앤필에 관한 자세한 정보는 자바 튜토리얼의 How to Set the Look and Feel를 참고하기 바란다.


반응형
Jar 파일에 리소스를 포함하여 배포하였을 경우 그 리소스에 접근하는 방법입니다.

예제코드:
package com.pmguda.resjar;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.sound.sampled.*;
import java.net.*;

public class JarResourceLoading extends JFrame
    implements ActionListener {

    JButton button;
    ImageIcon buttonIcon;
    Clip buhClip;

    public final static String SOUND_PATH = "res/gudaMid.wav";
    public final static String IMAGE_PATH = "res/gudaImage.jpg";

    public JarResourceLoading () {
        super ("Resources from .jar");
        // get image and make button
        URL imageURL = getClass().getClassLoader().getResource (IMAGE_PATH);
        System.out.println ("found image at " + imageURL);
        buttonIcon = new ImageIcon (imageURL);
        button = new JButton ("Click to Buh!", buttonIcon);
        button.setHorizontalTextPosition (SwingConstants.CENTER);
        button.setVerticalTextPosition (SwingConstants.BOTTOM);
        button.addActionListener (this);
        getContentPane().add (button);
        // load sound into Clip
        try {
            URL soundURL = getClass().getClassLoader().getResource (SOUND_PATH);
            System.out.println ("found sound at " + soundURL);
            Line.Info linfo = new Line.Info (Clip.class);
            Line line = AudioSystem.getLine (linfo);
            buhClip = (Clip) line;
            AudioInputStream ais = AudioSystem.getAudioInputStream(soundURL);
            buhClip.open(ais);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void actionPerformed (ActionEvent e) {
        System.out.println ("click!");
        if (buhClip != null) {
            buhClip.setFramePosition (0);
            buhClip.start();
        }
        else
            JOptionPane.showMessageDialog (this,
                                           "Couldn't load sound",
                                           "Error",
                                           JOptionPane.ERROR_MESSAGE);
    }

    public static final void main (String[] args) {
        JFrame frame = new JarResourceLoading();
        frame.pack();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

}
그림 1.


이클립스에서 jar 파일로 자동 배포 하였습니다. Jar 파일의 내부 구조도 이와 비슷하다.(압축을 풀어보면 알게된다.)

jar 파일안에 있는 이미지의 URL 은  Jar:file:/D:/resource.jar!/res/gudaImage.jpg

위의 코드에 대해 자세히 알아보고자 다른 자료를 찾아보았다.
다른 자료를 조금 인용하기 위해 긁어왔다.. 영문이다. ㅡㅡ;
해석해보면 그리 어렵지 않다. 위의 코드와 조금 다른 부분이 있을것이다. 잘 생각해 보자.


If you want to access a resource (configuration files, images, etc.) then you can certainly put it in a jar file, provided that jar file is in your classpath.

Then to access the resource, you can get a URL to it like this:
            URL url = this.getClass().getResource("/hello.jpg");
or you can get an InputStream to read it like this:
            InputStream is = this.getClass().getResourceAsStream("/app.properties");
Note that these methods will search for the resource in the directory tree relative to the package that "this" is in, so you will generally need the leading "/" to avoid that. But as for executable files in a jar file, forget it, there's no way to execute them from there. You can copy them out to a file and execute them from there, but that's the best you can do.


여기서 짚고 넘어가야 할 것은
패키지: com.pmguda.resjar
클래스: com.pmguda.resjar.JarResourceLoading
1.   getClass().getResource("gudaImage.jpg");
      현재 클래스의 위치에서 리소스를 찾는다
     클래스와 리소스의 위치가 같은 곳에 존재해야 한다.

2.   getClass().getResource("/res/gudaImage.jpg");
     패키지와 동일 루트에서 검색
3.   getClass().getClassLoader.getResource("res/gudaImage.jpg"); 
      패키지와 동일 루트에서의 상대 위치를 나타낸다.

java.lang.Class.getResource(String name)



번역이 완전하지가 않아 이해하기 어려울듯 하다.

java.lang.Class.getResource(String name)에서
name 가 "/gudaImage.jpg" 일 경우 절대 경로명(/gudaImage.jpg)으로 사용되고
"gudaImage.jpg" 일 경우 현 클래스의 위치에서 시작하는 상대경로가 되겠지요 이를
절대경로로
나타내면 (/com/pmguda/resjar/gudaImage.jpg)

java.lang.ClassLoader.getResource(String name)



1. getClass().getResource("/res/gudaImage.jpg"); 
2. getClass().getClassLoader.getResource("res/gudaImage.jpg"); 
동일한 URL을 리턴한다. 2번예시에서 앞에 "/" 을 넣어 시작하지 말도록 하자.
2번은 항상 상대경로만 인식하여 사용된다고 명심하시길..
ex)getClass().getClassLoader.getResource("/res/gudaImage.jpg");  

조금더 알아보고자 한다면 ClassLoader 에 대해 알아보는 것도 좋을듯 하다.
http://www.ibm.com/developerworks/kr/series/j-dclp.html?ca=dnn-krt-20071226

반응형
스윙에서는 다양한 종류에 룩앤필을 적용할수가 있다.

일단 java api에서 UIManager 에 대해 잠깐 조사해 보자.

Look & Feel 의 지정

Look & Feel 의 지정 방법은 2 서로 통과합니다. 1 개(살)은 Look & Feel 의 클래스의 완전 지정의 이름을 지정하는 방법, 이제(벌써) 1 개(살)은 LookAndFeel 의 인스턴스를 작성해,setLookAndFeel 에 건네주는 방법입니다. 다음에, 시스템의 Look & Feel 를 Look & Feel 로서 설정하는 예를 나타냅니다.
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
 
다음에, 클래스명을 지정해 Look & Feel 를 설정하는 예를 나타냅니다.
UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
 
Look & Feel 를 변경하면(자) 반드시, 모든 JComponents 상에서 updateUI 를 호출합니다. SwingUtilities.updateComponentTreeUI(java.awt.Component) 메소드를 사용하면(자), 포함 관계의 계층에 updateUI 를 간단하게 적용할 수 있습니다. 자세한 것은, 이 메소드를 참조해 주세요. Look & Feel 의 변경 후,updateUI 를 호출하지 않았던 경우의 정확한 동작은 지정되고 있지 않습니다. 예기치 않은 예외, 페인트의 문제, 또는 그 이상으로 곤란한 사태가 발생할 가능성이 높습니다.
-----------------  API 참조  --------------------------
try{
UIManager.setLookAndFeel(룩앤필 클래스이름);
}catch(Exception e){}

룩앤필은 익셉션을 발생시키기 때문에 try catch 문을 이용해 처리해 주면된다.

자바프로그램이 실행되는 시스템의 룩앤필을 적용하고자 할때는

룩앤필 클래스 이름: UIManager.getSystemLookAndFeelClassName()

ex)

try{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}catch(Exception e){}

이렇게 해주면 되고 기타 다른 룩앤필을 적용하는 방법도 있다.

http://www.jgoodies.com/downloads/libraries.html 이 사이트에 라이브러리를 적용해
룩앤필을 적용하는것도 자기가 원하는 디자인을 표현하는 것도 좋을것이다.
반응형

자바에서 가비지 컬렉션은 아주 중요한 부분을 차지하고 있다.
어플을 개발하면서 성능적 측면에서도 적지않는 영향을 주는 부분이다.
가비지 컬력션을 통해서 메모리의 상태를 모니터링하는 툴들이다.
1. 다운 사이트
http://java.sun.com/performance/jvmstat/               - Sun의 jvmstat.
http://docs.hp.com/en/5991-6757/ch03s04.html        - HP용 GC Viewer.
http://java.sun.com/developer/technicalArticles/Programming/GCPortal/  - GC관련 아티클 및 툴.
http://shark.ucsf.edu/gc/viewer/index.html
http://www.javaperformancetuning.com/tools/gcviewer/index.shtml
http://www.tagtraum.com/gcviewer-download.html

2. 특징

  • garbage collection데이터를 통해 성능 지표(throughput, accumulated pauses, longest pause 등)를 계산하여 보기 좋게 보여줌
  • GC Viewer같은 경우는 csv 포멧으로 데이터를 import할 수 있음
  • generation sizes를 변경하고 heap size를 설정하는 등을 통해 gc를 튜닝하는데 유용함

3. 플랫폼 별 설정 방법

  • Sun JDK 1.4/1.5 : -Xloggc:<file> [-XX:+PrintGCDetails]
  • Sun JDK 1.2.2/1.3.1/1.4 : -verbose:gc
  • IBM JDK 1.3.1/1.3.0/1.2.2 :n -verbose:gc
  • HP-UX JDK 1.2/1.3/1.4.x : -Xverbosegc
  • BEA JRockit 1.4.2/1.5 : -verbose:memory
  • 표준 : -Xloggc:<file> -XX:+PrintGCDetails

4. GC관련 아티클

<출처 : http://www.mimul.com/pebble/default/2008/01/09/1199887560000.html >
<출처: http://www.sjava.net/84 >

반응형

java exe wrappers
http://jsmooth.sourceforge.net/

jsmooth 를 사용하다가 splashscreen 을 사용하면
프로그램이 실행 되지가 않는다.. ㅡㅡ;
그리고 해당 아이콘(ico 아님) 가 같이 있지 않음 아이콘이 안보이는 단점
http://jstart32.sourceforge.net/
http://www.sauronsoftware.it/projects/kickstart/index.php
별다른 설정없이 exe 파일이 생성된다.
그냥 java -jar 를 이용한 그냥 심볼릭 파일 생성한다고 생각하면 될듯하다.
하지만 아이콘(ico)로 생성되기 때문에 별도의 이미지 파일이 필요 없는 장점.

java installers

http://nsis.sourceforge.net/
http://vainstall.sourceforge.net/
http://izpack.org/

+ Recent posts