98..Etc/JavaFX2008. 11. 12. 17:29
반응형

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 속성을 갖지 않습니다. 따라서 현재 ListListItem 구성요소가 생성하는 목록은 이미지를 포함할 수 없습니다.

이미지 검색을 위해 ImageSearcherPhoto의 두 가지 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가 변경됨) 변수 값은 변경됩니다(이 경우 PhotoListselectedPhoto 속성이 자동으로 업데이트됨). 바인딩은 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, idsecret 속성을 사용하는 것에 유의합니다. 또한 이미지 로드를 위해 자바 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
에서 보실 수 있습니다.

"Java FX" 카테고리의 다른 글

Posted by 1010
98..Etc/JavaFX2008. 11. 12. 17:29
반응형

2007년 8월과 9월에 썬 개발자 네트워크의 John O'Conner는 JavaFX 스크립트 프로그래밍 언어(이 기사에서는 JavaFX 스크립트라고 줄여서 부름)를 시작하는 사용자에게 도움을 주고자 "학습 곡선 일지(Learning Curve Journal)"라는 제목의 시리즈를 기고했습니다.

그 이후로 이 언어의 많은 중요한 부분이 개선되었습니다. 아마도 가장 중요한 변화는 JavaFX 스크립트의 초기 인터프리터 기반 버전을 대신하여 컴파일러 기반 버전을 사용할 수 있게 되었다는 점입니다. 이전의 학습 곡선 일지에서는 인터프리터 기반 버전 사용에 대해 설명했습니다.

업데이트된 학습 곡선 일지에서는 컴파일러 기반 버전의 언어 사용법을 보여줍니다. 최신 내용을 반영하여 다른 변경 사항도 적용되었습니다.

지난 학습 곡선 기사에서는 간단한 사용자 인터페이스(UI)를 구현하고 UI 시험이 성공적이었음을 확인했습니다. 결과 JavaFX 스크립트는 Flickr에서 이미지를 검색하는 이미지 검색 애플리케이션 구축을 위한 원래 자바 프로그래밍 언어 UI와 비슷하게 보였습니다. 그림 1은 결과적인 기본 프레임을 보여줍니다.


그림 1. 원래 UI를 복제한 JavaFX 이미지 검색 애플리케이션

선언적 JavaFX 스크립트 구문을 사용한 결과 코드는 자바 언어 UI의 이식을 위한 괜찮은 시작이었습니다. 그러나 유휴 상태의 프레임, 응답하지 않는 검색 필드, 비활성 진행 표시줄, 빈 목록 상자 및 빈 이미지 레이블에 대한 추가 작업이 필요합니다. 여기에서는 아무 일도 발생하지 않습니다. 현재까지는 활성 데이터모델에 아무 UI 요소도 연결되지 않았으며 사용자 상호작용에도 응답하지 않습니다. 예를 들어 Search 텍스트 필드는 입력한 문자를 받아서 보여주지만 아직 아무 것도 하지 않습니다.

이 골격 뿐인 UI에는 추가 작업이 필요합니다. 이를 위해 비활성 애플리케이션에서 필요한 작업을 수행하도록 하는 함수가 필요합니다.


함수

JavaFX 스크립트 함수는 자바 프로그래밍 언어 메소드와 비슷합니다. 이들 메소드와 마찬가지로 함수는 매개 변수와 반환 값을 갖습니다. 또한 속성 및 변수와 if-then, while 루프, for 루프 및 기타 조건문도 가질 수 있습니다. 다음은 몇 가지 유효한 함수의 예입니다.


 
function z(a,b) {
      var x = a + b;
      var y = a - b;
      return sq(x) / sq (y);
  }

  function sq(n) {return n * n; }

  function main() {
      return z(5, 10);
  }

  function min(x1 : Number, x2 : Number ): Number {
      if (x1 < x2) {
        return x1;
      }
      else {
        return x2;
  }
 

반환 값 유형이나 매개 변수 유형을 선언할 필요가 없습니다. 그러나 자바 언어 프로그래머인 저에게는 min 함수에서와 같이 유형을 사용하는 것이 익숙하므로 가능한 경우에는 항상 사용하곤 합니다. 매개 변수 유형을 선언하는 것은 명확성에 도움이 됩니다. 따라서 z 함수를 다음과 같이 다시 작성하겠습니다.


function z(a: Number, b: Number): Number {
   var x: Number = a + b;
   var y: Number = a - b;
   return sq(x) / sq (y);
}
 


return 키워드는 선택 사항입니다.

   function sq(n) {n*n;}

위 함수는 다음과 같습니다.

   function sq(n) {return n*n;}

함수는 function 키워드로 시작합니다. 그 뒤에는 함수 이름과 매개 변수 목록이 나옵니다. JavaFX 스크립트에서는 유형이 변수나 함수 이름 다음에 나옵니다. 예를 들면 b: Number 매개변수는 b 라는 인수의 유형이 Number라는 의미입니다. 마지막으로 함수는 Number를 반환하므로 이를 매개 변수 목록 뒤에 선언할 수 있습니다. 함수의 본문 앞뒤에는 자바 언어에서 메소드 본문을 둘러싸는 것과 같이 괄호가 있습니다.

함수는 매개 변수나 기타 참조 변수가 변경될 때마다 반환 값을 재평가합니다. 이 기능은 개체를 자주 변경될 수 있는 특정 값에 바인드하려는 경우 유용합니다. 바인딩에 대해서는 나중에 자세히 설명하겠습니다.

클래스에 대해 함수가 정의될 수도 있습니다. 다음은 Friends 클래스에 대해 정의된 함수의 예입니다.


   import java.lang.System;
  import javafx.lang.Sequences;

  class Friends {
      attribute knownNames: String[];
      function sayHello(name: String): String {
          var index = Sequences.indexOf(knownNames,name);
          if (index >= 0) {
              return "Hello, {name}!";
              } else {
              return "Sorry, I can't talk to strangers.";
              }
      }
  }

  var buddies = Friends {
      knownNames: ["John", "Robyn", "Jack", "Nick", "Matthew",
      "Tressa", "Ruby"]
  };

  var greeting = buddies.sayHello("John");
  System.out.println(greeting);
 


이 작은 프로그램은 sayHello 메소드에 아는 이름을 입력하면 "Hello"라고 응답합니다. 그렇지 않은 경우엔 "can't talk to strangers"라고 합니다. Friends 클래스에는 한 가지 속성과 속성을 사용하여 메시지를 반환하는 함수가 포함되어 있습니다. Sequences 클래스를 사용하는 것에 유의합니다. 이 클래스는 시퀀스 조작을 위한 다양한 함수를 포함합니다. 시퀀스는 이 예제에서 아는 이름의 목록과 같이 개체의 순서별 목록을 나타냅니다. SequencesindexOf 함수는 지정된 시퀀스에서 같은 값을 갖는 개체를 검색합니다. 여기에서 indexOf는 아는 이름 시퀀스에서 지정된 이름(이 예제에서는 "John")과 매칭되는 개체를 검색합니다.


반응적 UI 요소

UI 요소(JavaFX 스크립트 라이브러리에서는 노드)는 키 입력이나 마우스 클릭과 같은 사용자 상호작용에 응답할 수 있습니다. 위젯은 action, onMouseClicked, onKeyTyped 및 기타 이벤트 기반 속성을 갖습니다. 이들 속성과 함수를 연관시킬 수 있습니다. 예를 들어 함수를 TextFieldaction 속성과 연관시키면 해당 함수는 필드 내에서 Enter를 누를 때 실행됩니다. Button 위젯의 동일한 action 속성은 사용자가 클릭할 때마다 활성화됩니다.

JavaFX 이미지 검색 애플리케이션과 관련된 함수가 필요하므로 UI 요소에 대한 이벤트 핸들러를 작성하기로 했습니다. 다음 애플리케이션은 두 개의 버튼과 하나의 레이블을 생성합니다. Bigger 버튼을 누르면 레이블의 글꼴 크기가 증가하고 텍스트가 변경됩니다. Smaller 버튼을 누르면 레이블의 글꼴 크기가 감소하고 텍스트가 변경됩니다.

이 애플리케이션에서는 계층 구조적인 Swing 기반 접근 방법을 따릅니다. 이 시리즈의 1편에서 언급한 것처럼 앞으로는 JavaFX 스크립트 개발자가 노드 기반 접근 방법을 사용하도록 할 것입니다.


   import javafx.ext.swing.SwingFrame;
  import javafx.ext.swing.BorderPanel;
  import javafx.ext.swing.FlowPanel;
  import javafx.ext.swing.Button;
  import javafx.scene.Font;
  import javafx.scene.HorizontalAlignment;
  import javafx.ext.swing.Label;

  var font = Font { size: 18 };

  class FontDataModel {
      attribute text: String;

      function increaseFontSize() {
         font = Font { size: font.size + 1 };
         text= "Font Test ({font.size})";
         }
      function decreaseFontSize() {
         font = Font { size: font.size - 1 };
         text= "Font Test ({font.size})";
      }
  }

  SwingFrame {
       var myFont = FontDataModel {
           text: "Font Test (18)"

       }

      content:
      BorderPanel {
          top: FlowPanel {
              alignment: HorizontalAlignment.LEADING
              content: [
                  Button { text:"Bigger"
                      action :
                      function() {
                           myFont.increaseFontSize();
                      }
              },

              Button { text:"Smaller"
                  action : function() {
                      myFont.decreaseFontSize();
                  }
              }

              ]
          }
          center:
              Label {
                  width: 200
                  font: bind font
                  text: bind myFont.text

          }

       }
       visible: true
  }
 

이 코드를 잘라내어 JavaFX 애플리케이션에 바로 붙여넣을 수 있습니다. Preview 버튼을 사용하면 그림 2의 결과가 보입니다.


그림 2. 미리보기 기능을 사용하여 JavaFX 스크립트 언어를 대화형으로 시험

이 Bigger-Smaller 글꼴 애플리케이션은 텍스트 문자열 속성을 갖는 FontDataModel을 생성합니다. 애플리케이션은 FontDataModel 인스턴스를 생성하고 text 속성을 초기화합니다. FontDataModel에도 increaseFontSizedecreaseFontSize라는 두 가지 함수가 있습니다. 이들 함수는 텍스트 속성을 변경하고 Font 클래스의 인스턴스를 생성하며 인스턴스의 글꼴 크기를 업데이트합니다.

왜 글꼴을 FontDataModel의 속성으로 지정하고 텍스트 속성과 같은 방법으로 함수에서 업데이트하지 않는지 궁금할 지도 모릅니다. 이러한 접근 방법은 Font가 변경할 수 없는 개체, 즉 원래 개체의 속성을 변경할 수 없기 때문에 사용할 수 없습니다. 개체의 인스턴스에서만 속성을 변경할 수 있습니다.

각 버튼은 연관된 함수를 포함하는 action 속성을 갖습니다. 예를 들어 Bigger 레이블이 있는 버튼은 myFont 변수의 increaseFontSize 함수를 호출합니다.


           
Button { text:"Bigger"

       action :
      function() {
           myFont.increaseFontSize();
      }
   
}
 

버튼을 누를 때마다 글꼴 크기와 텍스트가 변경되는 것을 볼 수 있습니다. 그림 3은 Bigger 버튼을 두 번 눌렀을 때의 결과를 보여줍니다.


그림 3. 버튼을 클릭하면 텍스트와 글꼴 크기 변경

분명히 increaseFontSize 메소드는 글꼴과 텍스트를 변경합니다. 이를 위해 바인딩이라는 JavaFX 스크립트 기능을 사용했습니다.


뷰와 모델 바인딩

JavaFX 스크립트에는 하나의 속성이 다른 속성의 변경을 추적하도록 하는 bind 연산자가 있습니다. 하나의 속성을 다른 속성에 바인딩한다는 것은 바인딩된 속성이 대상 속성의 변경 사항을 항상 인지한다는 것을 의미합니다. 이 Bigger-Smaller 글꼴 애플리케이션에서는 Label이 글꼴과 텍스트의 변경 사항을 추적하도록 하려고 합니다. 이를 위해 Labelfont 속성을 Font 변수에 바인드하도록 bind 연산자를 사용했습니다. 또한 Labeltext 속성을 FontDataModeltext 속성에 바인드하도록 bind 연산자를 사용했습니다.

다음은 Label 선언입니다.


 
Label {
      width: 200
      font: bind font
      text: bind myFont.text

  }
 

bind 연산자는 함수에도 사용할 수 있습니다. 함수는 인수나 참조 변수가 변경될 때마다 결과를 업데이트하므로 함수에의 바인딩은 단일 속성에의 바인딩과 마찬가지로 작용합니다. 사실 함수는 실제로 바인딩과 함께 사용하도록 설계되었습니다. 본문 내의 매개 변수 및 참조 변수를 모두 포함하는 모든 종속성을 자동으로 추적하는 재사용 가능한 하위 루틴으로의 바인딩을 리팩터링하기 위해 이들을 사용할 수 있습니다.

표 1에서 코드의 일부를 참조하십시오.

표1. 함수와 함께/함수 없이 바인드 사용
함수 없이 바인드
함수와 함께 바인드
   import java.lang.System;

class Data {
attribute foo: Number;
attribute baz: Number;
}

var data = Data {
foo: 4
baz: 7
};

var zoo = bind data.foo +
data.baz + 10;
System.out.println(zoo);
data.baz = 12;
System.out.println(zoo);
   import java.lang.System;

class Data {
attribute foo: Number;
attribute baz: Number;
function add(x, y, z): Number {
return x+y+z;
}
}

var data = Data {
foo: 4
baz: 7
};

var zoo = bind data.add(data.foo, data.baz, 10);
System.out.println(zoo);
data.baz = 12;
System.out.println(zoo);
출력:
21.0
26.0
출력:
21.0
26.0

요약

JavaFX 애플리케이션에 동작을 추가하기 위해 함수를 사용합니다. UI 구성요소의 속성은 함수에 매핑됩니다. action, onMouseClickedonKeyTyped와 같은 UI 이벤트 처리를 위해 이들 함수를 정의할 수 있습니다. 함수에는 함수 본문 내에서 매개 변수 및 참조 변수를 재평가하는 추가적인 등록 정보가 있습니다.

bind 연산자를 사용하여 하나의 속성을 다른 속성에 연결할 수 있습니다. 이는 UI 위젯이 모델 속성을 추적하도록 할 때 특히 유용합니다. 뷰 속성을 모델 속성에 바인딩한다는 것은 모델과 뷰가 동일한 데이터로 항상 동기화됨을 의미합니다.

JavaFX 이미지 검색 애플리케이션의 UI에 아직 기능을 추가하지 않았지만 함수가 필요하다는 것을 알게 되었습니다. 검색 텍스트를 가져오기 위해서는 거의 확실히 함수를 사용해야 합니다. 같은 함수가 이미지를 가져오기 위해 Flickr 사이트에도 액세스할 것입니다. 또한 뷰를 기본 모델에 연결하기 위해 bind 연산자도 사용해야 할 것입니다.

이제 UI를 일부 기본 작업 및 함수에 연결하는 방법을 알게 되었습니다. 또한 UI를 기본 모델에 연결하기 위해 bind 연산자도 사용할 수 있습니다.

자세한 정보
이 글의 영문 원본은
Learning Curve Journal, Part 3: JavaFX Script Functions
에서 보실 수 있습니다.

"Java FX" 카테고리의 다른 글

Posted by 1010
98..Etc/JavaFX2008. 11. 12. 17:28
반응형

2007 년 8월과 9월에 썬 개발자 네트워크의 John O'Conner는 JavaFX 스크립트 프로그래밍 언어(이 기사에서는 JavaFX 스크립트라고 줄여서 부름)를 시작하는 사용자에게 도움을 주고자 "학습 곡선 일지(Learning Curve Journal)"라는 제목의 시리즈를 기고했습니다.

그 이후로 이 언어의 많은 중요한 부분이 개선되었습니다. 아마도 가장 중요한 변화는 JavaFX 스크립트의 초기 인터프리터 기반 버전을 대신하여 컴파일러 기반 버전을 사용할 수 있게 되었다는 점입니다. 이전의 학습 곡선 일지에서는 인터프리터 기반 버전 사용에 대해 설명했습니다.

업데이트된 학습 곡선 일지에서는 컴파일러 기반 버전의 언어 사용법을 보여줍니다. 최신 내용을 반영하여 다른 변경 사항도 적용되었습니다.

사용자 인터페이스를 정의하기 위해 10년 가까이 자바 프로그래밍 언어를 사용해온 저는 JavaFX 스크립트를 처음 사용해보고 두 가지 환경 사이의 큰 차이점을 금방 느낄 수 있었습니다. 프로그래머는 자바 언어에서 사용자 인터페이스(UI) 정의를 위해 절차적 언어를 사용하지만 JavaFX Script에서는 UI 정의에 선언적 문을 사용할 수 있습니다. 이는 커다란 차이이며 여기에 적응하는 데는 약간의 시간과 노력이 필요할 수 있습니다.

UI 생성을 위한 새로운 선언적 스타일에 대해 알아보고자 기존 애플리케이션 UI를 자바 언어 구현에서 JavaFX 스크립트로 이식하기로 했습니다. 썬 개발자 네트워크자바 언어 허브에 있는 Swingworker 기사에서 만든 이미지 뷰어 애플리케이션을 선택했습니다. 원래 애플리케이션은 Java SE 6에서 SwingWorker 클래스의 사용 방법을 보여주기 위한 것이었지만 UI 자체는 JavaFX 스크립트로의 간편한 이전을 제공할 만큼 충분히 단순해 보입니다.


기존 사용자 인터페이스

기 존 애플리케이션에서는 사용자가 유명한 Flickr 웹 사이트에서 이미지를 검색, 나열 및 표시할 수 있도록 했습니다. 사용자는 검색어를 입력할 수 있으며 애플리케이션은 REST API를 사용하여 매칭되는 축소판 이미지 목록을 Flickr에 쿼리합니다. 사용자는 축소판 이미지를 하나 선택하여 크고 상세한 이미지를 가져올 수 있습니다. 그림 1은 검색 결과가 나온 기존 애플리케이션을 보여줍니다.

그림 1. 검색 결과가 나온 애플리케이션 UI

이 UI는 위에서부터 아래로 다음 구성요소로 이루어져 있습니다.

  • 기본 프레임 창 컨테이너
     
  • 검색 레이블 및 검색 텍스트 필드
     
  • 검색 매칭 레이블 및 진행 표시줄
     
  • 매칭되는 축소판 이미지 목록과 짧은 설명
     
  • 선택 레이블 및 진행 표시줄
     
  • 선택한 이미지를 보여주는 레이블
     

UI는 JFrame, JLabel, JProgressBar, JScrollPaneJList.와 같은 일반 Swing 구성요소로 이루어집니다. JList 구성요소는 축소판 이미지와 제공되는 짧은 설명을 표시하기 위한 사용자 정의 렌더러를 갖지만 여전히 상대적으로 간단한 UI로서 JavaFX 스크립트의 선언적 UI 측면을 조사하는 데 도움이 될 것입니다. 전체 애플리케이션의 구현을 시도해 보겠지만 현재로는 기존 UI와 적당히 비슷한 정도로도 충분합니다. 작동하는 데모처럼 극적이진 않겠지만 그림 2에 나온 비활성 UI는 JavaFX 스크립트의 선언적 UI에 대한 초기 목표를 보여줍니다.

그림 2. 애플리케이션 UI

원래 UI는 NetBeans IDE 6.1과 Matisse GUI 편집기를 사용하여 구현했습니다. Swingworker 기사에서 모든 원래 코드 및 생성된 UI 주변의 코드를 다운로드 받을 수 있습니다. 코드에서 이 UI를 생성하기 위해 NetBeans IDE가 GroupLayout를 어떻게 사용했는지 볼 수 있습니다.

    private void initComponents() {
lblSearch = new javax.swing.JLabel();
txtSearch = new javax.swing.JTextField();
lblImageList = new javax.swing.JLabel();
scrollImageList = new javax.swing.JScrollPane();
listImages = new JList(listModel);
lblSelectedImage = new javax.swing.JLabel();
lblImage = new javax.swing.JLabel();
progressMatchedImages = new javax.swing.JProgressBar();
progressSelectedImage = new javax.swing.JProgressBar();

setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setTitle("Image Search");
lblSearch.setText("Search");
lblImageList.setText("Matched Images");

// ...
// event listeners, models, and cell renderers removed for this example
//

lblSelectedImage.setText("Selected Image");

lblImage.setBorder(javax.swing.BorderFactory.createLineBorder(
new java.awt.Color(204, 204, 204)));
lblImage.setFocusable(false);
lblImage.setMaximumSize(new java.awt.Dimension(500, 500));
lblImage.setMinimumSize(new java.awt.Dimension(250, 250));
lblImage.setOpaque(true);
lblImage.setPreferredSize(new java.awt.Dimension(500, 250));

javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(lblImage, javax.swing.GroupLayout.Alignment.LEADING,
javax.swing.GroupLayout.DEFAULT_SIZE, 462, Short.MAX_VALUE)
.addComponent(scrollImageList, javax.swing.GroupLayout.DEFAULT_SIZE,
462, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lblImageList)
.addComponent(lblSelectedImage))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(progressMatchedImages, javax.swing.GroupLayout.DEFAULT_SIZE,
350, Short.MAX_VALUE)
.addComponent(progressSelectedImage, javax.swing.GroupLayout.DEFAULT_SIZE,
350, Short.MAX_VALUE)))
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addComponent(lblSearch)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(txtSearch, javax.swing.GroupLayout.DEFAULT_SIZE,
411, Short.MAX_VALUE)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(lblSearch)
.addComponent(txtSearch, javax.swing.GroupLayout.PREFERRED_SIZE,
javax.swing.GroupLayout.DEFAULT_SIZE,
javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lblImageList)
.addComponent(progressMatchedImages, javax.swing.GroupLayout.PREFERRED_SIZE,
javax.swing.GroupLayout.DEFAULT_SIZE,
javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(scrollImageList, javax.swing.GroupLayout.PREFERRED_SIZE, 235,
javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lblSelectedImage)
.addComponent(progressSelectedImage, javax.swing.GroupLayout.PREFERRED_SIZE,
javax.swing.GroupLayout.DEFAULT_SIZE,
javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lblImage, javax.swing.GroupLayout.DEFAULT_SIZE, 305, Short.MAX_VALUE)
.addContainerGap())
);
pack();
}
 
