2007년 8월과 9월에 썬 개발자 네트워크의 John O'Conner는 JavaFX 스크립트 프로그래밍 언어
(이 기사에서는 JavaFX 스크립트라고 줄여서 부름)를 시작하는 사용자에게 도움을 주고자
"학습 곡선 일지(Learning Curve Journal)"라는 제목의 시리즈를 기고했습니다.
그 이후로 이 언어의 많은 중요한 부분이 개선되었습니다.
아마도 가장 중요한 변화는 JavaFX 스크립트의 초기 인터프리터 기반 버전을 대신하여 컴파일러 기반 버전을
사용할 수 있게 되었다는 점입니다. 학습 곡선 일지 시리즈의 1편, 2편 및 3편은 컴파일러 기반 버전의
언어 사용 방법을 보여주기 위해 업데이트 되었습니다.
최신 내용을 반영하여 다른 변경 사항도 적용되었습니다.
4편은 시리즈의 2편에서 시작된 JavaFX 이미지 검색 애플리케이션을 완결하는 새로운 부분입니다.
학습 곡선 일지의 앞 부분에서는 JavaFX 스크립트를 사용하여 Flickr에서 이미지를 검색하는
기존 이미지 검색 애플리케이션의 사용자 인터페이스(UI)를 재현했습니다.
결과 JavaFX 스크립트는 원래 UI와 완전히 똑같진 않지만 상당히 근접했습니다.
그림 1은 JavaFX 스크립트 구현으로 생성된 기본 UI를 보여줍니다.
그림 1. JavaFX 이미지 검색 애플리케이션 UI
UI 구축에는 계층 구조적인 Swing 기반 접근 방법을 따랐습니다.
이 시리즈의 1편에서 언급한 것처럼 JavaFX 개발자는 앞으로 노드 기반 접근 방법을 사용할 것입니다.
이제 애플리케이션의 JavaFX 스크립트 버전을 완료하고 사용자가 Flickr 웹 사이트에서 이미지를 검색, 나열 및
표시할 수 있도록 하겠습니다. 완성된 JavaFX 스크립트 애플리케이션을 위해 NetBeans 프로젝트 다운로드를 받을 수 있습니다.
이미지 검색
구 현할 첫 번째 동작은 이미지 검색입니다.
사용자가 UI의 검색 필드에 검색어를 입력하면 애플리케이션은 Flickr 웹 사이트에서 이미지 검색을 시작합니다.
검색어와 매칭되는 이미지가 있으면 애플리케이션은 최대 100개의 매칭되는 축소판 이미지의 목록을 로드하여
UI의 Matched Images 영역에 선택 가능한 목록으로 표시합니다. 또한 진행 표시줄이 이미지 검색을 추적합니다.
기 존 이미지 검색 애플리케이션은 Matched Images 영역에 반환된 축소판 이미지의 목록을 제목과 함께 표시합니다.
우리는 JavaFX 스크립트 애플리케이션 목록에서 매칭되는 이미지의 반환된 제목만 보여주도록 단순화하겠습니다.
JavaFX 스크립트의 ListItem
구성요소는 현재 icon
속성을 갖지 않습니다.
따라서 현재 List
및 ListItem
구성요소가 생성하는 목록은 이미지를 포함할 수 없습니다.
이미지 검색을 위해 ImageSearcher
및 Photo
의 두 가지 JavaFX 스크립트 클래스를 생성했습니다.
또한 진행 표시줄을 위한 별도의 클래스도 생성했습니다. JavaFX 스크립트 패키지에 ProgressBar
구성요소는 아직 없습니다.
임시 해결책으로 학습 곡선 일지 시리즈에서 구축한 이미지 검색 UI에 Swing JProgressBar
구성요소로부터
JavaFX 스크립트 진행 표시줄을 생성하는 createProgressBar
함수를 포함했습니다.
우리는 이 함수를 자체 파일의 자체 클래스로 이동하기로 결정했습니다.
이는 애플리케이션의 주요 부분을 깔끔하게 만드는 이점이 있습니다.
따라서 애플리케이션에는 이제 4개의 파일이 있습니다.
Main.fx
: 애플리케이션의 주요 부분을 제공합니다. UI를 표시하고 이미지 검색 및 가져오기를 호출합니다.
ImageSearcher.fx
: 이미지 검색을 수행합니다.
Photo.fx
: Flickr 사진을 나타냅니다.
TempProgressBar.fx
: 진행 표시줄을 생성합니다.
다음은 이미지 검색을 호출하는 Main.fx
의 코드입니다.
var searcher = ImageSearcher {
callback: function(photos:Photo[]):Void {
thumbnailList.items = for(photo in photos) {
ListItem {
text: photo.title
value: photo
}
};
matchedImagePB.indeterminate= false;
}
};
var search = function():Void {
System.out.println("searching... {searchTextField.text}");
matchedImagePB.indeterminate = true;
searcher.search(searchTextField.text);
};
searchTextField.action=search;
사용자가 UI의 검색 필드에 검색어를 입력하면 ImageSearcher
클래스 내의 search
함수를 호출하는 작업을
트리거하고 검색어를 search
함수로 전달합니다.
변수에 함수가 지정된 것에 유의하십시오.
var search = function():Void {...}
JavaFX 스크립트에서 함수는 변수에 지정되거나 매개 변수로써 다른 함수에 전달될 수 있는 1급 개체(first-class object)입니다.
또한 함수는 matchedImagePB
의 indeterminate 등록 정보를 true
로 설정합니다.
var matchedImagePB = TempProgressBar { };
matchedImagePB.indeterminate = true;
TempProgressBar
클래스는 matchedImagePB
변수에 지정됩니다.
따라서 matchedImagePB
의 indeterminate 등록 정보를 설정하는 것은 실질적으로
TempProgressBar
개체의 indeterminate 등록 정보를 설정하는 것입니다.
그 영향을 이해하기 위해 TempProgressBar
클래스를 살펴보겠습니다.
TempProgressBar
클래스
다음은 TempProgressBar
클래스입니다.
package javafxscriptimgsearch2;
import javafx.ext.swing.*;
public class TempProgressBar extends Component {
protected function createJComponent():javax.swing.JComponent {
return new javax.swing.JProgressBar();
}
public attribute indeterminate:Boolean = false on replace {
var prog = this.getJComponent() as javax.swing.JProgressBar;
prog.setIndeterminate(indeterminate);
}
}
클래스에는 createJComponent
함수와 indeterminate
속성이 있습니다.
replace
트리거가 indeterminate
속성에 연결되어 있습니다.
public attribute indeterminate:Boolean = false on replace {
var prog = this.getJComponent() as javax.swing.JProgressBar;
prog.setIndeterminate(indeterminate);
}
애플리케이션의 주요 부분이 속성을 true
로 설정하는 것과 같이 속성 값이 변경되면 트리거는 진행 표시줄을 생성합니다.
특히 트리거는 getJComponent()
함수를 사용하여 JavaFX 스크립트 구성요소로 캡슐화된 Swing JProgressBar
구성요소를 생성합니다. 트리거는 또한 JProgressBar
구성요소의 indeterminate 등록 정보를
true
로 설정하여 검색 진행 중에 진행 표시줄이 계속 움직이도록 합니다.
트 리거는 JavaFX 스크립트의 강력한 기능 중 하나입니다. 트리거는 특정 조건 충족 시 코드 블록을 실행하도록 합니다.
또한 기존 Swing 구성요소의 재사용이 얼마나 쉬운지에도 유의하십시오.
Swing 구성요소를 사용하려면 JavaFX 스크립트로 간단한 래퍼(wrapper)를 작성하기만 하면 됩니다.
ImageSearcher
클래스
다음은 ImageSearcher
클래스입니다.
package javafxscriptimgsearch2;
import javax.xml.parsers.*; import org.xml.sax.helpers.DefaultHandler; import java.lang.System; import java.lang.Thread; import java.lang.Runnable; import javax.swing.SwingUtilities;
public class ImageSearcher { public attribute callback: function(photos:Photo[]):Void;
public function search(search:String) { var thread = new Thread(Runnable { public function run():Void { var photos:Photo[];
var handler = DefaultHandler { public function startDocument() { } public function startElement(uri:String, localName:String, qName:String , attributes:org.xml.sax.Attributes ) { if(qName == "photo") { var photo = Photo { id: attributes.getValue("id") server: attributes.getValue("server") farm: attributes.getValue("farm") title: attributes.getValue("title") secret: attributes.getValue("secret") }; insert photo into photos; } } public function endElement(uri:String , localName:String , qName:String ) { } public function endDocument() { }
};
var SEARCH_URL = "http://api.flickr.com/services/rest/?" + "method=flickr.photos.search"; var key = "339db1433e5f6f11f3ad54135e6c07a9"; var MAX_IMAGES = 100; var searchUrl = "{SEARCH_URL}&api_key={key}&per_page={MAX_IMAGES}&text={search}"; var url = new java.net.URL(searchUrl); var is = url.openStream(); var factory = SAXParserFactory.newInstance(); var saxParser = factory.newSAXParser(); saxParser.parse(is, handler);
SwingUtilities.invokeLater(Runnable { public function run():Void {
if(callback != null) { callback(photos); } } }); } }); thread.start(); } }
|
ImageSearcher
클래스는 애플리케이션에서 이미지 검색을 수행하는 search
함수의 래퍼(wrapper)입니다.
새 스레드를 시작하여 search
함수가 시작됩니다. 스레드 내에서 함수는 검색어와 매칭되는 이미지(Flickr에서는 사진)를 가져오기
위해 Flickr 사진 검색 웹 서비스를 호출합니다. 다음은 Flickr 서비스를 호출하는 코드입니다.
var SEARCH_URL = "http://api.flickr.com/services/rest/?" +
"method=flickr.photos.search";
var key = "339db1433e5f6f11f3ad54135e6c07a9";
var MAX_IMAGES = 100;
var searchUrl = "{SEARCH_URL}&api_key={key}&per_page={MAX_IMAGES}&text={search}";
var url = new java.net.URL(searchUrl);
var is = url.openStream();
호출은 Flickr API의 REST(Representational State Transfer) 버전을 사용합니다
(Flickr는 XML-RPC 및 SOAP 버전도 제공합니다). API에 전달되는 인수에는 API 키, 반환되는 이미지의 최대 갯수,
사용자가 지정한 검색어가 포함됩니다. 애플리케이션에서 최대 100개의 매칭되는 이미지 목록을 다운로드하고 싶으므로
이미지 최대 갯수를 100으로 설정했습니다. 사진 검색 서비스는 사진을 XML 문서로 반환하므로 문서를 분석하는 기법이 필요합니다.
이 애플리케이션에서는 분석 수행을 위해 SAX DefaultHandler
를 사용했습니다.
var handler = DefaultHandler { public function startDocument() { } public function startElement(uri:String, localName:String, qName:String , attributes:org.xml.sax.Attributes ) { if(qName == "photo") { var photo = Photo { id: attributes.getValue("id") server: attributes.getValue("server") farm: attributes.getValue("farm") title: attributes.getValue("title") secret: attributes.getValue("secret") }; insert photo into photos; } } public function endElement(uri:String , localName:String , qName:String ) { } public function endDocument() { }
};
|
각 Flickr 사진에 대해 DefaultHandler
는 사진
개체를 인스턴스화하고 속성 값을 Flicker 사진의 해당 속성 값으로 설정합니다.
DefaultHandler
는 그 다음 각 Photo
개체를 photos
라는 시퀀스에 추가합니다.
학습 곡선 일지 3편: JavaFX 스크립트 함수에서 시퀀스는 동일한 유형을 갖는 개체의 순서별 목록을 나타냈습니다.
search
함수는 그 다음 callback
을 호출하는 다른 스레드를 시작합니다.
SwingUtilities.invokeLater(Runnable {
public function run():Void {
if(callback != null) {
callback(photos);
}
}
}
SwingUtilities.invokeLater
메소드는 스레드의 Runnable
작업을 이벤트 디스패치 스레드에 놓습니다.
Callback
함수
ImageSearcher
클래스는 속성 유형이 함수인 callback
이라는 속성을 갖습니다. 함수는 photos
시퀀스를 매개 변수로
받고 아무 것도 반환하지 않습니다.
public attribute callback: function(photos:Photo[]):Void;
ImageSearcher
클래스가 인스턴스화되면(애플리케이션의 주요 부분에 발생) callback
함수는 UI의 Matched Images
영역에 표시하기 위해 제목의 목록을 구축합니다. 목록의 각 항목은 속성 값이 반환된 사진의 제목인 text
속성과
속성 값이 Photo
개체인 value
속성을 갖습니다. 다음 코드는 애플리케이션의 주요 부분 내에서
ImageSearcher
클래스를 인스턴스화하고 callback
함수를 제공합니다.
var searcher = ImageSearcher {
callback: function(photos:Photo[]):Void {
thumbnailList.items = for(photo in photos) {
ListItem {
text: photo.title
value: photo
}
};
matchedImagePB.Indeterminate= false;
}
};
매칭되는 사진 목록을 구축한 후 callback
함수는 Matched Images 진행 표시줄의 indeterminate 등록 정보를
기본값인 false
로 다시 설정합니다.
이미지 검색 수행
이미지 검색을 다루는 코드를 검토했으니 이제 작업을 살펴봅시다. 그림 2는 사용자가 검색어를 입력한 후 Search 필드와
Matched Images 진행 표시줄의 상태를 보여줍니다.
그림 2. 검색어 입력
애플리케이션이 매칭되는 이미지를 검색하는 동안 검색어를 기반으로 검색 중임을 나타내는 메시지가 나타납니다.
이 예제에서는 "searching... polar bear" 메시지가 나타납니다.
그림 3은 매칭되는 이미지의 반환된 제목 목록 일부를 보여줍니다.
그림 3. 이미지 검색 결과
검색 함수가 올바르게 작동합니다!
이미지 표시
구현할 다음 동작은 이미지 표시입니다.
사용자가 반환된 이미지 제목 목록에서 제목을 선택하면 애플리케이션은 해당 이미지를 Flickr 사이트에서 가져와
UI의 Selected Image 영역에 표시해야 합니다.
표시를 위해 이미지를 가져오도록 onChange
위임을 추가하는 List
클래스의 사용자 정의 하위 클래스인
PhotoList
라는 클래스를 추가했습니다. 다음은 Main.fx
파일에서 PhotoList
가 인스턴스화되는 방법을 보여줍니다.
class PhotoList extends List {
public attribute onChange:function(photo:Photo);
public attribute selectedPhoto:ListItem = bind selectedItem on replace {
var photo = selectedPhoto.value as Photo;
if(onChange != null) {
onChange(photo);
}
}
}
var thumbnailList = PhotoList {
preferredSize:[300, 230]
hmax: Layout.UNLIMITED_SIZE
vmax: Layout.UNLIMITED_SIZE
};
사용자가 목록에서 항목을 선택하면 애플리케이션은 PhotoList
개체의 selectedPhoto
속성을 선택된 항목으로 설정합니다.
이는 PhotoList
개체의 selectedPhoto
속성이 다음 표현식의 결과에 바인딩되었기 때문입니다.
selectedItem on replace {
var photo = selectedPhoto.value as Photo;
}
바인딩은 표현식의 결과를 변수와 연관시키는 것을 의미합니다. 표현식이 변경되면(이 경우 selectedItem
가 변경됨) 변수 값은
변경됩니다(이 경우 PhotoList
의 selectedPhoto
속성이 자동으로 업데이트됨).
바인딩은 JavaFX 스크립트의 또 다른 강력한 기능입니다. 바인딩을 사용하여 애플리케이션의 부분을 직접적이고
우아하게 동기화할 수 있습니다.
또한 사용자가 목록에서 항목을 선택하면 애플리케이션은 PhotoList
개체의 onChange
속성과 연관된 이미지 로더 함수를 호출합니다.
이 함수는 선택된 진행 표시줄의 indeterminate 등록 정보를 true
로 설정하여 이미지를 가져오는 동안 계속 진행되도록 합니다.
그 다음 선택된 Photo
개체에서 해당 이미지를 가져오기 위해 loadFullImage
함수를 호출합니다.
class PhotoList extends List {
public attribute onChange:function(photo:Photo);
...
if(onChange != null) {
onChange(photo);
}
// configure the image loader
var imageLoader = function(photo:Photo):Void {
if(photo != null) {
selectedImagePB.indeterminate = true;
photo.loadFullImage(function():Void{
selectedImageDisplay.icon = Icon { image: photo.fullImage };
selectedImagePB.indeterminate = false;
});
}
};
thumbnailList.onChange = imageLoader;
이미지 로더는 Selected Image 영역에 표시하기 위해 이미지를 설정하고 이미지를 가져온 후
선택된 진행 표시줄의 indeterminate 등록 정보를 다시 false
로 설정합니다.
selectedImageDisplay.icon = Icon { image: photo.fullImage };
selectedImagePB.indeterminate = false;
Photo
클래스
Photo
클래스를 살펴봅시다.
package javafxscriptimgsearch2;
import javafx.scene.image.*; import java.lang.*; import javax.swing.SwingUtilities; import javax.imageio.ImageIO;
public class Photo { public attribute id:String; public attribute server:String; public attribute farm:String; public attribute title:String; public attribute secret:String;
private attribute image:Image = null;
public attribute fullImage:Image = null;
public attribute fullImageURL = bind "http://static.flickr.com/{server}/{id}_{secret}.jpg";
public function loadFullImage( callback:function():Void ):Void {
if(image == null) { var thread = new Thread(Runnable { public function run():Void { var strImageUrl = "http://static.flickr.com/{server}/{id}_{secret}.jpg"; System.out.println("loading: {strImageUrl}"); var buffImg = ImageIO.read(new java.net.URL(strImageUrl));
SwingUtilities.invokeLater(Runnable { public function run():Void { image = Image.fromBufferedImage(buffImg); fullImage = image; if(callback != null) { callback(); } } });
}}); thread.start(); } else { callback(); }
} }
|
Photo
클래스는 Flickr에 저장된 사진과 연관시키는 XML 속성에 해당하는 몇 가지 속성을 갖습니다.
예를 들어 Photo
클래스의 id
속성은 Flickr에 저장된 사진의 id
속성에 해당합니다. 또한 클래스는 Flickr에서 사진을 가져오는
loadFullImage
함수도 제공합니다.
새 스레드를 시작하여 loadFullImage
함수가 시작됩니다.
스레드 내에서 함수는 특정 사진에 대해 사진 소스 URL을 구성한 다음 사진을 가져옵니다.
다음은 사진 소스 URL을 구성하고 사진을 가져오는 코드입니다
var strImageUrl = http://static.flickr.com/{server}/{id}_{secret}.jpg
var buffImg = ImageIO.read(new java.net.URL(strImageUrl));
loadFullImage
함수가 URL 구성을 위해 Photo
개체의 server
, id
및 secret
속성을 사용하는 것에 유의합니다.
또한 이미지 로드를 위해 자바 imagio
패키지의 ImageIO.read
메소드를 사용했습니다.
함수는 JavaFX 스크립트 Image
클래스의 fromBufferedImage
메소드를 사용하여 가져온 사진을 Photo
개체의
image
속성에 지정합니다. 그런 다음 호출자(이 경우 애플리케이션의 주요 부분에 있는 이미지 로더)에게의 callback
을
호출하는 다른 스레드를 시작합니다.
SwingUtilities.invokeLater(Runnable {
public function run():Void {
image = Image.fromBufferedImage(buffImg);
fullImage = image;
if(callback != null) {
callback();
}
}
});
SwingUtilities.invokeLater
메소드는 스레드의 Runnable 작업을 이벤트 디스패치 스레드에 놓습니다.
목록에 이미지 표시
애플리케이션의 이미지 표시 부분을 테스트해봅시다. 그림 4는 Matched Images 영역에서 이미지를 선택한 후
Selected Image 진행 표시줄의 상태를 보여줍니다.
그림 4. 표시할 이미지 선택
애플리케이션이 이미지를 가져오면 이미지의 사진 소스 URL을 식별하는 메시지를 표시합니다.
이 예제에서는 "loading: http://static.flickr.com/3234/2595583764_a2e6661d3a.jpg" 메시지가 나타납니다.
이미지 가져오기가 끝나면 애플리케이션은 "loaded" 메시지를 표시합니다.
그림 5는 UI의 Selected Image에 선택한 이미지가 표시된 완전한 UI를 보여줍니다.
그림 5. 표시된 이미지
애플리케이션의 이미지 가져오기 부분이 작동합니다. 애플리케이션도 의도대로 작동합니다.
완성된 JavaFX 스크립트 애플리케이션을 위해 NetBeans 프로젝트 다운로드를 받을 수 있습니다.
요약
이 학습 곡선 일지 시리즈에서는 JavaFX 스크립트 프로그래밍 언어
(이 시리즈에서는 JavaFX 스크립트라고 줄여서 부름)의 사용을 시작하는데 도움이 되는 몇 가지 기본 개념 및 기법을 소개했습니다.
시리즈의 1편에서는 간단한 JavaFX 애플리케이션을 만드는 방법을 소개했습니다.
2편에서는 풍부한 UI 생성을 위해 사용 가능한 JavaFX 라이브러리 내의 일부 클래스와 언어의 선택적 구문을 설명했습니다.
UI 구축에는 계층 구조적인 Swing 기반 접근 방법을 따랐습니다.
이 시리즈의 1편에서 언급한 것처럼 앞으로 JavaFX 개발자는 노드 기반 접근 방법을 사용할 것입니다.
노드 기반 접근 방법에서는 UI 구축을 위해 다른 JavaFX 라이브러리를 사용할 것입니다.
3편에서는 뷰와 데이터 모델 등의 애플리케이션 부분을 동기화하기 위해 사용 가능한 JavaFX 스크립트의 바인딩 기능과
JavaFX 스크립트 함수를 다뤘습니다.
4편(본 기사)에서는 웹 서비스 액세스를 위한 JavaFX 스크립트 사용 방법을 보여주었습니다.
그 과정 중에 FX 스크립트에서 Swing 클래스와 같은 자바 기술 슬래스의 액세스가 얼마나 쉬운지도 보여주었습니다.
또한 이 시리즈를 통해 코드 완성과 같은 기능이 어떻게 NetBeans IDE 6.1에서 JavaFX 애플리케이션의 빌드 및 실행을
단순화하는지도 볼 수 있었습니다. JavaFX Preview SDK를 사용하여 명령줄에서 JavaFX 애플리케이션을
빌드 및 실행할 수도 있습니다.JavaFX 포함 NetBeans IDE 6.1을 다운로드하여 설치하거나
JavaFX Preview SDK를 다운로드하여 JavaFX 스크립트를 시작하십시오.
자세한 정보
이 글의 영문 원본은 Learning Curve Journal, Part 4: Accessing a Web Service에서 보실 수 있습니다.