NetBeans IDE를 사용한 UI 레이아웃은 쉽습니다. 드래그 앤 드롭이면 충분합니다. 이 예제에서 NetBeans IDE 6.1은 여러 가지 호스트 플랫폼에서 정확한 크기, 위치 및 구성요소의 간격을 제공하는 javax.swing.GroupLayout을 사용하여 UI 코드를 생성했습니다. 결과 코드는 그다지 읽기 쉽진 않지만 도구 지원이 뛰어나 레이아웃 코드를 직접 수동으로 작업할 필요가 없습니다.


선언적 JavaFX 스크립트 인터페이스

JavaFX 스크립트용의 설계 도구가 개발 중이지만 아직은 사용할 수 없습니다. 그러나 JavaFX 스크립트 플러그인이 포함된 NetBeans IDE 6.1의 미리보기 기능을 사용하면 수동으로 UI 코드를 입력하고 결과를 즉시 볼 수 있습니다.

이 인터페이스에 필요한 UI 구성요소는 javafx.ext.swing 패키지 및 javafx.scene으로 시작하는 다양한 패키지에 들어 있습니다. UI 구축은 계층 구조적인 Swing 기반 접근 방법을 따릅니다. 이 시리즈의 1편에서 언급한 것처럼 앞으로 JavaFX 개발자는 노드 기반 접근 방법을 사용할 것입니다. 노드 기반 접근 방법으로 UI를 구축하면 javafx.application 클래스의 일부 클래스가 필요할 것입니다.

JavaFX 스크립트는 패키지 구조 및 가져오기 문을 지원한다는 점에서 자바 언어와 유사합니다. 구성요소에 대해 공부하는 동안은 전체 패키지를 가져오는 대신 한 번에 하나씩 특정 구성요소를 가져오겠습니다. 이는 매우 지루하지만 하나씩 사용하게 함으로써 패키지에 어떤 구성요소가 있는지 보여줍니다.

JavaFX 스크립트 구성요소는 height, width, textcontent와 같은 속성을 갖습니다. content 속성은 자녀 구성요소를 갖습니다. 구성요소에 따라 content 속성은 array로 선언되는 여러 개의 구성요소를 포함할 수 있습니다. 이미지 검색 애플리케이션을 위해 사용자 인터페이스를 선언할 때는 JavaFX 스크립트 구성요소의 선택 및 사용 후 속성을 설정합니다.

예를 들어 다음의 짧은 스크립트는 빈 프레임을 정의합니다. 이 코드는 컨텐츠가 없는 프레임 컨테이너를 생성합니다.


  import javafx.ext.swing.SwingFrame;

  SwingFrame {
      title: "JFX Image Search"
      height: 500
      width: 500
      visible: true
  }
 

Swing의 JFrame에 해당하는 JavaFX 스크립트는 javafx.ext.swing.SwingFrame입니다. title 속성은 프레임에 나타나는 텍스트인 창 프레임 제목을 선언합니다. heightwidth 속성은 픽셀 단위로 크기 규격을 정의합니다. 마지막으로 visible 속성은 Swing의 JFrame 클래스 setVisible 메소드와 유사하게 프레임의 가시성 여부를 선언합니다.


Border Panel 및 Flow Panel

javafx.ext.swing 패키지에는 SwingFrame, Label, BorderPanel, FlowPanelList와 같은 구성요소가 포함됩니다. 이러한 이름은 일반적인 Swing 구성요소 같으므로 대상 UI 구현에 이들을 먼저 사용해보겠습니다. javafx.scene 패키지의 HorizontalAlignment 구성요소와 javafx.scene.paint 패키지의 Color 구성요소도 사용했습니다. 그러나 JavaFX 스크립트 패키지에 ProgressBar 구성요소는 아직 없습니다. 따라서 진행 표시줄을 생성하는 함수를 코딩했습니다. 이 시리즈의 3편에서는 JavaFX 스크립트 함수를 검토합니다. BorderPanelFlowPanel 등의 구성요소와 진행 표시줄의 함수를 사용하기로 결정하고 다음 JavaFXImageSearchUI1.fx 코드를 생성했습니다.


 
package com.sun.demo.jfx;

  import javafx.ext.swing.SwingFrame;
  import javafx.ext.swing.BorderPanel;
  import javafx.ext.swing.FlowPanel;
  import javafx.ext.swing.Label;
  import javafx.ext.swing.TextField;
  import javafx.ext.swing.List;
  import javafx.ext.swing.Component;
  import javafx.scene.paint.Color;
  import javafx.scene.HorizontalAlignment;
  import java.awt.Dimension;

  function createProgressBar(preferredSize:Integer[]) :Component {
      var jprogressbar = new javax.swing.JProgressBar();
      var comp = Component.fromJComponent(jprogressbar);
      comp.preferredSize = preferredSize;
      comp;
  }

  SwingFrame {
      title: "JFX Image Search"
      content: BorderPanel  {

          top: FlowPanel {
              alignment: HorizontalAlignment.LEFT
              content: [
              Label {
                  text: "Search"
                  },
              TextField {
                  columns: 50
              }
              ]
          }
          center: BorderPanel {
              top: FlowPanel {
                  alignment: HorizontalAlignment.LEFT
                  content: [
                  Label {
                      text: "Matched Images"

                      },
                  createProgressBar([360, 20])
                  ]
              }
              center: List {
                  preferredSize: [100, 200]
              }
              bottom: FlowPanel {
                  alignment: HorizontalAlignment.LEFT
                  content: [
                  Label {
                      text: "Selected Image"
                      },
                  createProgressBar([365, 20])
                  ]
              }
          }
          bottom: Label {
                  preferredSize: [400, 300]

          }

      }
      visible: true
  }
 

NetBeans IDE 6.1에 프로젝트를 생성한 후에 JFXImageSearchUI1.fx 파일에 위의 코드를 입력했습니다. 미리보기 버튼을 클릭하면 그림 3과 같은 결과가 나타납니다.

그림 3. JavaFX 이미지 검색 UI -- 첫 번째 시도


프레임의 컨텐츠는 BorderPanel 구성요소로, top 속성은 FlowPanel 구성요소에 지정되고 center 속성은 다른 BorderPanel 구성요소에 지정되며 bottom 속성은 Label 구성요소에 지정되었습니다. FlowPanel 구성요소는 content 등록 정보를 갖습니다. 이 등록 정보에 하나 이상의 구성요소를 넣을 수 있습니다. 여러 개의 구성요소를 삽입할 때는 array를 정의하는 괄호 속에 구성요소를 넣어야 합니다. 다음과 같이 쉼표를 사용하여 컨텐츠 array에서 개별 구성요소를 구분합니다.


  content: [
  Label {
     text: "Search"
     },
  TextField {
     columns: 50
  }
  ]
 

상단 FlowPanel에는 Label 구성요소와 TextField 구성요소가 중첩되어 있습니다. FlowPanel 의 방향 속성은 HorizontalAlignment.LEFT입니다. 이는 FlowPanel 내의 구성요소를 가로와 왼쪽으로 정렬합니다. 가운데의 BorderPanel은 이 영역의 상단에 가로로 정렬된 레이블과 진행 표시줄을, 중앙에 목록을, 하단에 다른 레이블과 진행 표시줄을 레이아웃합니다. 프레임의 하단에는 레이블이 들어갑니다.

FlowPanel 구성요소는 레이블-구성요소 쌍을 만들기에 좋습니다. 여기에서는 여러 개의 FlowPanel을 사용하여 레이블과 TextField 등의 구성요소를 묶었습니다.

createProgressBar 함수는 JavaFX 스크립트를 사용하여 Swing 구성요소로부터 javafx.gui 구성요소를 생성하는 방법을 보여줍니다. 여기에서는 Swing JProgressBar 구성요소로부터 JavaFX 스트립트 진행 표시줄을 생성했습니다.


 
 function createProgressBar(preferredSize:Integer[]) :Component {
          var jprogressbar = new javax.swing.JProgressBar();
          var comp = Component.fromJComponent(jprogressbar);
          comp.preferredSize = preferredSize;
          comp;
  }
 

첫 번째 시도에서 UI는 상당히 잘 작동하지만 원래 UI를 제대로 재현하지는 못합니다. 그래서 다른 접근 방법을 시도해 보겠습니다.


클러스터

원래 UI를 복제하려는 두 번째 시도에서는 JavaFX 스크립트 ClusterPanel 구성요소를 활용하겠습니다. 이 구성요소를 사용하여 프레임 내에서 구성요소를 클러스터할 수 있습니다. JavaFX 스크립트에는 ClusterPanel 내에서 구성요소를 순서대로나 병렬로 정리하기 위해 사용할 수 있는 SequentialClusterParallelCluster도 포함됩니다.

진행 표시줄, 검색 텍스트 필드, 축소판 이미지 목록 및 선택된 이미지 표시 영역의 최대 너비 및 높이를 설정하기 위해 JavaFX 스크립트 Layout 구성요소도 활용하기로 결정했습니다. Layout 구성요소는 UNLIMITED_SIZE 등의 레이아웃 관련 상수를 제공합니다.

UI의 두 번째 버전은 다음 JFXImageSearchUI.fx 파일에 코딩되었습니다.


   package com.sun.demo.jfx;

import javafx.ext.swing.Component;
import javafx.ext.swing.SwingFrame;
import javafx.ext.swing.Label;
import javafx.ext.swing.TextField;
import javafx.ext.swing.List;
import javafx.ext.swing.Cluster;
import javafx.ext.swing.Layout;
import javafx.ext.swing.ClusterPanel;
import javafx.ext.swing.SequentialCluster;
import javafx.ext.swing.ParallelCluster;
import javafx.scene.paint.Color;
import java.awt.Dimension;
import java.lang.System;

import javax.swing.border.LineBorder;

function createProgressBar() :Component {
var jprogressbar = new javax.swing.JProgressBar();
var comp = Component.fromJComponent(jprogressbar);
comp.hmax = Layout.UNLIMITED_SIZE;
comp;
}

var searchLabel = Label {
text: "Search:"

}
var searchTextField = TextField {
hmax: Layout.UNLIMITED_SIZE
columns: 50
}

var matchedImageLabel = Label {
text: "Matched Images"
}
var matchedImagePB = createProgressBar();


var thumbnailList = List {
preferredSize:[300, 230]
hmax: Layout.UNLIMITED_SIZE
vmax: Layout.UNLIMITED_SIZE
}

var selectedImageLabel = Label {
text: "Selected Image"
}
var selectedImagePB = createProgressBar();

var selectedImageDisplay = Label {
preferredSize: [400,300]
hmax: Layout.UNLIMITED_SIZE
vmax: Layout.UNLIMITED_SIZE
}

selectedImageDisplay.getJComponent().setOpaque(true);
selectedImageDisplay.getJComponent().setBackground(Color.WHITE.getAWTColor());
selectedImageDisplay.getJComponent().setBorder(new LineBorder(Color.BLACK.getAWTColor(), 1, true));

SwingFrame {
title: "JavaFX Image Search"
content:
// main panel within the frame
ClusterPanel {

hcluster: SequentialCluster {
content: [
ParallelCluster{ // mainCol
resizable: true
content: [
SequentialCluster{
content: [
ParallelCluster { //searchLabelCol
content: searchLabel
},
ParallelCluster { //searchTextFieldCol
resizable: true
content: searchTextField
}
]},
SequentialCluster {
content: [
ParallelCluster { //lblCol
content: matchedImageLabel
},
ParallelCluster { //progressBarCol
resizable: true
content: matchedImagePB
}
]},
thumbnailList,
SequentialCluster {
content: [
ParallelCluster { //lblCol
content: selectedImageLabel
},
ParallelCluster { //progressBarCol
resizable: true
content: selectedImagePB
}
]},
selectedImageDisplay
]
}
]

}

vcluster: SequentialCluster {
content: [
ParallelCluster{ //searchRow
content: SequentialCluster {
content: [
ParallelCluster { // row
content: [searchLabel, searchTextField]
}
]
}
},
ParallelCluster{ // matchedProgressRow
content: SequentialCluster {
content: [
ParallelCluster { // row
content: [matchedImageLabel, matchedImagePB]
}
]
}
},
ParallelCluster{ // thumbNailRow
resizable:true
content: thumbnailList
},
ParallelCluster{ // selectedProgressRow
content: SequentialCluster {
content: [
ParallelCluster { // row
content: [selectedImageLabel, selectedImagePB]
}
]
}
},
ParallelCluster{ // imageRow
content: selectedImageDisplay
}

]
}

}
visible: true
}


 
이 코드의 결과는 훨씬 보기 좋습니다.
그림 4에 나온 것처럼 구성요소는 이제 프레임의 왼쪽과 오른쪽으로 완벽하게 정렬되었습니다.

그림 4. JavaFX 이미지 검색 UI -- 두 번째 시도

프레임의 주 컨텐츠는 ClusterPanel 구성요소입니다. ClusterPanel 내에서 SequentialCluster 구성요소 그룹의 구성요소는 모두 순서대로 있습니다. ParallelCluster 구성요소 그룹의 구성요소는 병렬로 있습니다. 예를 들어 다음은 구성요소를 프레임의 상단 영역에 정리합니다. 검색 레이블과 검색 텍스트 필드를 병렬 열로 클러스터합니다.


 
 hcluster: SequentialCluster {
      content: [
          ParallelCluster{ // mainCol
              resizable: true
              content: [
                  SequentialCluster{
                      content: [
                          ParallelCluster {  //searchLabelCol
                              content: searchLabel
                          },
                          ParallelCluster { //searchTextFieldCol
                              resizable: true
                              content: searchTextField
                          }
              ]},
 

SequentialClusterSequentialClusterParallelCluster와 같은 ClusterElement 구성요소를 지정할 수 있는 content 속성을 갖습니다. ParallelCluster 구성요소는 크기 조정이 가능하므로 필요에 따라 프레임 너비를 채웁니다.

인터페이스의 나머지도 같은 패턴을 따릅니다. 인터페이스의 각 5개 영역 내에서 구성요소는 SequentialCluster 구성요소 내에서 ParallelCluster 구성요소를 사용하여 그룹화 및 정리됩니다. 예를 들어 다음 코드는 매칭되는 이미지 레이블과 매칭되는 이미지 진행 표시줄을 병렬 열로 클러스터합니다. 클러스터 다음에는 검색과 매칭되는 이미지의 축소판 목록이 나옵니다.


 
SequentialCluster {
      content: [
          ParallelCluster {  //lblCol
              content: matchedImageLabel
          },
          ParallelCluster { //progressBarCol
              resizable: true
              content: matchedImagePB
          }
      ]},
  thumbnailList,
 

한 가지 주목할 점은 javafx.ext.swing 패키지의 Label 구성요소가 테두리 특성, 불투명도 또는 배경 색상의 설정을 위한 속성을 현재 지원하지 않는다는 것입니다. 따라서 이들 설정을 위해 getJComponent 함수에 대한 Label 구성요소의 지원을 활용했습니다. 함수는 JavaFX 스크립트 구성요소로 캡슐화된 Swing jComponent를 반환합니다. 이 경우에는 Swing Label 구성요소를 반환합니다. 다음 코드를 사용하여 필요한 레이블 속성을 정의할 수 있습니다.


var selectedImageDisplay = Label {
         ...
  }

  selectedImageDisplay.getJComponent().setOpaque(true);
  selectedImageDisplay.getJComponent().setBackground(Color.WHITE.getAWTColor());
  selectedImageDisplay.getJComponent().setBorder(new LineBorder(Color.BLACK.getAWTColor(), 1, true));
 

설계 도구 없이 이미지 검색 사용자 인터페이스를 복제하는 것에 대해 처음에 걱정했던 것과 달리 가장 유용하고 필요한 기능은 빠르고 쉽게 액세스가 가능합니다. 또한 NetBeans IDE용 JavaFX 플러그인은 구성요소를 사용하여 작업할 때 사용 가능한 속성을 띄우는 문맥 인식 코드 완성을 제공합니다. 코드 완성을 사용하면 UI의 구현이 처음 생각처럼 어렵지만은 않습니다. 그림 5는 IDE에서 Ctrl+spacebar를 입력할 때 활성화되는 일부 팝업 옵션을 보여줍니다.


그림 5. 옵션


요약

UI 생성을 위한 선언적 구문을 살펴보기 위해 기존 애플리케이션의 UI를 이식했습니다. 원래 애플리케이션에서는 구성요소의 배치 및 정렬에 Swing의 GroupLayout을 사용했습니다. NetBeans IDE 6.1은 아직 JavaFX 스크립트를 위한 그래픽 설계 도구를 지원하지 않지만 수동으로 인터페이스를 레이아웃하는 것은 처음 걱정했던것 만큼 어렵지 않았습니다. ClusterPanel, SequentialClusterParallelCluster 구성요소의 조합을 통해 JavaFX 스크립트 구현은 원래 UI와 사실상 똑같아 보입니다. 이들 조합에 NetBeans IDE용 JavaFX 플러그인 및 문맥 인식 코드 완성이 더해져 작업은 더욱 쉬워졌습니다.


자세한 정보


이 글의 영문 원본은
# Learning Curve Journal, Part 2: Declarative User Interfaces
에서 보실 수 있습니다.

"Java FX" 카테고리의 다른 글

Posted by 1010
98..Etc/JavaFX2008. 11. 12. 17:28
반응형

2007년 8월과 9월에 썬 개발자 네트워크의 John O'Conner는 JavaFX 스크립트 프로그래밍 언어(이 기사에서는 JavaFX 스크립트라고 줄여서 부름)를 시작하는 사용자에게 도움을 주고자 "학습 곡선 일지(Learning Curve Journal)"라는 제목의 시리즈를 기고했습니다.

그 이후로 이 언어의 많은 중요한 부분이 개선되었습니다. 아마도 가장 중요한 변화는 JavaFX 스크립트의 초기 인터프리터 기반 버전을 대신하여 컴파일러 기반 버전을 사용할 수 있게 되었다는 점입니다. 이전의 학습 곡선 일지에서는 인터프리터 기반 버전 사용에 대해 설명했습니다. 업데이트된 학습 곡선 일지에서는 컴파일러 기반 버전의 언어 사용법을 보여줍니다. 최신 내용을 반영하여 다른 변경 사항도 적용되었습니다.

이 전과 마찬가지로 시리즈의 1편은 JavaFX 프로그램, 즉 JavaFX 스크립트 언어로 쓰여진 간단한 프로그램으로 시작합니다. JavaFX 스크립트에서의 프로그래밍을 위한 환경 설정 방법과 JavaFX 프로그램 빌드 및 실행 방법을 배우게 됩니다. 2편은 JavaFX 스크립트에서 사용 가능한 선언적 코딩 스타일에 중점을 둡니다. 이러한 스타일이 어떻게 그래픽 애플리케이션을 더욱 단순하고 직관적으로 만들 수 있는지 볼 수 있을 것입니다. 3편에서는 JavaFX 프로그램에서 작업 구현을 위한 JavaFX 스크립트 함수의 사용 방법을 보여줍니다. 4편에서는 웹 서비스 액세스를 위한 JavaFX 스크립트 사용 방법을 보여줍니다. 그 과정 중에 FX 스크립트에서 Swing 클래스와 같은 자바 기술 슬래스의 액세스가 얼마나 쉬운지도 보여줍니다.

JavaFX 스크립트는 개발자가 동적인 그래픽 컨텐츠 생성에 사용할 수 있는 새로운 스트립팅 언어입니다. 데스크탑에서 언어는 Swing 사용자 인터페이스(UI) 툴킷과 자바 2D API를 편리하게 사용할 수 있는 라이브러리를 제공합니다. Swing 또는 자바 2D를 대체하는 것이 아니고 풍부한 컨텐츠 개발자가 이들 API에 더욱 쉽게 액세스할 수 있도록 하는 것이 목적입니다. 모바일 시스템과 같은 다른 환경에서 JavaFX 스크립트는 Swing 이외의 사용자 인터페이스 기술을 사용합니다. JavaFX 스크립트를 사용하여 여러 가지 플랫폼과 운영 환경에서 실행되는 시각적으로 풍부한 애플리케이션을 만들 수 있습니다.

언어는 선언적 및 절차적 구문을 모두 제공합니다. 선언적으로 풍부한 사용자 인터페이스를 만든 다음 이벤트 처리 루틴과 작업을 추가할 수 있습니다.

그러나 대부분의 사용자는 좀 더 소박하게 시작해야 하며 이 기사의 목적도 그러합니다. 목표는 JavaFX 스크립트를 시작하는 방법을 보여주는 것입니다. 먼저 다음이 필요합니다.



자바 플랫폼 설정

개발자라면 물론 시스템에 JDK가 설치되어 있을 것입니다. 그러나 시스템을 한동안 업데이트하지 않은 경우에는 자바 SE 6이 설치되어 있는지 확인하십시오. 학습 곡선 일지는 JavaFX 스크립트의 컴파일러 기반 버전과 NetBeans IDE 6.1에서의 지원에 중점을 두고 있습니다. JavaFX 기술이 적용된 NetBeans IDE 6.1을 설치 및 사용하려면 시스템에 자바 SE 6의 최신 수준(현재는 자바 SE 6 업데이트 10 베타)을 설치하는 것이 좋습니다. 썬 개발자 네트워크의 자바 SE 다운로드 페이지에서 최신 JDK를 다운로드합니다. Mac OS X를 사용하는 경우에는 Apple Developer Connection의 자바 섹션에서 직접 Apple의 최신 자바 플랫폼 개발 키트(현재는 Mac OS X 10.5, 업데이트 1용 자바)를 다운로드 받을 수 있습니다.


자료 참조

새로운 환경이나 언어를 경험할 때는 교착 상태에 빠지거나 난관에 봉착하게 됩니다. 이는 최첨단 기술을 사용할 때 모두가 겪게 되는 과정입니다. 학습 곡선을 원만하게 하기 위해서는 훌륭한 문서와 예제가 매우 중요합니다. 썬 개발자 네트워크의 JavaFX 기술 허브와 함께 javafx.comProject OpenJFX 웹 사이트는 정확한 정보를 얻을 수 있는 최신 문서와 데모 자료를 제공합니다.

일 부 사용자들은 언어 참조 자료를 읽지도 않고 프로그래밍을 바로 시작하고자 할 수 있습니다. 또다른 사용자들은 JavaFX 스크립트를 실제로 사용하기 전에 모든 자료를 읽을 것입니다. 바로 시작하는 유형의 사용자더라도 일종의 언어 사양이나 자습서부터 시작해야 합니다. 전형적인 "Hello, world" 예제를 쓰기 전에 기본 언어 구문을 알아야 합니다. JavaFX 참조 페이지의 문서부터 시작하는 것이 좋습니다. 여기에서 JavaFX 스크립트 프로그래밍 언어 참조 자료 등의 참조 문서와 JavaFX 기술 시작하기NetBeans IDE를 사용하여 간단한 JavaFX 애플리케이션 생성 등의 많은 기사와 자습서로의 링크를 찾을 수 있습니다.


JavaFX 애플리케이션 생성

일부 언어 참조 문서를 읽었다면 이제는 간단한 JavaFX 애플리케이션을 만들어 볼 차례입니다. 명령줄에서 수동으로 JavaFX 애플리케이션 빌드 및 실행이 가능하긴 하지만 애플리케이션 개발을 단순화하는 많은 기능을 가진 NetBeans IDE 6.1를 사용해 봅시다. NetBeans용 JavaFX 플러그인을 설치해야 합니다.

NetBeans IDE 6.1을 설치하지 않은 경우에는 NetBeans IDE 6.1과 NetBeans용 JavaFX을 포함하는 하나의 패키지인 JavaFX 포함 NetBeans IDE 6.1 다운로드가 가능합니다. NetBeans IDE 6.1을 이미 설치한 경우에는 NetBeans 업데이트 센터에서 JavaFX 플러그인을 설치하여 JavaFX 기술 지원을 추가할 수 있습니다. NetBeans용 JavaFX는 현재 Windows 및 Mac OS/X 환경에서 사용 가능합니다. JavaFX 플러그인을 설치하면 NetBeans IDE 6.1을 사용하여 JavaFX 스크립트의 컴파일러 기반 버전으로 작성된 애플리케이션을 생성, 테스트, 디버그 및 배포할 수 있습니다. 플러그인은 JavaFX 스크립트 파일 포함을 위한 프로젝트 및 편집기 지원을 향상시킵니다. 또한 스크립트 엔진 및 라이브러리를 위한 코어 라이브러리도 제공합니다.

JavaFX 포함 NetBeans IDE 6.1이나 NetBeans용 JavaFX 플러그인을 설치했으면 첫 번째 JavaFX 애플리케이션을 빌드할 준비가 된 것입니다. 물론 "Hello, world!"부터 시작해야겠지요.

다음과 같이 프로젝트 생성을 시작합니다.

  1. 주 메뉴에서 File -> New Project를 선택합니다.
  2. New Project 마법사에서 JavaFX 범주와 JavaFX Script Application 프로젝트 유형을 선택합니다.
  3. Next 버튼을 클릭합니다.
  4. HelloWorldJFX와 같이 프로젝트의 이름을 지정합니다.
  5. 기본 프로젝트 위치를 수락하거나 다른 위치를 선택하여 이동합니다.
  6. Create Main Class 확인란을 선택된 상태로 두고 다른 기본 설정도 변경하지 않습니다.
  7. Finish 버튼을 클릭합니다.

그림 1과 같이 IDE는 지정된 프로젝트 폴더에 프로젝트 디렉토리를 생성하고 프로젝트 이름과 같은 HelloWorldJFX라는 이름을 부여합니다. HelloWorldJFX 프로젝트를 확장합니다. Source Packages 노드의 helloworldjfx 패키지 아래에 Main.fx 클래스 파일이 있습니다. IDE는 프로젝트 생성 시 Create Main Class 확인란이 선택되어 있었으므로 Main.fx 파일을 생성합니다. 이 파일에 애플리케이션의 소스 코드가 들어갑니다.


그림 1.
HelloWorldJFX 프로젝트 파일


  /*
   * Main.fx
   *
   * Created on ...
   */

  package helloworldjfx;

  /**
   * @author ...
   */

  // place your code here
 

// place your code here 라인을 다음 코드로 바꿉니다.

  import javafx.ext.swing.Label;

  Label {
      text: "Hello, world!"
  }
 


JavaFXScript 편집기는 기본 서식 설정과 코드 완성을 제공합니다. 우리와 같이 JavaFX 스크립트에 익숙하지 않은 프로그래머는 언어 구문에 확신이 들지 않을 때도 있으므로 코드 완성이 도움이 됩니다. Ctrl + Space 키를 누르면 편집기에서 코드 완성이 활성화됩니다.

또 한 JavaFX 스크립트 플러그인은 컴파일과 실행을 해 볼 필요 없이 애플리케이션의 결과를 볼 수 있는 미리보기 기능을 제공합니다. 소스 파일에 변경한 내용은 즉시 미리보기 창에 반영됩니다. 미리보기 기능은 현재 Mac OS X 플랫폼에서는 사용할 수 없습니다.

Enable Preview 버튼 을 클릭하여 미리보기 기능을 작동시킵니다. 그림 2와 같이 편집기 바로 위에서 출력을 볼 수 있습니다.



그림 2. 기본적인 "Hello, world!"

별로 놀라지 않으셨나요? 좋습니다. 설정 방법을 보여주려는 것 뿐이었지만 좀 더 재미있는 것을 해보도록 하겠습니다. JavaFX 환경은 모든 Swing UI 구성요소를 구현하므로 레이블에만 한정될 필요는 없습니다. 버튼이나 대화 상자 같은 다른 위젯을 사용할 수도 있습니다.

다음은 버튼의 이벤트 핸들러를 소개하는 예제입니다. 언어 참조 자료에서 actionfunction 구문을 읽었으면 버튼을 누를 때 메시지 상자가 표시되도록 해봅시다.


  import javafx.ext.swing.SwingFrame;
   
import javafx.ext.swing.Button;
   
import javafx.ext.swing.SwingDialog;
   
import javafx.ext.swing.Label;
 
   
SwingFrame {
       content
: Button {
           text
: "Press me!"
           action
: function() {
               
SwingDialog {
                   title
: "You pressed me"
                   content
: Label{ text: "Hey, don't do that!"}
                   visible
: true
               
}
       
}
   
}
 
   visible
: true
   
}



Main.fx 파일에 이를 입력한 후에 Press me! 버튼을 누르면 그림 3과 같은 결과가 나타납니다.

그림 3. "Press me!" 버튼 메시지


앞에서 말한 것처럼 미리보기 기능을 사용하여 코드를 컴파일 및 실행하지 않고도 애플리케이션의 결과를 볼 수 있습니다. 코드를 컴파일하려는 경우에는 Project 창에서 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 Build Project를 선택합니다. 애플리케이션을 실행하려면 Project 창에서 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 Run Project를 선택합니다.


구성요소 기반에서 노드 기반 UI로의 이동

이전 예제에서 Frame과 Dialog의 구성요소가 간단하게 FrameDialog가 아니라 SwingFrameSwingDialog라 는 클래스 이름을 갖는지 궁금하셨을지도 모릅니다. 그 답은 JavaFX 스크립트 개발자가 Swing 기반 구성요소의 계층 구조를 사용하는 기존 접근 방법 대신 노드 기반 접근 방법을 사용하여 UI를 구축하는 향후의 접근 방법에 관련되어 있습니다. 사실 이러한 향후의 접근 방법에 대한 초기 지원은 이미 JavaFX 라이브러리에 포함되어 있습니다. 예를 들면 javafx.application 패키지에는 노드 기반 접근 방법을 지원하는 Frame, DialogWindow 등의 클래스가 포함됩니다. 다른 접근 방법을 지원하는 클래스와의 혼동을 피하기 위해 javafx.application 패키지에 해당 항목이 있는 javafx.ext.swing 패키지는 클래스 이름에 접두어 "Swing"을 추가했습니다.

학습 곡선 시리즈에서는 계층 구조적인 Swing 기반 접근 방법을 사용했지만 javafx.ext.swing 패키지의 SwingFrame, SwingDialogSwingWindow와 같은 클래스는 임시적입니다. JavaFX 팀은 Swing 구성요소와 유사하지만 더욱 유연하고 강력한 새로운 노드 기반 구성요소 집합을 설계 중입니다.

노드 기반 접근 방법을 사용한 UI 구축에 대한 자세한 내용은 장면 그래프를 사용하여 JavaFX 스크립트에서 비주얼 개체 표시 기사를 참조하십시오. NetBeans IDE를 사용하여 간단한 JavaFX 애플리케이션 생성 자습서도 노드 기반 접근 방법을 사용하는 JavaFX 애플리케이션을 설명합니다.


프로파일

JavaFX는 특정 플랫폼이나 장치에서만 사용 가능한 클래스 그룹을 의미하는 프로파일을 지원합니다. javafx.ext.swing 패키지의 클래스는 데스크탑 프로파일에 들어 있으며 데스크탑 환경에서만 가용성이 보장됩니다. 예를 들어 많은 휴대 전화는 Swing 클래스를 갖지 않습니다. 휴대 전화와 TV를 포함한 모든 플랫폼에서 보장되는 클래스를 정의한 일반 프로파일도 있습니다. 새로운 노드 기반 구성요소는 일반 프로파일에 있으므로 모든 화면 및 장치에서 작동합니다.

JavaFX API 문서는 정확한 프로파일 사용을 보장하기 위해 하나의 프로파일에서 다른 프로파일로 전환할 수 있도록 하는 버튼을 제공합니다. 이 기사 시리즈에서는 데스크탑 환경을 위한 애플리케이션을 구축하므로 데스크탑 프로파일의 클래스와 일반 프로파일의 일부 클래스를 사용할 것입니다.


명령줄에서 JavaFX 애플리케이션 빌드 및 실행

다음과 같이 명령줄에서 JavaFX 애플리케이션을 빌드 및 실행할 수 있습니다.

  1. JavaFX Preview SDK 다운로드를 받습니다. SDK에는 JavaFX 스크립트 컴파일러, 문서, 런타임, 라이브러리 및 코드 샘플이 포함됩니다.
  2. 다운로드한 패키지를 확장합니다. 여러 디렉토리 중에 javafxcjavafx 명령을 위한 실행 가능 파일을 포함하는 bin 디렉토리가 보여야 합니다.
  3. fx 확장자를 갖는 파일(예: MyApp.fx)에 애플리케이션의 소스 코드를 저장합니다
  4. 다음과 같이 javafxc 명령을 수행하고 소스 파일을 지정하여 애플리케이션을 컴파일합니다.
       javafxc MyApp.fx

    애플리케이션을 위한 클래스 파일이 생성됩니다.

  5. 다음과 같이 javafx 명령을 수행하고 클래스 파일을 지정하여 애플리케이션을 위한 클래스 파일을 실행합니다.
       javafx MyApp

요약

새로운 기술을 조사할 때는 올바르게 시작하는 것이 중요합니다. 정확한 정보와 도구를 사용하여 시작하도록 하십시오. JavaFX 스크립트에 대한 최선의 방법은 4단계 절차를 따르는 것입니다.

  1. 최신의 Java SE Development Kit를 받습니다.
  2. 정보의 출처로 JavaFX 기술 허브, javafx.com 사이트Project OpenJFX 사이트를 사용합니다.
  3. IDE용 개발 플러그인을 받습니다. JavaFX 포함 NetBeans IDE 6.1은 새로운 JavaFX 스크립트의 컴파일러 기반 버전을 지원합니다. NetBeans IDE 6.1을 이미 설치한 경우에는 NetBeans 업데이트 센터에서 JavaFX 플러그인을 설치하여 JavaFX 기술 지원을 추가할 수 있습니다.
  4. 첫 번째 스크립트 시험에 미리보기 기능을 사용합니다.


자세한 정보

이 시리즈의 2편인 선언적 사용자 인터페이스를 참조하십시오.



이 글의 영문 원본은
Learning Curve Journal, Part 1: Exploring JavaFX Script
에서 보실 수 있습니다.


"Java FX" 카테고리의 다른 글

2008/08/27 10:05 2008/08/27 10:05
Posted by 1010
98..Etc/JavaFX2008. 11. 12. 17:27
반응형

2008년 7월 저는 그래픽 디자이너(Mark Dingman of Malden Labs)와 공동 작업으로 상상 속의 Sound Beans 애플리케이션을 만드는 연속 게시물이 포함된 JFX 사용자 지정 노드 카테고리를 시작했습니다.  이 애플리케이션을 작성하는 목표는 JavaFX 사용자 지정 노드를 만드는 방법을 보여주고, 그래픽 디자이너와 애플리케이션 개발자가 공동으로 JavaFX 애플리케이션을 효과적으로 개발할 수 있는 방법에 관한 사례 연구를 제공하기 위한 것입니다. 

이 연속 게시물의 첫 게시물인 자신만의 JavaFX "사용자 지정 노드" 만들기: 그래픽 메뉴의 예는 JavaFX에서 자신만의 UI를 만드는 방법을 보여줍니다. 해당 게시물에서는 마우스를 갖다댈 경우 밝아지고 확장되는 버튼으로 구성된 메뉴를 쉽게 만들 수 있도록 MenuNodeButtonNode 사용자 지정 노드를 정의했습니다.  이어지는 다음 게시물에서는 다음 내용을 다룹니다.

오늘의 게시물에서는 테이블의 행을 보고 선택할 수 있는 확장 가능한 테이블을 제공할 수 있도록 TableNode라는 이름의 사용자 지정 노드를 작성해보겠습니다. 테이블의 각 셀에는 Node의 하위 클래스를 포함할 수 있으므로 JavaFX SDK 1.0에서 사용할 노드 중심 방식으로 줄에 놓여질 수 있습니다. 또한, JavaFX SDK 1.0에 일종의 테이블 UI 컨트롤을 포함하려고 생각하고 있습니다. 다음은 상상 속의 Sound Beans 프로그램에서 사용하는 TableNode의 스크린샷입니다.

테이블


이것은 Mark Dingman이 제공한 재생 목록 comp(웹 사이트에 대한 종합 이미지, 모형)를 기초로 만든 것입니다(Getting Decked: Another JavaFX Custom Node post 참조). 그 다음에는 Mark에게 모양을 그려(이미지를 사용하는 것과는 다름) 구현할 수 있는 스크롤바 comp를 부탁했습니다. Mark의 comp에는 위에서 보듯이 수평 스크롤바 트랙과 함께 둥근 사각형 모양의 진행률 스크롤바 썸이 있습니다.

상상 속의 Sound Beans 프로그램에 대한 이번 반복에서는 테이블에서 다른 행을 클릭하면 UI의 왼쪽 위 모서리의 숫자가 변경되어 TableNodeselectedIndex 속성을 바인딩할 수 있음을 나타냅니다. 다음의 반복에서는 앨범 그래픽, 제목 등이 바뀌게 하고 왼쪽 위 모서리에 해당 번호가 나타나도록 할 것입니다. 이 Java Web Start 링크를 클릭하여 이 코드를 사용해보십시오. JRE 6 이상이 필요합니다. 또한 자바 SE 6 업데이트 10을 설치하면 배포 시간이 단축됩니다.

Web

다음은 TableNode.fx 파일에 있는 TableNode 사용자 지정 노드의 코드입니다.

/*
*  TableNode.fx -
*  A custom node that contains rows and columns, each cell
*  containing a node.
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to demonstrate how to create custom nodes in JavaFX
*/


package com.javafxpert.custom_node;

import javafx.input.*;
import javafx.scene.*;
import javafx.scene.geometry.*;
import javafx.scene.paint.*;
import javafx.scene.transform.*;
import java.lang.System;

/*
* A custom node that contains rows and columns, each cell
* containing a node.  Column widths may be set individually,
* and the height of the rows can be set.  In addition, several
* other attributes such as width and color of the scrollbar
* may be set.  The scrollbar will show only when necessary,
* and overlays the right side of each row, so the rightmost
* column should be given plenty of room to display data and
* a scrollbar.
*/

public class TableNode extends CustomNode {

 
/*
   * Contains the height of the table in pixels.
   */

 
public attribute height:Integer = 200;
   
 
/*
   * Contains the height of each row in pixels.
   */

 
public attribute rowHeight:Integer;
   
 
/*
   * A sequence containing the column widths in pixels.  The
   * number of elements in the sequence determines the number of
   * columns in the table.
   */

 
public attribute columnWidths:Integer[];
   
 
/*
   * A sequence containing the nodes in the cells.  The nodes are
   * placed from left to right, continuing to the next row when
   * the current row is filled.
   */

 
public attribute content:Node[];
   
 
/*
   * The selected row number (zero-based)
   */

 
public attribute selectedIndex:Integer;
   
 
/*
   * The height (in pixels) of the space between rows of the table.
   * This space will be filled with the tableFill color.
   */

 
public attribute rowSpacing:Integer = 1;
   
 
/*
   * The background color of the table
   */

 
public attribute tableFill:Paint;
   
 
/*
   * The background color of an unselected row
   */

 
public attribute rowFill:Paint;
   
 
/*
   * The background color of a selected row
   */

 
public attribute selectedRowFill:Paint;
   
 
/*
   * The color or gradient of the vertical scrollbar.
   */

 
public attribute vertScrollbarFill:Paint = Color.BLACK;
   
 
/*
   * The color or gradient of the vertical scrollbar thumb.
   */

 
public attribute vertScrollbarThumbFill:Paint = Color.WHITE;
   
 
/*
   * The width (in pixels) of the vertical scrollbar.
   */

 
public attribute vertScrollbarWidth:Integer = 20;
   
 
/*
   * The number of pixels from the left of a cell to place the node
   */

 
private attribute cellHorizMargin:Integer = 10;
   
 
/*
   * Contains the width of the table in pixels.  This is currently a
   * calculated value based upon the specified column widths
   */

 
private attribute width:Integer = bind
    computePosition
(columnWidths, sizeof columnWidths);
   
 
private function computePosition(sizes:Integer[], element:Integer) {
   
var position = 0;
   
if (sizeof sizes > 1) {
     
for (i in [0..element - 1]) {
        position
+= sizes[i];
     
}
   
}
   
return position;
 
}
 
 
/**
   * The onSelectionChange function attribute that is executed when the
   * a row is selected
   */

 
public attribute onSelectionChange:function(row:Integer):Void;
   
 
/**
   * Create the Node
   */

 
public function create():Node {
   
var numRows = sizeof content / sizeof columnWidths;
   
var tableContentsNode:Group;
   
var needScrollbar:Boolean = bind (rowHeight + rowSpacing) * numRows  > height;
   
Group {
     
var thumbStartY = 0.0;
     
var thumbEndY = 0.0;
     
var thumb:Rectangle;
     
var track:Rectangle;
     
var rowRef:Group;
      content
: [
       
for (row in [0..numRows - 1], colWidth in columnWidths) {
         
Group {
            transform
: bind
             
Translate.translate(computePosition(columnWidths, indexof colWidth) +
                                  cellHorizMargin
,
                                 
((rowHeight + rowSpacing) * row) + (-1.0 * thumbEndY *
                                 
((rowHeight + rowSpacing) * numRows) / height))
            content
: bind [
             
Rectangle {
                width
: colWidth
                height
: rowHeight
                fill
: if (indexof row == selectedIndex)
                        selectedRowFill
                     
else
                        rowFill
             
},
             
Line {
                startX
: 0
                startY
: 0
                endX
: colWidth
                endY
: 0
                strokeWidth
: rowSpacing
                stroke
: tableFill
             
},
              rowRef
= Group {
               
var node =
                  content
[indexof row * (sizeof columnWidths) + indexof colWidth];
                transform
: bind Translate.translate(0, rowHeight / 2 -
                                                       node
.getHeight() / 2)
                content
: node
             
}
           
]
            onMouseClicked
:
             
function (me:MouseEvent) {
                selectedIndex
= row;
                onSelectionChange
(row);
             
}
         
}
       
},
       
// Scrollbar
       
if (needScrollbar)
         
Group {
            transform
: bind Translate.translate(width - vertScrollbarWidth, 0)
            content
: [
              track
= Rectangle {
                x
: 0
                y
: 0
                width
: vertScrollbarWidth
                height
: bind height
                fill
: vertScrollbarFill
             
},
             
//Scrollbar thumb
              thumb
= Rectangle {
                x
: 0
                y
: bind thumbEndY
                width
: vertScrollbarWidth
                height
: bind 1.0 * height / ((rowHeight + rowSpacing) * numRows) * height
                fill
: vertScrollbarThumbFill
                arcHeight
: 10
                arcWidth
: 10
                onMousePressed
: function(e:MouseEvent):Void {  
                  thumbStartY
= e.getDragY() - thumbEndY;  
               
}  
                onMouseDragged
: function(e:MouseEvent):Void {
                 
var tempY = e.getDragY() - thumbStartY;
                 
// Keep the scroll thumb within the bounds of the scrollbar
                 
if (tempY >=0 and tempY + thumb.getHeight() <= track.getHeight()) {
                    thumbEndY
= tempY;  
                 
}
                 
else if (tempY < 0) {
                    thumbEndY
= 0;
                 
}
                 
else {
                    thumbEndY
= track.getHeight() - thumb.getHeight();
                 
}
               
}
                onMouseDragged
: function(e:MouseEvent):Void {
                 
var tempY = e.getDragY() - thumbStartY;
                 
// Keep the scroll thumb within the bounds of the scrollbar
                 
if (tempY >=0 and tempY + thumb.getHeight() <= track.getHeight()) {
                    thumbEndY
= tempY;  
                 
}
                 
else if (tempY < 0) {
                    thumbEndY
= 0;
                 
}
                 
else {
                    thumbEndY
= track.getHeight() - thumb.getHeight();
                 
}
               
}
             
}
           
]
         
}  
       
else
         
null
     
]
      clip
:
       
Rectangle {
          width
: bind width
          height
: bind height
       
}
      onMouseWheelMoved
: function(e:MouseEvent):Void {
       
var tempY = thumbEndY + e.getWheelRotation() * 4;
       
// Keep the scroll thumb within the bounds of the scrollbar
       
if (tempY >=0 and tempY + thumb.getHeight() <= track.getHeight()) {
          thumbEndY
= tempY;  
       
}
       
else if (tempY < 0) {
          thumbEndY
= 0;
       
}
       
else {
          thumbEndY
= track.getHeight() - thumb.getHeight();
       
}
     
}
   
}    
 
}
}
 

public 속성에서 볼 수 있듯이, 테이블 높이, 행 높이, 각 열의 폭, 다양한 UI 요소의 색 또는 그라데이션 등 개발자가 구성할 수 있는 여러 가지 TableNode 속성이 있습니다. 목록 끝의 코드는 마우스 휠 지원을 제공합니다. 이제 The "Play" page로 주석 처리한 섹션을 중심으로 기본 프로그램을 살펴보겠습니다. 이 섹션이 TableNodeExampleMain.fx 파일에 TableNode 인스턴스가 만들어지는 부분입니다.

/*
 *  TableNodeExampleMain.fx -
 *  An example of using the TableNode custom node.  It also demonstrates
 *  the ProgressNode, DeckNode, MenuNode and ButtonNode custom nodes
 *
 *  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
 *  to demonstrate how to create custom nodes in JavaFX
 */

package com.javafxpert.table_node_example.ui;

import javafx.application.*;
import javafx.ext.swing.*;
import javafx.scene.*;
import javafx.scene.geometry.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.transform.*;
import java.lang.Object;
import java.lang.System;
import com.javafxpert.custom_node.*;
import com.javafxpert.table_node_example.model.*;

var deckRef:DeckNode;

Frame {
 
var model = TableNodeExampleModel.getInstance();
 
var stageRef:Stage;
 
var menuRef:MenuNode;
  title
: "TableNode Example"
  width
: 500
  height
: 400
  visible
: true
  stage
:
    stageRef
= Stage {
      fill
: Color.BLACK
      content
: [
        deckRef
= DeckNode {
          fadeInDur
: 700ms
          content
: [
           
// The "Splash" page
           
Group {
             
var vboxRef:VBox;
             
var splashFont =
               
Font {
                  name
: "Sans serif"
                  style
: FontStyle.BOLD
                  size
: 12
               
};
              id
: "Splash"
              content
: [
               
ImageView {
                  image
:
                   
Image {
                      url
: "{__DIR__}images/splashpage.png"
                   
}
               
},
                vboxRef
= VBox {
                  translateX
: bind stageRef.width - vboxRef.getWidth() - 10
                  translateY
: 215
                  spacing
: 1
                  content
: [
                   
Text {
                      content
: "A Fictitious Audio Application that Demonstrates"
                      fill
: Color.WHITE
                      font
: splashFont
                   
},
                   
Text {
                      content
: "Creating JavaFX Custom Nodes"
                      fill
: Color.WHITE
                      font
: splashFont
                   
},
                   
Text {
                      content
: "Application Developer: Jim Weaver"
                      fill
: Color.WHITE
                      font
: splashFont
                   
},
                   
Text {
                      content
: "Graphics Designer: Mark Dingman"
                      fill
: Color.WHITE
                      font
: splashFont
                   
},
                 
]
               
}
             
]
           
},
           
// The "Play" page
           
VBox {
             
var tableNode:TableNode
              id
: "Play"
              spacing
: 4
              content
: [
               
Group {
                  content
: [
                   
ImageView {
                      image
:
                       
Image {
                          url
: "{__DIR__}images/playing_currently.png"
                       
}
                   
},
                   
Text {
                      textOrigin
: TextOrigin.TOP
                      content
: bind "{tableNode.selectedIndex}"
                      font
: Font {
                        size
: 24
                     
}
                   
}
                 
]
               
},
                tableNode
= TableNode {
                  height
: 135
                  rowHeight
: 25
                  rowSpacing
: 2
                  columnWidths
: [150, 247, 25, 70]
                  tableFill
: Color.BLACK
                  rowFill
: Color.rgb(28, 28, 28)
                  selectedRowFill
: Color.rgb(45, 45, 45)
                  selectedIndex
: -1
                  vertScrollbarWidth
: 20
                  vertScrollbarFill
: LinearGradient {
                    startX
: 0.0
                    startY
: 0.0
                    endX
: 1.0
                    endY
: 0.0
                    stops
: [
                     
Stop {
                        offset
: 0.0
                        color
: Color.rgb(11, 11, 11)
                     
},
                     
Stop {
                        offset
: 1.0
                        color
: Color.rgb(52, 52, 52)
                     
}
                   
]
                 
}
                  vertScrollbarThumbFill
: Color.rgb(239, 239, 239)
                  content
: bind
                   
for (obj in model.playlistObjects) {
                     
if (obj instanceof String)
                       
Text {
                          textOrigin
: TextOrigin.TOP
                          fill
: Color.rgb(183, 183, 183)
                          content
: obj as String
                          font
:
                           
Font {
                              size
: 11
                           
}
                       
}
                     
else if (obj instanceof Image)
                       
ImageView {
                          image
: obj as Image
                       
}
                     
else
                       
null
                   
}
                  onSelectionChange
:
                   
function(row:Integer):Void {
                     
System.out.println("Table row #{row} selected");
                   
}
               
}
             
]
           
},
           
// The "Burn" page
           
Group {
             
var vboxRef:VBox;
              id
: "Burn"
              content
: [
                vboxRef
= VBox {
                  translateX
: bind stageRef.width / 2 - vboxRef.getWidth() / 2
                  translateY
: bind stageRef.height / 2 - vboxRef.getHeight() / 2
                  spacing
: 15
                  content
: [
                   
Text {
                      textOrigin
: TextOrigin.TOP
                      content
: "Burning custom playlist to CD..."
                      font
:
                       
Font {
                          name
: "Sans serif"
                          style
: FontStyle.PLAIN
                          size
: 22
                       
}
                      fill
: Color.rgb(211, 211, 211)
                   
},
                   
ProgressNode {
                      width
: 430
                      height
: 15
                      progressPercentColor
: Color.rgb(191, 223, 239)
                      progressTextColor
: Color.rgb(12, 21, 21)
                      progressText
: bind "{model.remainingBurnTime} Remaining"
                      progressFill
:
                       
LinearGradient {
                          startX
: 0.0
                          startY
: 0.0
                          endX
: 0.0
                          endY
: 1.0
                          stops
: [
                           
Stop {
                              offset
: 0.0
                              color
: Color.rgb(0, 192, 255)
                           
},
                           
Stop {
                              offset
: 0.20
                              color
: Color.rgb(0, 172, 234)
                           
},
                           
Stop {
                              offset
: 1.0
                              color
: Color.rgb(0, 112, 174)
                           
},
                         
]
                       
}
                      barFill
:
                       
LinearGradient {
                          startX
: 0.0
                          startY
: 0.0
                          endX
: 0.0
                          endY
: 1.0
                          stops
: [
                           
Stop {
                              offset
: 0.0
                              color
: Color.rgb(112, 112, 112)
                           
},
                           
Stop {
                              offset
: 1.0
                              color
: Color.rgb(88, 88, 88)
                           
},
                         
]
                       
}
                      progress
: bind model.burnProgressPercent / 100.0
                   
},
                   
ComponentView {
                      component
:
                       
FlowPanel {
                          background
: Color.BLACK
                          content
: [
                           
Label {
                              text
: "Slide to simulate burn progress:"
                              foreground
: Color.rgb(211, 211, 211)
                           
},
                           
Slider {
                              orientation
: Orientation.HORIZONTAL
                              minimum
: 0
                              maximum
: 100
                              value
: bind model.burnProgressPercent with inverse
                              preferredSize
: [200, 20]
                           
}
                         
]
                       
}
                   
}
                 
]
               
}
             
]
           
},
           
// The "Config" page
           
Group {
              id
: "Config"
              content
: [
               
ImageView {
                  image
:
                   
Image {
                      url
: "{__DIR__}images/config.png"
                   
}
               
}
             
]
           
},
           
// The "Help" page
           
Group {
              id
: "Help"
              content
: [
               
ImageView {
                  image
:
                   
Image {
                      url
: "{__DIR__}images/help.png"
                   
}
               
}
             
]
           
}
         
]
       
},
        menuRef
= MenuNode {
          translateX
: bind stageRef.width / 2 - menuRef.getWidth() / 2
          translateY
: bind stageRef.height - menuRef.getHeight()
          buttons
: [
           
ButtonNode {
              title
: "Play"
              imageURL
: "{__DIR__}icons/play.png"
              action
:
               
function():Void {
                  deckRef
.visibleNodeId = "Play";
               
}
           
},
           
ButtonNode {
              title
: "Burn"
              imageURL
: "{__DIR__}icons/burn.png"
              action
:
               
function():Void {
                  deckRef
.visibleNodeId = "Burn";
               
}
           
},
           
ButtonNode {
              title
: "Config"
              imageURL
: "{__DIR__}icons/config.png"
              action
:
               
function():Void {
                  deckRef
.visibleNodeId = "Config";
               
}
           
},
           
ButtonNode {
              title
: "Help"
              imageURL
: "{__DIR__}icons/help.png"
              action
:
               
function():Void {
                  deckRef
.visibleNodeId = "Help";
               
}
           
},
         
]
       
}
     
]
   
}
}

deckRef
.visibleNodeId = "Splash";


The Model Behind the UI

"JavaFX 방식"은 UI 속성을 모델에 바인딩하는 것이므로 위에서 보는 바와 같이 TableNode의 콘텐츠 속성이 모델에 바인딩됩니다. 아래는 현재까지 TableNodeExampleModel.fx 파일에 만들어진 Sound Beans 프로그램의 모델입니다. playlistObjects 시퀀스에는 어떠한 종류의 개체도 포함할 수 있으며, Node 인스턴스를 모델에 포함하지만 않으면 됩니다(이러한 인스턴스는 UI에 속하므로). 그러므로 TableNode,를 채우려면 모델에 앨범 제목 및 이미지 URL 같은 문자열을 포함하는 방식을 사용합니다. 위에 표시된 TableModel의 콘텐츠 속성에 바인딩하는 동안 Node 하위 클래스(예: TextImageView)가 만들어집니다.

/*
 *  TableNodeExampleModel.fx -
 *  The model behind the TableNode example
 *
 *  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
 */

package com.javafxpert.table_node_example.model;

import java.lang.Object;
import javafx.scene.*;
import javafx.scene.image.*;
import javafx.scene.text.*;

/**
 * The model behind the TableNode example
 */

public class TableNodeExampleModel {
 
 
/**
   * The total estimated number of seconds for the burn.
   * For this example program, we'll set it to 10 minutes
   */

 
public attribute estimatedBurnTime:Integer = 600;

 
/**
   * The percent progress of the CD burn, represented by a number
   * between 0 and 100 inclusive.
   */

 
public attribute burnProgressPercent:Integer on replace {
     
var remainingSeconds = estimatedBurnTime * (burnProgressPercent / 100.0) as Integer;
      remainingBurnTime
= "{remainingSeconds / 60}:{%02d (remainingSeconds mod 60)}";
 
};

 
/**
   * The time remaining on the CD burn, expressed as a String in mm:ss
   */

 
public attribute remainingBurnTime:String;

 
/**
   * An image of a play button to be displayed in each row of the table
   */

 
private attribute playBtnImage = Image {url: "{__DIR__}images/play-btn.png"};
   
 
/**
   * The song information in the playlist
   */

 
public attribute playlistObjects:Object[] =
   
["Who'll Stop the Rain", "Three Sides Now", playBtnImage, "2:43",
     
"Jackie Blue", "Ozark Mountain Devils", playBtnImage, "2:15",
     
"Come and Get Your Love", "Redbone", playBtnImage, "3:22",
     
"Love Machine", "Miracles", playBtnImage, "2:56",
     
"25 or 6 to 4", "Chicago", playBtnImage, "3:02",
     
"Free Bird", "Lynard Skynard", playBtnImage, "5:00",
     
"Riding the Storm Out", "REO Speedwagon", playBtnImage, "3:00",
     
"Lay it on the Line", "Triumph", playBtnImage, "2:00",
     
"Secret World", "Peter Gabriel", playBtnImage, "4:00"];
 
 
 
 
//-----------------Use Singleton pattern to get model instance -----------------------
 
private static attribute instance:TableNodeExampleModel;

 
public static function getInstance():TableNodeExampleModel {
   
if (instance == null) {
      instance
= TableNodeExampleModel {};
   
}
   
else {
      instance
;
   
}
 
}
}


언제나처럼 질문이나 의견이 있으면 남겨 주십시오. 그리고 이 기사의 이미지를 다운로드한 후 이 그래픽으로 이 예에서 소개한 대로 작성하고 실행할 수 있습니다. 이 이미지는 프로젝트의 클래스 경로에서 확장할 수 있는 zip 파일입니다. 이 JFX 사용자 지정 노드 연속 게시물 중 이전 게시물에 소개했던 ButtonNode, MenuNode, DeckNode, ProgressNode 코드가 필요합니다.

감사합니다.
Jim Weaver
JavaFXpert.com

이 글의 영문 원본은
Creating a Custom Scrollable Table with JavaFX
에서 보실 수 있습니다.

"Java FX" 카테고리의 다른 글

Posted by 1010
98..Etc/JavaFX2008. 11. 12. 17:26
반응형

이제 JavaFX SDK 기술 Preview가 릴리스되었으므로 자신만의 "사용자 지정 노드"를 빠르게 만드는 방법을 설명하겠습니다. JavaFX에서 사용자 지정 노드는 위젯, 가젯, UI 구성 요소 등 어느 것이나 모두 의미할 수 있으나 목적은 동일합니다. 다시 사용 가능한 JavaFX 프로그램용 UI를 만들 수 있도록 하는 것입니다. 오늘의 예는 사용자 지정 노드(2개)를 만드는 방법을 보여줍니다. 다음 스크린샷을 참조하십시오.

 

그런데 현재 이 예에 구현된 코드가 단순해진 것은 Edgar Merino 덕분입니다. 이 코드를 사용해보려면 Java Web Start 링크를 클릭하십시오. JRE 6 이상이 필요합니다. 또한 자바 SE 6 업데이트 10을 설치하면 배포 시간이 단축됩니다.



JavaFX SDK Packages are Taking Shape 게시물에서 언급한 것처럼 JavaFX는 UI를 개발할 때 그래픽 "노드 중심" 방식을 채택하고 있으므로 JavaFX 사용자 인터페이스의 거의 대부분은 노드(Node)입니다.  사용자 지정 노드를 만들려는 경우 CustomNode 클래스를 확장하여 원하는 특성과 동작을 지정합니다.  아래 코드는 이 예에서 이미지를 표시하고 마우스 이벤트에 응답(예: 마우스를 위에 갖다대면 좀 더 투명해지고 텍스트가 표시되는 이벤트)하는 사용자 지정 노드를 만드는 코드입니다. 

주: javafx.ext.swing 패키지에 있는 Button 클래스를 사용하지 않는 이유가 궁금하실 것입니다. 이유는 Button 클래스는 Node가 아니라 Component이기 때문이며, 위에 언급한 대로 노드 중심 방식으로 변화되는 방향을 따르는 것이 가장 좋다고 생각합니다. 어떤 부분에서는 노드를 하위 클래스로 만드는 버튼이 나타납니다. 이 경우에는 ButtonNode 클래스가 더 이상 필요하지 않을 수 있습니다.

ButtonNode.fx


/*
*  ButtonNode.fx -
*  A node that functions as an image button
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  and Edgar Merino (http://devpower.blogsite.org/) to demonstrate how
*  to create custom nodes in JavaFX
*/


package com.javafxpert.custom_node;

import javafx.animation.*;
import javafx.input.*;
import javafx.scene.*;
import javafx.scene.effect.*;
import javafx.scene.geometry.*;
import javafx.scene.image.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.transform.*;

public class ButtonNode extends CustomNode {
 
/**
   * The title for this button
   */

 
public attribute title:String;

 
/**
   * The Image for this button
   */

 
private attribute btnImage:Image;

 
/**
   * The URL of the image on the button
   */

 
public attribute imageURL:String on replace {
    btnImage
=
     
Image {
        url
: imageURL
     
};
 
}
   
 
/**
   * The percent of the original image size to show when mouse isn't
   * rolling over it.  
   * Note: The image will be its original size when it's being
   * rolled over.
   */

 
public attribute scale:Number = 0.9;

 
/**
   * The opacity of the button when not in a rollover state
   */

 
public attribute opacityValue:Number = 0.8;

 
/**
   * The opacity of the text when not in a rollover state
   */

 
public attribute textOpacityValue:Number = 0.0;

 
/**
   * A Timeline to control fading behavior when mouse enters or exits a button
   */

 
private attribute fadeTimeline =
   
Timeline {
      toggle
: true
      keyFrames
: [
       
KeyFrame {
          time
: 600ms
          values
: [
            scale
=> 1.0 tween Interpolator.LINEAR,
            opacityValue
=> 1.0 tween Interpolator.LINEAR,
            textOpacityValue
=> 1.0 tween Interpolator.LINEAR
         
]
       
}
     
]
   
};

 
/**
   * This attribute is interpolated by a Timeline, and various
   * attributes are bound to it for fade-in behaviors
   */

 
private attribute fade:Number = 1.0;
 
 
/**
   * This attribute represents the state of whether the mouse is inside
   * or outside the button, and is used to help compute opacity values
   * for fade-in and fade-out behavior.
   */

 
private attribute mouseInside:Boolean;

 
/**
   * The action function attribute that is executed when the
   * the button is pressed
   */

 
public attribute action:function():Void;
   
 
/**
   * Create the Node
   */

 
public function create():Node {
   
Group {
     
var textRef:Text;
      content
: [
       
Rectangle {
          width
: bind btnImage.width
          height
: bind btnImage.height
          opacity
: 0.0
       
},
       
ImageView {
          image
: btnImage
          opacity
: bind opacityValue;
          scaleX
: bind scale;
          scaleY
: bind scale;
          translateX
: bind btnImage.width / 2 - btnImage.width * scale / 2
          translateY
: bind btnImage.height - btnImage.height * scale
          onMouseEntered
:
           
function(me:MouseEvent):Void {
              mouseInside
= true;
              fadeTimeline
.start();
           
}
          onMouseExited
:
           
function(me:MouseEvent):Void {
              mouseInside
= false;
              fadeTimeline
.start();
              me
.node.effect = null
           
}
          onMousePressed
:
           
function(me:MouseEvent):Void {
              me
.node.effect = Glow {
                level
: 0.9
             
};
           
}
          onMouseReleased
:
           
function(me:MouseEvent):Void {
              me
.node.effect = null;
           
}
          onMouseClicked
:
           
function(me:MouseEvent):Void {
              action
();
           
}
       
},
        textRef
= Text {
          translateX
: bind btnImage.width / 2 - textRef.getWidth() / 2
          translateY
: bind btnImage.height - textRef.getHeight()
          textOrigin
: TextOrigin.TOP
          content
: title
          fill
: Color.WHITE
          opacity
: bind textOpacityValue
          font
:
           
Font {
              name
: "Sans serif"
              size
: 16
              style
: FontStyle.BOLD
           
}
       
},
     
]
   
};
 
}
}  



위의 ButtonNode.fx 코드 목록에서는 다음 내용을 짚고 넘어가겠습니다.

  • ButtonNode 클래스는 CustomNode를 확장합니다.
  • 이 새로운 클래스는 사용자 지정 노드에 표시될 이미지와 텍스트를 저장하는 속성을 채용합니다.
  • create() 함수는 사용자 지정 노드의 UI 모양과 동작에 관한 선언 표현식을 반환합니다.
  • javafx.scene.effect 패키지의 Glow 효과는 이미지를 클릭할 경우 이미지에 빛나는 효과를 줄 때 사용됩니다.
  • 이미지의 투명도, 이미지의 크기, 사용자 지정 노드의 제목은 마우스를 누르거나 버튼을 놓을 때 전환됩니다. Timeline은 이러한 전환이 점진적으로 이루어지도록 하는 데 사용됩니다.
  • 투명도를 조정하고 Glow 효과를 적용한 후에 onMouseClicked 함수가 목록의 앞 부분에 정의된 action() 함수 속성을 호출합니다. 그러면 사용자 지정 노드가 Button과 유사하게 동작합니다.

"메뉴"에 ButtonNode 인스턴스 배열

Setting the "Stage" for the JavaFX SDK 게시물에서 설명한 것처럼 HBox 클래스는 javafx.scene.layout 패키지에 있으며, 이 패키지 안의 다른 노드를 배열하는 노드입니다. 아래와 같은 MenuNode 사용자 지정 노드는 ButtonNode 인스턴스를 수평으로 배열하고, javafx.scene.effects 패키지의 Reflection 클래스는 해당 버튼 아래에 멋진 반사 효과를 추가합니다. 코드는 다음과 같습니다.

MenuNode.fx

/*
 *  MenuNode.fx -
 *  A custom node that functions as a menu
 *
 *  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
 *  to demonstrate how to create custom nodes in JavaFX
 */


package com.javafxpert.custom_node;
 
import javafx.scene.*;
import javafx.scene.effect.*;
import javafx.scene.layout.*;

public class MenuNode extends CustomNode {

 
/*
   * A sequence containing the ButtonNode instances
   */

 
public attribute buttons:ButtonNode[];
   
 
/**
   * Create the Node
   */

 
public function create():Node {
   
HBox {
      spacing
: 10
      content
: buttons
      effect
:
       
Reflection {
          fraction
: 0.50
          topOpacity
: 0.8
       
}
   
}    
 
}
}  


사용자 지정 노드 사용

이제 사용자 지정 노드를 정의했으므로 간단한 프로그램에서 이 노드를 사용하는 방법을 보여드리겠습니다. 이 블로그를 따라해보신 분은 "JavaFX가 UI와 모델을 바인딩하는 방식"을 알게 되었을 것입니다. 이 간단한 예에서는 사용자 지정 노드를 만드는 방법을 알리는 데 중점을 두기 때문에 모델을 만들어서 이 모델에 UI를 바인딩하는 복잡한 작업까지 보여드리지는 않겠습니다. 대신, ButtonNode 인스턴스를 클릭할 때마다 문자열을 콘솔에 인쇄하는 간단한 작업을 보여드리겠습니다. 이번 예의 기본 프로그램의 코드는 다음과 같습니다.

MenuNodeExampleMain.fx

/*
 *  MenuNodeExampleMain.fx -
 *  An example of using the MenuNode custom node
 *
 *  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
 *  to demonstrate how to create custom nodes in JavaFX
 */

package com.javafxpert.menu_node_example.ui;

import javafx.application.*;
import javafx.scene.paint.*;
import javafx.scene.transform.*;
import java.lang.System;
import com.javafxpert.custom_node.*;

Frame {
 
var stageRef:Stage;
 
var menuRef:MenuNode;
  title
: "MenuNode Example"
  width
: 500
  height
: 400
  visible
: true
  stage
:
    stageRef
= Stage {
      fill
: Color.BLACK
      content
: [
        menuRef
= MenuNode {
          translateX
: bind stageRef.width / 2 - menuRef.getWidth() / 2
          translateY
: bind stageRef.height - menuRef.getHeight()
          buttons
: [
           
ButtonNode {
              title
: "Play"
              imageURL
: "{__DIR__}icons/play.png"
              action
:
               
function():Void {
                 
System.out.println("Play button clicked");
               
}
           
},
           
ButtonNode {
              title
: "Burn"
              imageURL
: "{__DIR__}icons/burn.png"
              action
:
               
function():Void {
                 
System.out.println("Burn button clicked");
               
}
           
},
           
ButtonNode {
              title
: "Config"
              imageURL
: "{__DIR__}icons/config.png"
              action
:
               
function():Void {
                 
System.out.println("Config button clicked");
               
}
           
},
           
ButtonNode {
              title
: "Help"
              imageURL
: "{__DIR__}icons/help.png"
              action
:
               
function():Void {
                 
System.out.println("Help button clicked");
               
}
           
},
         
]
       
}
     
]
   
}
}

앞서 언급한 대로 사용자가 해당 ButtonNode를 마우스로 클릭할 때마다 호출된 함수에 action 속성이 할당됩니다. 그리고 __DIR__ 표현식은 CLASS 파일이 있는 디렉토리로 평가됩니다. 이 경우 그래픽 이미지는 com/javafxpert/menu_node_example/ui/icons 디렉토리에 있습니다.

이 기사의 이미지를 다운로드한 후 이 그래픽으로 이 예에서 소개한 대로 작성하고 실행할 수 있습니다. 이 이미지는 프로젝트의 클래스 경로에서 확장할 수 있는 zip 파일입니다.

이 파일은 JavaFX SDK Technology Preview에 유용한 사용자 지정 노드 라이브러리를 작성하여 이 블로그의 JFX Custom Nodes 카테고리에 게시하기 위해 만든 것입니다. 사용자 지정 노드와 관련하여 아이디어가 있거나 자신이 개발한 사용자 지정 노드를 공유하려면 lat-inc.com의 jim.weaver로 연락해주십시오.

이 게시물을 실행한 후에 Weiqi Gao가 Java WebStart Works On Debian GNU/Linux 4.0 AMD64 게시물에 몇 가지 좋은 소식을 올렸습니다. JavaFX 스크립트 설명서의 기술 검토를 훌륭하게 해주셔서 저는 Weiqi(발음: 웨이치) 씨가 매우 좋습니다. ;-)

감사합니다.
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

지금 바로 the book's Apress site에서 eBook(PDF)을 다운로드할 수 있습니다.

이 글의 영문 원본은
Rolling Your Own JavaFX "Custom Nodes": A Graphical Menu Example
에서 보실 수 있습니다.

"Java FX" 카테고리의 다른 글

Posted by 1010
98..Etc/JavaFX2008. 10. 17. 17:10
반응형


Running Draggable JavaFX Applets




The new Drag'able feature in Applet in Java SE 6 update 10 unifies user's browser and desktop experience - The new Plug-In allows you to drag an Applet off from a browser to your desktop and allows your Applet continues to run. In addition to dragging an Applet off from a browser, when user closes the browser, a shortcut can also be created from this Drag'able Applet. The shortcut utilizes Java Web Start technology and allow user to launch the Applet with Java Web Start without opening a web browser. Thus, this feature unifies desktop application deployment via Java Web Start technology, and Applet deployment inside the browser.

Acknowledgments: The sample applications are from Joshua Marinacci and Chuk-Munn Lee.

Expected duration: 60 minutes (excluding homework)


Software Needed


  • JRE 6 update 10 (download)
    • The installation instruction of JRE 6 update 10 is described below.
  • Firefox 3 (download)
    • The installation instruction of JRE 6 update 10 is described below.
  • 4611_javafxdraggableapplets.zip (download)
    • It contains this document and the lab contents
    • Download it and unzip in a directory of your choice

OS platforms you can use

  • Windows
  • Solaris x86, Solaris Sparc
  • Linux
  • Mac OS X

Change Log

  • June 16th, 2008: Created


Lab Exercises


Exercise 1: Download and Install JRE 6 Update 10

You will have to use JRE 6 Update 10 in order to run the StopWatch application leveraging the new Java plug-in architecture.


(1.1) Download JRE 6 Update 10









(1.2) Install JRE 6 Update 10

















(1.3) Verify that JRE 6 update 10 is installed











Exercise 2: Download and Install Firefox 3

You will need either Firefox 3 or IE 7 in order to run StopWatch application.


(2.1) Download Firefox 3





(2.2) Install Firefox 3


          <To do: Include installation of Firefox 3>

(2.3) Verify that Firefox 3 is using JRE 6 Update 10


1. Verify that Firefox 3 is using JRE 6 Update 10 from the browser.
  • In the URL field, type in about:plugins.
  • Make sure you see Java(TM) Platform SE 6 U10 and its Enabled column says Yes.



2. Verify that Firefox 3 is using JRE 6 Update 10 from the Add-ons page. (This is an optional step.)
  • Select Tools from the top-level menu and select Add-ons in the Firefox 3.


  • Make sure you see Java(TM) Platform SE 6 U10 is in enabled state.



Exercise 3: Run StopWatch application


The StopWatch application is compiled version of JavaFX application.  The StopWatch application is provided as part of this hands-on lab zip file and located under <LAB_UNZIPPED_DIRECTORY>/javafxstopwatch/samples/StopWatch directory.

(3.1) Open index.html of the Stopwatch application from local file system


1. Open index.html of the application from the local file system.
  • Within Firefox 3 browser, select File->Open File.



  • Go to <LAB_UNZIPPED_DIRECTORY>/javafxstopwatch/samples/StopWatch directory.
  • Select index.html.
  • Click Open.



(3.2) Observe the stopwatch


1. Click the start button of the stopwatch.



2. Observe that the stopwatch now starts timing.



3. While holding ALT key, drag the stopwatch from the browser to the desktop.


  • Observe that the browser now has the Java logo (instead of the stopwatch) and the stopwatch on the desktop still works.
  • If you want to move the stopwatch around on the desktop, hold ALT key and move it around.
Note: Somehow I could not capture the stopwatch on the desktop.

4. Close the browser.
  • You can close the browser now and the stopwatch on the desktop still works.









(3.3)  Look under the hood of the application


1.  index.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Java FX Applets</title>
<style type="text/css">
<!--
body,td,th {
    font-family: Arial, Helvetica, sans-serif;
    color: #CCCCCC;
}
body {
    background-color: #000000;
    margin-left: 20px;
    margin-top: 20px;
    margin-right: 20px;
    margin-bottom: 20px;
}
#content {
    background-color: #3b3b3b;
}
-->
</style></head>
<script src="http://java.com/js/deployJava.js"></script>
<body>
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="max-width:1000px" align="center">
  <tr>
    <td><table width="100%" border="0" cellspacing="0" cellpadding="0">
      <tr>
        <td><img src="header-left.png" width="592" height="147" /></td>
        <td width="100%" background="header-center.png">&nbsp;</td>
        <td><img src="header-right.png" width="19" height="147" /></td>
      </tr>
    </table></td>
  </tr>
   <tr>
    <td colspan=3 align="center">
    <applet width="350" height="350">
        <param name="draggable" value="true">
        <param name="image" value="spinner.gif">
        <param name="boxborder" value="false">
        <param name="centerimage" value="true">
        <param name="boxbgcolor" value="#3b3b3b">
        <param name="boxfgcolor" value="#ffffff">
        <param name="jnlp_href" value="Stopwatch.jnlp">
    </applet>
    </td>
   </tr>
   <tr>
    <td colspan=3 align="center"><h3>Stopwatch</h3>
      <p>Need to keep track of how long it takes to run around your office? How long a coworker is talking to you and won't leave? How fast you wrote that code? Look no further. This Stopwatch applet does all that, and more, all while animating with drop shadows and lighting effects.</p></td>
   </tr>
</table>
</body>
</html>
index.html

2. Stopwatch.jnlp

<?xml version="1.0" encoding="UTF-8"?>
<jnlp href="Stopwatch.jnlp">
  <information>
    <title>Stopwatch</title>
    <vendor>Sun Microsystems</vendor>
    <offline-allowed />
    <shortcut online="false">
        <desktop/>
    </shortcut>
  </information>
  <resources>
    <j2se version="1.6+" href="http://java.sun.com/products/autodl/j2se" />
    <jar href="Widgets.jar" main="true" />
    <extension name="JavaFXRT" href="extensions/javafxrt.jnlp" />
    <extension name="Reprise" href="extensions/Reprise.jnlp" />
    <extension name="Scenario" href="extensions/Scenario.jnlp" />
    <extension name="Swingx-WS" href="extensions/swingx-ws.jnlp" />
  </resources>
  <applet-desc
      name="Stopwatch"
      main-class="widgets.stopwatch.StopwatchApplet"
      width="500"
      height="500">
  </applet-desc>
</jnlp>
Stopwatch.jnlp

Exercise 4: Run Magnifying Glass Draggable Applet


In this exercise, you are going to run magnifuing glass draggable applet.

(4.1) Access the Magnifying Glass Draggable Applet


1. From your browser go to Magnifying Glass Draggable Applet.



2. Drag the magnifying class from the browser to the desktop.
3. Observe that the magnifying glass magnifies the things on the desktop.
4. Closes the browser and observe that the magnifying glass still works.

Exercise 5: Run LiveConnect Draggable Applet


In this exercise, you are going to run LiveConnect draggable applet application.

(4.0) Create a Twitter account


<To do: Add instruction on how to create Twitter account>

(4.1) Access the LiveConnect Applet


1. From your browser, go to LiveConnect.



2. Enter your twitter account username and password.



3. Observe that the picture icon table is being displayed on the left.
4. Drag it from the browser to the desktop.



5. Close the browser.



6. Observe that the LiveConnect applet still works in the desktop.



If you want to close the LiveConnect on the desktop, press ALT key and click x button (upper right corner).

Homework Exercise (for people who are taking Sang Shin's "Java Web Services Programming online course")


<tbd>

                                                                                                                           return to the top




Posted by 1010
98..Etc/JavaFX2008. 10. 17. 14:56
반응형

Build rich internet applications (RIAs) with the JavaFX family of products. It includes the tools and platform SDK for developers, web scripters, and designers to create dynamic applications for the next generation of web delivered content. Learn more about JavaFX technology and how you can build applications for desktop, mobile devices, and TV screens here.


 



image of swirling squares
Launch


 









Launch
 
The JavaFX Preview SDK was used to build these applications. Try them out!
(Check the system requirements on the Download page if you encounter problems.)
 
 
Get the JavaFX Preview SDK now and try it out for yourself!
 
Posted by 1010
98..Etc/JavaFX2008. 10. 17. 14:54
반응형
Robert Eckstein 및 JavaFX Programming Language Reference 필진 작성, 2007년 7월

JavaFX Script 프로그래밍 언어(JavaFX)는 Sun Microsystems, Inc.에서 제공하는 선언적, 정적 형식의 스크립팅 언어이다. Open JavaFX (OpenJFX) 웹 사이트에서 언급한 대로, JavaFX 기술은 Java 기술 API에 직접 호출하는 것을 비롯한 다양한 기능으로 구성되어 있다. JavaFX 스크립트는 정적 형식인 만큼 동일한 코드 구조, 재사용 및 캡슐화 기능을 갖추고 있어(예: 패키지, 클래스, 상속, 별도의 컴파일 및 배포 단위) Java 기술을 사용해 매우 큰 규모의 프로그램을 만들고 유지 관리할 수 있다.

시리즈로 구성된 이 세 기사는 JavaFX 프로그래밍 언어를 처음 사용하는 데 도움이 될 것이다. 이번 시리즈의 1부에서는 JavaFX 프로그래밍 언어를 소개하며, 이미 Java 기술과 스크립팅 언어의 기초를 알고 있는 독자를 대상으로 한다. 시리즈 2부와 3부에서는 JavaFX 기술과 RMI(Remote Method Invocation)JAX-WS(Java API for XML Web Services)와 같은 기술을 이용하여 원격 서버에 연결하는 방법을 소개한다.

JavaFX Pad 응용 프로그램

시스템에 JRE(Java Runtime Environment)가 있는 경우, JavaFX 기술을 가장 손쉽게 시작하는 방법은 Java Web Start 사용 가능 데모 프로그램인 JavaFX Pad를 시작하는 것이다. 응용 프로그램을 시작했으면 그림 1과 같은 화면이 나타나야 한다.

그림 1. Microsoft Windows Vista, JDK 6에서 실행 중인 JavaFX Pad 응용 프로그램
그림 1. Microsoft Windows Vista, JDK 6에서 실행 중인 JavaFX Pad 응용 프로그램

JavaFX Pad는 기본 응용 프로그램이 로드된 상태에서 시작하므로, 이 응용 프로그램이 즉시 실행된다. 그러나 이 기사의 JavaFX 소스 코드를 잘라내어 샘플에 붙여 넣어 수정 사항을 볼 수도 있다. 또한 JavaFX 소스 예제를 로컬 디스크에 저장하고 로드할 수 있다. JavaFX Pad 응용 프로그램은 런타임 시 수행하는 작업을 정확하게 파악하고 진행 과정에서 수정하며 즉시 그 결과를 볼 수 있는 효과적인 방법이다.

JavaFX 기술: 정적 형식 언어

JavaFX 프로그래밍 언어는 정적(static) 형식의 스크립팅 언어이다. 이는 정확하게 어떤 의미인가? 다음을 살펴 본다.

var myVariable = "Hello";

JavaScript 기술에서 볼 수 있는 것과 비슷한 이 선언에서는 myVariable이라는 변수를 만들고 여기에 string 값 Hello를 지정한다. 그러나 변수를 선언한 다음에 string이 아닌 다른 것을 지정해 보겠다.

myVariable = 12345;

이 코드에서는 12345를 따옴표로 묶지 않았기 때문에 이 변수에는 string이 아니라 integer가 지정된다. JavaScript 기술에서는 변수 형식을 동적으로 재지정할 수 있다. 그러나 JavaFX와 같은 정적 형식의 언어는 이를 허용하지 않는다. myVariable은 원래 String 형식으로 선언되었지만, 나중에 코드에서 이 변수를 integer로 재지정하려고 하기 때문이다. JavaFX를 사용할 경우, String으로 선언된 변수는 String으로 유지되어야 한다.

실제로 JavaFX Pad 데모에서 이 두 줄의 코드를 만나면 그림 2와 같이 창 맨 아래에 즉시 오류가 표시된다.

그림 2. JavaFX 기술에서는 정적 형식의 변수에서 형식을 변경할 수 없다.
그림 2. JavaFX 기술에서는 정적 형식의 변수에서 형식을 변경할 수 없다.



JavaFX 기술: 선언적 스크립팅 언어

JavaFX 기술은 선언적 스크립팅 언어이기도 하다. 그러나 선언적이란 정확하게 무엇을 의미하는가? 이 질문에 답하기 위해 OpenJFX 웹 사이트의 이 Hello World 프로그램을 살펴 보겠다.

class HelloWorldModel {
    attribute saying: String;
}

var model = HelloWorldModel {
    saying: "Hello World"
};

var win = Frame {
    title: bind "{model.saying} JavaFX"
    width: 200
    content: TextField {
        value: bind model.saying
    }
    visible: true
};

Java 프로그래밍 언어를 비롯하여 대부분의 컴파일된 언어는 imperative 프로그래밍 언어로 간주된다. 무엇보다도 이는 Java 기술의 main() 메소드와 같은 시작점에 의존한다는 뜻이다. 이 시작점으로부터 다른 클래스나 필드를 인스턴스화거나 변수 또는 프로그램 상태에 따라 자원을 처리한다. 이 예제를 다소 확장하자면, imperative 프로그래밍 언어에서는 런타임 시 "공식을 통해(formulaically)" 실행 경로를 결정한다고 할 수 있다. 이 공식이 각 실행에서 동일한 경로를 만들 수도 있으나, 이 언어에서는 그 실행 경로를 런타임에 결정한다.

그러나 앞서 소개한 Hello World와 같은 JavaFX 프로그램에는 main() 메소드가 없다. 그 대신, 스크립팅 엔진에서는 실행 직전에 전체 프로그램을 읽으므로, 인터프리터가 프로그램을 올바르게 실행하는 데 필요한 모든 단계를 적용할 수 있다. 더 정확하게 말하자면, 스크립팅 엔진에 필요한 것은 실행 시작 전에 선언되며 모든 선언이 주어진 가운데 엔진은 명시된 목표 달성에 필요한 작업을 결정한다.

JavaFX Pad에서 System.out.println() 사용

곧 살펴 보겠지만, JavaFX는 기존의 Java 라이브러리를 호출할 수 있다. 그러나 JavaFX Pad 응용 프로그램에서 System.out.println()를 사용하려면 콘솔 지원을 활성화해야 한다. 그 방법은 다음과 같다.

  • Microsoft Windows XP 또는 Vista를 사용 중이라면 제어판에서 Java 아이콘을 클릭하고 고급 탭을 선택한 다음 Java 콘솔 항목에서 콘솔 표시를 선택한다.

  • Solaris 사용자라면 Preferences 탭에서 Java 아이콘을 클릭하고 Advanced 탭을 선택한 다음 Java Console 항목에서 Show Console을 선택한다. Preferences 탭에 Java 아이콘이 없으면 Java 배포판의 bin 디렉토리에서 ControlPanel 응용 프로그램(또는 jcontrol)을 실행한다.

  • Linux 사용자라면 해당 Java 배포판의 bin 디렉토리에서 ControlPanel(또는 jcontrol)이라는 응용 프로그램을 찾는다. 이를 실행한 다음 Preferences 탭에서 Java 아이콘을 클릭하고 Advanced 탭을 선택한 다음 Java Console 항목에서 Show Console을 선택한다.

  • Mac OS X 사용자라면 /Applications/Utilities/Java/[Java version]/ 아래에서 Java Preferences 응용 프로그램을 연다. 그런 다음 Advanced 탭을 선택하고 Java Console 항목에서 Show Console을 선택한다. 참고: Intel Mac에서 Java Preferences를 변경한 후 Java Web Start가 제대로 시작하지 않을 경우, 홈 디렉토리에서 Library/Caches/Java/deployment.properties 파일을 열고 모든 osarch 변수를 i386에서 ppc로 다시 변경해 본다.

JavaFX Pad에서 Run 메뉴의 Run Automatically 설정을 비활성화하고, JavaFX 응용 프로그램을 수동으로 실행하기 직전에 Java Console을 지운다. 응용 프로그램을 수동으로 실행하려면 JavaFX Pad 응용 프로그램의 Run 메뉴에서 Run 메뉴 항목을 사용한다.

그림 3에서는 Intel 기반 Macintosh에서 콘솔이 열린 상태로 실행 중인 JavaFX Pad를 보여 준다. 그림 4는 OpenSolaris에서 실행 중인 JavaFX Pad이다.

그림 3. Mac OS X, JDK 1.5.0_07의 Java Console에서 실행 중인 JavaFX Pad

그림 3. Mac OS X, JDK 1.5.0_07의 Java Console에서 실행 중인 JavaFX Pad


그림 4. OpenSolaris, JDK 6의 Java Console에서 실행 중인 JavaFX Pad 

그림 4. OpenSolaris, JDK 6의 Java Console에서 실행 중인 JavaFX Pad

마지막으로, JavaFX에서 string 내부에 변수를 포함시킨 경우(주로 Java 프로그래밍 언어에서 System.out.println()을 사용), 알맞은 JavaFX 구문은 다음과 같다.

    import java.lang.System;

    System.out.println("Text {variable} and more text");

이는 Java 언어 구문과 다르다.

    import java.lang.System;

    System.out.println("Text " + variable + " and more text");

JavaFX 기술 알아보기

이 섹션에서는 JavaFX 기술의 기본 사항을 살펴 본다. 이 정보의 대부분은 정식 JavaFX Programming Language Reference에서 직접 발췌한 것이며, 단 이 기사의 작성자들이 Java 프로그래머를 위해 그 내용을 수정했다.

Primitive

JavaFX 프로그래밍 언어는 String, Boolean, NumberInteger의 4개 primitive 형식만 제공한다. Java 프로그래밍 언어와 달리 primitive는 대문자로 시작한다. 표 1에서는 JavaFX 인터프리터 내부의 형식 그리고 이 형식이 매핑되는 Java 객체를 보여 준다.

JavaFX의 Primitive 매핑 JavaFX 기술의 Primitive 대표적인 Java 기술 Primitive 또는 클래스
String     java.lang.String
Boolean Number          java.lang.Number
Integer byte, short, int, long,          java.math.BigInteger

참고: 복잡성을 피하기 위해, Integer는 작은 수와 큰 수를 모두 나타내는데, Java 프로그래머는 short 또는 long과 같이 서로 다른 primitive 형식을 사용하기도 한다. Java 기술의 부동 소수점 숫자(예: float, double)는 Number 형식으로 표현한다.

Java 객체가 이 primitive를 나타내므로 이 형식 각각에서 기존의 Java 메소드를 호출할 수 있다.

var s:String = "Hello";
s.toUpperCase();      // String method that yields "HELLO";
s.substring(1);       // String method that yields "ello";

var n:Number = 1.5;
n.intValue();         // Number method that yields integer 1
(1.5).intValue();     // Number method that yields integer 1

var b:Boolean = true;
b instanceof Boolean; // Boolean method that yields true

그 결과를 보고 싶다면 System.out.println() 문에서 각 표현식을 줄바꿈하고 반드시 맨 위에 java.lang.System을 가져온다. 또한 호환되지 않는 형식이라는 오류가 발생할 경우, 우선 스크립트 끝에 null return을 추가하면 된다. 예제 코드는 다음과 같다.

import java.lang.System;

var s:String = "Hello";
System.out.println(s.toUpperCase());      // String method that yields "HELLO";
System.out.println(s.substring(1));       // String method that yields "ello";

var n:Number = 1.5;
System.out.println(n.intValue());         // Number method that yields integer 1
System.out.println((1.5).intValue());     // Number method that yields integer 1

var b:Boolean = true;
System.out.println(b instanceof Boolean); // Boolean method that yields true

return null;                              // Final node returned for JavaFX Pad display

역시 var 키워드 사용에 주목한다. Java 기술에서 사용되지는 않지만 var는 JavaFX 및 그 밖의 스크립팅 언어에서 새 변수를 선언할 때 쓰인다. JavaFX는 정적 형식 언어이므로, 선언에서 변수의 형식을 지정할 수 있으며 그렇지 않으면 JavaFX 인터프리터는 변수의 쓰임새로부터 변수의 형식을 유추하려고 한다. 예를 들어, 다음 세 가지 모두 JavaFX에서 유효하다.

var s:String;
var s:String = "A New String";
var s = "A New String";

첫 번째와 두 번째 선언에서는 공식적으로 String 형식을 변수에 지정하지만, 세 번째에서는 등호 (=) 부호의 오른쪽에 있는 초기값으로부터 이를 String으로 유추한다. 이를 더 공식적으로 나타낸다면 JavaFX 변수 선언을 이렇게 표현할 수 있다.

var variableName [: typeName] [? | + | *] [= initializer];

물음표, 더하기 부호 및 별표는 카디널리티 연산자라고 부른다. 표현식 언어를 사용한 적이 있으면 이 용어가 익숙할 것이다. 이 세 연산자 중 하나를 사용하여 변수의 카디널리티(구성원 수)를 나타낼 수 있다. 표 2를 참조한다.

표. JavaFX 카디널리티 연산자
연산자
의미
?
선택 사항(예를 들어 null이 될 수 있음)
+
하나 이상
*
0 이상

다음은 그 예제이다.

var nums:Number* = [5, 9, 13];

이 예제에서 선언하는 새 변수, nums의 값은 Number 형식의 인스턴스 0개 이상으로 구성되도록 정의되며, 초기 값은 3개의 숫자, 5, 9 그리고 13이다.

선언에서 typeName, 카디널리티 연산자와 초기값은 선택 사항이므로, 다음은 위의 예제와 동일하다.

var nums = [5, 9, 13];

리터럴

JavaFX 기술에서 리터럴 문자 스트링은 작은 따옴표 또는 큰 따옴표로 지정된다.

var s = 'Hello';
var s = "Hello";

필자가 앞서 언급한 대로, 변수와 심지어 JavaFX 표현식 전체를 중괄호({})로 묶을 수 있다.

var name = 'Joe';
var s = "Hello {name}"; // s = 'Hello Joe'
 

포함된 표현식 자체가 따옴표가 붙은 스트링을 포함할 수 있으며, 이 스트링이 다시 표현식을 포함하기도 한다.

var answer = true;
var s = "The answer is {if answer then "Yes" else "No"}";
    // s = 'The answer is Yes'
 

마지막으로, Java 기술과 달리 큰 따옴표로 묶인 String 리터럴은 새 행을 포함할 수 있다.

var s = "This
         contains
         new lines";
 

어레이 및 리스트 이해

앞서 카디널리티 연산자가 어레이를 생성하는 것을 보았을 것이다. JavaFX에서 어레이는 대괄호와 쉼표로 표시한다. Java와 마찬가지로 JavaFX 어레이의 요소는 모두 형식이 동일해야 한다.

var weekdays = ["Mon","Tue","Wed","Thur","Fri"];
var days = [weekdays, ["Sat","Sun"]];
 

어레이는 객체의 시퀀스를 나타낸다. JavaFX에서는 어레이 자체가 객체는 아니다. 또한 중첩된 어레이를 만드는 표현식(예: 앞의 코드 예제에서 두 번째 변수 days의 초기화)은 자동으로 평면화된다.

days == ["Mon","Tue","Wed","Thur","Fri","Sat","Sun"];
     // returns true
 

System.out.println(days)을 실행하면 어레이의 첫 번째 요소만 표시된다. 나머지 요소를 얻으려면 어레이 색인을 사용한다. 또한 존재하지 않는 색인을 지정할 경우, Java와 같이 ArrayIndexOutOfBoundsException이 발생하지 않고 0을 얻을 뿐이다.

sizeof 연산자를 사용하여 현재 어레이 크기를 구할 수 있다.

var n = sizeof days;     // n = 7
 

또한 간략하게 마침표 2개(..)를 사용하여 요소가 산술적 시리즈를 형성하는 어레이를 나타낼 수 있다. 예를 들어, 다음은 100개 요소의 어레이를 생성한다.

var oneToAHundred = [1..100];
var arraySize = sizeof oneToAHundred;   // size == 100
 

JavaFX 기술은 어레이 사용 시 insertdelete 문도 지원한다. 다음은 값을 어레이에 삽입하는 예제이다.

var x = [1,2,3];
insert 12 into x;                 // yields [1,2,3,12]
insert 10 as first into x;        // yields [10,1,2,3,12]
insert [99,100] as last into x;   // yields [10,1,2,3,12,99,100]
 

into 외에도 다음과 같이 beforeafter 키워드를 사용할 수 있다.

var x = [1,2,3];
insert 10 after x[. == 3];        // yields [1,2,3,10]
insert 12 before x[1];            // yields [1,12,2,3,10]
insert 13 after x[. == 2];        // yields [1, 12, 2, 13, 3, 10];
 

대괄호로 묶인 표현식 중 일부가 이상하게 보이더라도 신경 쓸 필요 없다. 실제로 Xquery-Update(XPath와 유사) 술어이다. 여기서 괄호 안의 마침표는 색인을 나타내지 않지만, 대신 색인의 을 의미한다. JavaFX 기술은 표현식이 true가 될 때까지 어레이의 요소 각각을 테스트한 다음 insert를 적용한다. 예를 들어, 마지막 문은 어레이의 각 값을 거치면서 반복되다가 (어레이의 세 번째 요소인)2와 동일한 어레이 값을 찾아내면 숫자 13을 삽입한다. 여기서 중지한다.

delete 문도 거의 동일하게 실행된다. 대괄호 안의 표현식이 생략될 경우, 어레이 전체가 지워진다.

var x = [1,2,3];
insert 10 into x;          // yields [1,2,3,10]
insert 12 before x[1];     // yields [1,12,2,3,10]
delete x[. == 12];         // yields [1,2,3,10]
delete x[. >= 3];          // yields [1,2]
insert 5 after x[. == 1];  // yields [1,5,2];
insert 13 as first into x; // yields [13, 1, 5, 2];
delete x;                  // clears the array and yields []
 

마지막으로, selectfor each 연산자를 사용하여 어레이에 대해 더 복잡한 쿼리를 수행할 수 있다. 이를 list comprehension이라고 한다. 다음은 select를 사용하는 간단한 예제이다.

var a:Integer* = select n*n from n in [1..10];
    //  yields [1,4,9,16,25,36,49,64,81,100]
 

이는 "[1..10] 어레이의 각 숫자를 루프하면서 로컬 변수 n에 지정한 다음 각 n에 대해 새 요소 n squared를 생성하고 이를 Integers a의 어레이에 추가한다"는 뜻이다.

또한 다음을 사용하여 필터를 추가할 수 있다.

var a:Integer* = select n*n from n in [1..10] where (n%2 == 0);
    //  yields [4,16,36,64,100]
 

이는 정의를 다음으로 변경한다. "[1..10] 어레이의 각 숫자를 루프하면서 그 숫자를 2로 나눈 나머지가 0인 경우에만, 즉 짝수인 경우에만 로컬 변수 n에 지정한다. 그런 다음 결과 값인 n 각각에 대해 새 요소 n squared를 생성하고 이를 Integers a의 어레이에 추가한다."

마지막으로, 여러 개의 리스트를 선택에 추가할 수도 있다.

var a:Integer* = select n*m from n in [1..4], m in [100,200] where (n%2 == 0);
    //  yields [200, 400, 400, 800]
 

실제로 이는 루프 안에 루프가 존재하면서 공식적으로는 Cartesian Product.를 생성한다. 먼저 첫 번째 유효한 n(즉 2)을 얻고 여기에 첫 번째 유효한 m인 100을 곱한다. 그런 다음 다시 2를 다음 번 유효한 m인 200과 곱한다. 그리고 나서 다음 번 유효한 n인 4를 사용하여 유효한 m의 리스트(100과 200)를 반복해 400과 800을 얻는다. 여기서 선택이 종료하며, 최종 어레이(어레이 a)가 얻어진다.

동일한 내용을 foreach 연산자를 사용하여 표현할 수 있다.

var a:Integer* =
    foreach(n in [1..4], m in [100,200] where (n%2 == 0) )
            n*m;      // also yields [200, 400, 400, 800]
 
형식 지정

format as 연산자는 표 3에서 보여주는 것처럼 몇 가지 형식 지정 지시문을 지원한다.

JavaFX 형식 지정 지시문 지시문사용된 형식의 클래스%로 시작하는 형식 지정 지시문java.util.Formatter표현식이 Numberjava.text.DecimalFormat표현식이 java.util.Date java.text.SimpleDateFormat

다음은 몇 가지 예제이다.

import java.util.Date;

100.896 format as <<%f>>; // yields '100.896000'
31.intValue() format as <<%02X>>;
    // yields '1F'
var d = new Date();
d format as <<yyyy-MM-dd'T'HH:mm:ss.SSSZ>>;
    // yields '2005-10-31T08:04:31.323-0800'
0.00123 format as <<00.###E0>>;
    // yields '12.3E-4'

이 예제에서는 프랑스어 따옴표, 즉 guillemets(<< >>)가 사용되었다. JavaFX 기술에서는 공백을 비롯하여 프랑스어 따옴표로 묶인 문자 시퀀스를 모두 식별자로 취급한다. 따라서 JavaFX 키워드 또는 평소에는 적합하지 않은 식별자를 클래스, 변수, 함수 또는 속성 이름으로 사용할 수 있다. 다음은 그 예제이다.

var <<while>> = 100;

이 기능에서는 이 예제와 같이 JavaFX 키워드와 이름이 같은 Java 메소드를 호출할 수 있다.

import javax.swing.JTextArea;

var textArea = new JTextArea();
textArea.<<insert>>("Hello", 0);
클래스 선언

클래스를 지정하는 JavaFX 구문은 class 키워드 다음에 클래스 이름, extends 키워드(옵션) 그리고 쉼표로 구분된 기본 클래스 이름의 목록이다. Java와 달리 JavaFX 기술에서는 둘 이상의 클래스를 확장할 수 있다. 그 다음에 여는 중괄호, 속성, 함수 및 연산의 목록(각각 세미콜론(;)으로 끝남)과 닫는 중괄호가 이어진다. 다음은 그 예제이다.

class Person {

    attribute name: String;
    attribute parent: Person;
    attribute children: Person*;

    function getFamilyIncome(): Number;
    function getNumberOfChildren(): Number;

    operation marry(spouse: Person): Boolean;
}

속성, 함수 및 연산

세 가지 형식의 클래스 구성원 선언을 자세히 살펴 보도록 한다. 속성(Attribute)attribute 키워드 다음에 속성의 이름, 콜론(:), 속성의 형식, 카디널리티 사양(옵션) 및 역 절(inverse clause)(옵션)을 사용하여 선언한다. 카디널리티를 사용하는 경우, 종종 그 속성을 다중 값 속성(multivalued attribute)이라고 한다.

더 공식적으로 설명하자면, 속성이 다음 구문을 사용한다.

attribute AttributeName [: AttributeType] [? | + | *] [inverse ClassName.InverseAttributeName];
>

맨 끝에 오는 inverse 절 옵션은 다른 속성과의 양방향 관계를 지정한다. inverse 절이 존재하는 경우, JavaFX 기술은 inverse 절에 지정된 속성이 수정될 때마다 그 속성을 자동으로 업데이트한다(속성의 업데이트 및 카디널리티 종류에 따라 insert, delete 또는 replace 사용).

함수(Function)는 JavaFX 프로그래밍 언어의 순수 기능 하위 집합을 나타낸다. 즉 함수의 본문은 일련의 변수 선언과 return 문만 포함할 수 있다. 그 정도면 속성 접근자(getter) 및 간단한 수학적 절차 구현에 충분하다. 몇 가지 함수의 예제를 소개한다.

function z(a,b) {
    var x = a + b;
    var y = a - b;
    return sq(x) / sq (y);
}

function sq(n) {return n * n; }

JavaFX에서 연산(Operation)operation 키워드를 사용하여 선언한다. 함수와 달리 연산은 조건문, 루핑문, trycatch 문을 비롯하여 개수 제한 없이 문을 포함할 수 있다. 따라서 Java 기술의 클래스 메소드와 더 비슷하다. 본문을 선언할 때 연산의 이름이 주어지고 괄호로 묶인 입력 변수, 콜론 그리고 반환 변수 형식이 이어진다. 다음은 그 예제이다.

operation substring(s:String, n:Number): String {
    try {
        return s.substring(n);
    } catch (e:StringIndexOutOfBoundsException) {
        throw "sorry, index out of bounds";
    }
}

선언된 속성 초기화

함수 및 프로시저와 마찬가지로, 속성의 초기 값은 클래스 정의의 외부에서 선언한다. 이 초기값은 새로 생성된 객체의 컨텍스트에 따라 클래스 선언에서 속성이 지정된 순서대로 평가된다.

import java.lang.System;

class X {
    attribute a: Number;
    attribute b: Number;
}

attribute X.a = 10;
attribute X.b = -1;

var x = new X();
System.out.println(x.a); // prints 10
System.out.println(x.b); // prints -1
>

JavaFX 객체는 선언적 구문을 사용하여 초기화할 수도 있다. 이 구문은 클래스 이름, 중괄호로 구분된 속성 초기값 목록의 순서로 구성된다. 각 초기값은 속성 이름, 콜론 그리고 그 값을 정의하는 표현식의 순서로 구성된다. new 키워드는 생략된다. 다음은 동일한 예제이다.

var myXClass = X {
    a: 10
    b: -1
};

선언적 구문은 JavaFX 기술에서 자주 사용된다. 그러나 Java 객체 할당 구문도 지원된다. Java 클래스의 경우, Java 기술에서처럼 클래스의 구성자에 인수를 전달하거나 선언적 구문을 사용할 수 있다.

import java.util.Date;
import java.lang.System;

var date1 = new Date(95, 4, 23);    // call a Java constructor
var date2 = Date {  // create the same date as an object literal
    month: 4
    date: 23
    year: 95
};

System.out.println(date1 == date2);   // prints true

함수 및 연산 정의

Java 메소드와 달리 모든 구성원 함수 및 연산의 본문은 클래스 선언의 외부에서 정의된다. 이 구문이 맨 처음에는 Java 프로그래머에게 약간 이상하게 보일 수 있으나 비교적 간단하게 이해할 수 있다. JavaFX 기술을 사용하면 연산 이름의 함수 앞에 그 함수가 속한 클래스 및 마침표가 온다. 반환 값은 함수 또는 연산 이름 뒤에 나열할 수 있으며, 앞에 콜론이 온다. 매개 변수는 서명 괄호에 포함시킬 수 있다. 이들은 쉼표로 구분되며, 앞서 연산에 대해 설명했던 name:type 구문을 따른다. 다음은 그 예제이다.

operation Person.marry(spouse: Person): Boolean {
    //  Body of operation
}

클래스 선언 내부의 연산 및 함수 선언에서는 괄호와 반환 값이 반드시 필요하다. 그러나 외부 정의에서는 생략 가능하다. 따라서 다음과 같이 쉽게 표현할 수 있다.

operation Person.marry() {
    //  Body of operation
}

트리거

Java 기술과 달리, JavaFX 클래스는 구성자가 없으며 JavaFX 속성은 일반적으로 "setter" 메소드를 갖지 않는다. 그 대신 JavaFX 기술에서는 데이터 수정 이벤트를 처리할 수 있도록 SQL과 비슷한 트리거를 제공한다. 이 트리거에서는 trigger 키워드를 사용한다.

객체 생성 트리거

생성 트리거를 지정하는 방법으로 새로 생성된 객체의 컨텍스트에서 작업을 트리거할 수 있다.

import java.lang.System;

class X {
     attribute nums: Number*;
}

trigger on new X {
     insert [3,4] into this.nums;
}

var x = new X();
System.out.println(x.nums == [3,4]); // prints true

이 예제에서는 X 클래스의 새 인스턴스가 만들어질 때마다 실행될 트리거를 정의한다. 여기서는 두 개의 숫자를 nums 속성에 삽입한다. 트리거의 컨텍스트에서 현재 객체를 가리킬 때 this 키워드를 사용한다.

Insert 트리거

또한 다중 값 속성에 요소가 삽입될 때마다 어떤 작업을 트리거할 수 있다.

import java.lang.System;

class X {
     attribute nums: Number*;
}

trigger on insert num into X.nums {
     System.out.println("just inserted {num} into X.nums at position {indexof num}");
}

var x = new X();
insert 12 into x.nums;
    // prints just inserted 12 into X.nums at position 0
insert 13 into x.nums;
    // prints just inserted 13 into X.nums at position 1

Delete 트리거

동일한 방법으로 다중 값 속성에서 요소가 삭제될 때마다 어떤 작업을 트리거할 수 있다.

import java.lang.System;

class X {
     attribute nums: Number*;
}

trigger on delete num from X.nums {
     System.out.println("just deleted {num} from X.nums at position {indexof num}");
}

var x = X {
     nums: [12, 13]
};

delete x.nums[1];
    // prints just deleted 13 from X.nums at position 1
delete x.nums[0];
    // prints just deleted 12 from X.nums at position 0

Replace 트리거

마지막으로, 속성의 값이 대체될 때마다 작업을 수행할 수 있다. 다음 예제에서 oldValuenewValue는 대체되는 요소의 이전 값과 현재 값을 참조하는 임의의 변수 이름이다. 다른 변수 이름을 자유롭게 선택할 수 있다.

import java.lang.System;

class X {
     attribute nums: Number*;
     attribute num: Number?;
}

trigger on X.nums[oldValue] = newValue {
     System.out.println("X.nums: replaced {oldValue} with {newValue} at position {indexof newValue}");
}

trigger on X.num[oldValue] = newValue {
     System.out.println("X.num: replaced {oldValue} with {newValue}");
}

var x = X {
     nums: [12, 13]
     num: 100
};

x.nums[1] = 5;
    // prints replaced 13 with 5 at position 1 in X.nums
x.num = 3;
    // prints X.num: replaced 100 with 3
x.num = null;
    // prints X.num: replaced 3 with null

JavaFX 기술에서는 Java 기술의 해당 문과 비슷하지만 똑같지 않은 몇몇 문을 지원한다. 이 섹션에서는 그 차이점을 간략하게 소개한다.

If

JavaFX if 문은 중괄호가 필요하다는 점을 제외하고 Java 기술의 문과 같다.

if (condition1) {
    System.out.println("Condition 1");
} else if (condition2) {
    System.out.println("Condition2");
} else {
    System.out.println("not Condition 1 or Condition 2");
}

While

JavaFX while 문 역시 본문을 중괄호로 묶어야 한다.

var i = 0;
while (i < 10) {
    if (i > 5) {
       break;
    }
    System.out.println("i = {i}");
    i += 1;
}

Try, CatchThrow

JavaFX trycatch 문은 JavaFX 변수 선언 구문을 제외하고 Java 기술의 문과 같다. JavaFX 기술에서는 java.lang.Throwable을 확장하는 객체 외에 어떤 객체도 throw 및 catch할 수 있다.

try {
   throw "Hello";
} catch (s:String) {
   System.out.println("caught a String: {s}");
} catch (any) {
   System.out.println("caught something not a String: {any}");
} finally {
   System.out.println("finally...");
}

For

JavaFX for 문의 헤더에서는 앞서 설명한 foreach list-comprehension 연산자와 동일한 구문을 사용한다. 문의 본문은 list comprehension에서 생성한 각 요소에 대해 실행된다. 다음은 그 예제이다.

for (i in [0..10]) {
     System.out.println("i = {i}");
}

// print only the even numbers using a filter
for (i in [0..10] where (i%2 == 0) ) {
     System.out.println("i = {i}");
}

// print only the odd numbers using a range expression
for (i in [1,3..10]) {
     System.out.println("i = {i}");
}

// print the cartesian product
for (i in [0..10], j in [0..10]) {
     System.out.println(i);
     System.out.println(j);
}

Return

JavaFX return 문은 Java 기술의 문과 동일하다.

operation add(x, y) {
    return x + y;
}

BreakContinue

JavaFX breakcontinue 문은 레이블이 지원되지 않는다는 점을 제외하고 Java 기술의 문과 같다. Java 프로그래밍에서처럼 breakcontinuewhile 또는 for 문의 본문에 나타나야 한다.

operation foo() {
   for (i in [0..10]) {
       if (i > 5) {
           break;
       }
       if (i % 2 == 0) {
           continue;
       }
       System.out.println(i);
   }
}

operation bar() {
    var i = 0;
    while (i < 10) {
        if (i > 5) {
            break;
        }
        if (i % 2 == 0) {
            continue;
        }
        System.out.println(i);
        i += 1;
    }
}

DoDo Later

JavaFX do 문에서는 JavaFX 코드의 블록을 실행할 수 있다. 그러나 do 본문은 항상 백그라운드 스레드에서 실행된다. 일반적으로 JavaFX 코드는 AWT EDT(Event Dispatch Thread)에서 실행된다. do 문의 본문에 포함된 코드만 다른 스레드에서 실행 가능하다. 다음 예제를 살펴 본다.

import java.net.URL;
import java.lang.StringBuffer;
import java.lang.System;
import java.io.InputStreamReader;
import java.io.BufferedReader;

// in the AWT Event Dispatch Thread (EDT)
var result = new StringBuffer();

do {
    // now in a background thread
     var url = new URL("http://www.foo.com/abc.xml");
     var is = url.openStream();
     var reader = new BufferedReader(new InputStreamReader(is));
     var line;
     while (true) {
          line = reader.readLine();
          if (line == null) {
               break;
          }
          result.append(line);
          result.append("\n");
     }
}

// now back in the EDT
System.out.println("result = {result}");

do 문의 본문이 실행되는 중에 EDT 블록에서 실행되는 코드이다. 그러나 백그라운드 스레드가 완료될 때까지 기다리는 동안 스택에서 새로운 이벤트 디스패치 루프가 생성된다. 그 결과, do 문이 실행되는 동안 GUI(graphical user interface) 이벤트는 계속 처리된다.

do 문은 do later라는 두 번째 형식을 갖는데, 이 형식은 java.awt.EventQueue.invokeLater()의 기능과 비슷하게, 백그라운드 스레드에서 동시 실행이 아니라 EDT에서 본문이 비동기식으로 실행될 수 있게 한다. 다음은 그 예제이다.

import java.lang.System;
var saying1 = "Hello World!";
var saying2 = "Goodbye Cruel World!";
do later {
     System.out.println(saying1);
}
System.out.println(saying2);

이 코드를 실행하면 다음과 같은 출력이 생성된다.

Goodbye Cruel World!
Hello World!

증분 평가

증분 평가(incremental evaluation)는 JavaFX 기술에서 가장 강력한 기능 중 하나이다. 프로그래머가 복잡한 동적 GUI를 선언적으로 정의할 수 있다. JavaFX 기술에서는 bind 연산자를 사용하면 속성 초기값을 증분식으로 평가할 수 있다. 바인딩된 이 속성은 스프레드시트의 셀처럼 작동하면서 리터럴 값 대신 공식을 포함한다. 초기값 표현식의 오른쪽에서 참조하는 객체가 변경될 때마다 속성의 값인 왼쪽이 자동으로 업데이트된다.

다음은 간단한 예제이다.

import java.lang.System;

class X {
    attribute a: Number;
    attribute b: Number;
}

var x1 = X {
    a: 1
    b: 2
};

var x2 = X {
    a: x1.a           // nonincremental
    b: bind x1.b      // incremental
};

System.out.println(x2.a); // prints 1
System.out.println(x2.b); // prints 2

x1.a = 5;
x1.b = 5;

System.out.println(x2.a); // prints 1
System.out.println(x2.b); // prints 5

이 예제에서 x2b 속성은 x1b 속성에 바인딩된다. 즉 x1b 속성이 업데이트될 때마다 x2b 속성도 업데이트된다.

함수의 본문은 bind 연산자가 없더라도 항상 증분식으로 평가되지만, 연산의 본문은 그렇지 않다. 함수와 달리, 연산 내부의 로컬 변수가 변경되더라도 증분 평가가 트리거되지 않는다. 표현식 앞에 명시적으로 bind라는 접두어가 붙지 않는 한 연산의 본문 내부에서는 증분 평가가 수행되지 않는다.

느린 증분 평가(lazy incremental evaluation)도 사용하도록 예제를 수정할 수 있다. 여기서는 attribute 값이 맨 처음 액세스될 때까지 바인딩이 적용되지 않는다.

import java.lang.System;

class X {
    attribute a: Number;
}

var x1 = X {
    a: 1
};

var x2 = X {
    a: bind lazy x1.a
    // no value is assigned yet
};

System.out.println(x2.a);
// Now the value is accessed, so x2.a is set to 1

느린 증분 평가 기능은 재귀적 데이터 구조(예: 트리, 그래프)를 처리할 때 자주 쓰인다.

결론

이 기사에서는 JavaFX 플랫폼을 간단하게 소개했다. 2부와 3부에서는 JavaFX 기술 기반의 GUI를 사용하여 클라이언트 서버 통신을 처리하는 방법을 다룬다.

자세한 정보

"Java FX" 카테고리의 다른 글

Posted by 1010