[JavaFX] 선언적 사용자 인터페이스 : 학습 곡선 일지 2편
그 이후로 이 언어의 많은 중요한 부분이 개선되었습니다.
업데이트된 학습 곡선 일지에서는 컴파일러 기반 버전의 언어 사용법을 보여줍니다.
사용자 인터페이스를 정의하기 위해 10년 가까이 자바 프로그래밍 언어를 사용해온 저는
JavaFX 스크립트를 처음 사용해보고 두 가지 환경 사이의 큰 차이점을 금방 느낄 수 있었습니다.
프로그래머는 자바 언어에서 사용자 인터페이스(UI) 정의를 위해 절차적 언어를 사용하지만
JavaFX Script에서는 UI 정의에 선언적 문을 사용할 수 있습니다.
이는 커다란 차이이며 여기에 적응하는 데는 약간의 시간과 노력이 필요할 수 있습니다.
UI 생성을 위한 새로운 선언적 스타일에 대해 알아보고자
기존 애플리케이션 UI를 자바 언어 구현에서 JavaFX 스크립트로 이식하기로 했습니다.
썬 개발자 네트워크의 자바 언어 허브에 있는 Swingworker 기사에서 만든 이미지 뷰어 애플리케이션을 선택했습니다.
원래 애플리케이션은 Java SE 6에서 SwingWorker
클래스의 사용 방법을 보여주기 위한 것이었지만
UI 자체는 JavaFX 스크립트로의 간편한 이전을 제공할 만큼 충분히 단순해 보입니다.
기존 사용자 인터페이스
기존 애플리케이션에서는 사용자가 유명한 Flickr 웹 사이트에서 이미지를 검색, 나열 및 표시할 수 있도록 했습니다.
사용자는 검색어를 입력할 수 있으며 애플리케이션은 REST API를 사용하여 매칭되는 축소판 이미지 목록을 Flickr에 쿼리합니다.
사용자는 축소판 이미지를 하나 선택하여 크고 상세한 이미지를 가져올 수 있습니다.
그림 1은 검색 결과가 나온 기존 애플리케이션을 보여줍니다.
이 UI는 위에서부터 아래로 다음 구성요소로 이루어져 있습니다.
- 기본 프레임 창 컨테이너
- 검색 레이블 및 검색 텍스트 필드
- 검색 매칭 레이블 및 진행 표시줄
- 매칭되는 축소판 이미지 목록과 짧은 설명
- 선택 레이블 및 진행 표시줄
- 선택한 이미지를 보여주는 레이블
UI는 JFrame
, JLabel
, JProgressBar
, JScrollPane
및 JList
.와 같은 일반 Swing 구성요소로 이루어집니다.
JList
구성요소는 축소판 이미지와 제공되는 짧은 설명을 표시하기 위한 사용자 정의 렌더러를 갖지만 여전히 상대적으로 간단한 UI로서
JavaFX 스크립트의 선언적 UI 측면을 조사하는 데 도움이 될 것입니다.
전체 애플리케이션의 구현을 시도해 보겠지만 현재로는 기존 UI와 적당히 비슷한 정도로도 충분합니다.
작동하는 데모처럼 극적이진 않겠지만 그림 2에 나온 비활성 UI는 JavaFX 스크립트의 선언적 UI에 대한 초기 목표를 보여줍니다.
원래 UI는 NetBeans IDE 6.1과 Matisse GUI 편집기를 사용하여 구현했습니다.
Swingworker 기사에서 모든 원래 코드 및 생성된 UI 주변의 코드를 다운로드 받을 수 있습니다.
코드에서 이 UI를 생성하기 위해 NetBeans IDE가 GroupLayout
를 어떻게 사용했는지 볼 수 있습니다.
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();
}
javax.swing.GroupLayout
을 사용하여 UI 코드를 생성했습니다. JavaFX 스크립트용의 설계 도구가 개발 중이지만 아직은 사용할 수 없습니다.
이 인터페이스에 필요한 UI 구성요소는 javafx.ext.swing
패키지 및 javafx.scene
으로 시작하는
다양한 패키지에 들어 있습니다. UI 구축은 계층 구조적인 Swing 기반 접근 방법을 따릅니다.
이 시리즈의 1편에서 언급한 것처럼 앞으로 JavaFX 개발자는 노드 기반 접근 방법을 사용할 것입니다.
노드 기반 접근 방법으로 UI를 구축하면 javafx.application
클래스의 일부 클래스가 필요할 것입니다.
JavaFX 스크립트는 패키지 구조 및 가져오기 문을 지원한다는 점에서 자바 언어와 유사합니다.
구성요소에 대해 공부하는 동안은 전체 패키지를 가져오는 대신 한 번에 하나씩 특정 구성요소를 가져오겠습니다.
이는 매우 지루하지만 하나씩 사용하게 함으로써 패키지에 어떤 구성요소가 있는지 보여줍니다.
JavaFX 스크립트 구성요소는 height
, width
, text
및 content
와 같은 속성을 갖습니다.
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
속성은 프레임에 나타나는 텍스트인 창 프레임 제목을 선언합니다.
height
및 width
속성은 픽셀 단위로 크기 규격을 정의합니다.
마지막으로 visible
속성은 Swing의 JFrame
클래스 setVisible
메소드와 유사하게 프레임의 가시성 여부를 선언합니다.
javafx.ext.swing
패키지에는 SwingFrame
, Label
, BorderPanel
, FlowPanel
및 List
와 같은 구성요소가 포함됩니다.
이러한 이름은 일반적인 Swing 구성요소 같으므로 대상 UI 구현에 이들을 먼저 사용해보겠습니다.
javafx.scene
패키지의 HorizontalAlignment
구성요소와 javafx.scene.paint
패키지의 Color
구성요소도 사용했습니다.
그러나 JavaFX 스크립트 패키지에 ProgressBar
구성요소는 아직 없습니다.
따라서 진행 표시줄을 생성하는 함수를 코딩했습니다. 이 시리즈의 3편에서는 JavaFX 스크립트 함수를 검토합니다.
BorderPanel
및 FlowPanel
등의 구성요소와 진행 표시줄의 함수를 사용하기로 결정하고 다음
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
구성요소에 지정되고 BorderPanel
구성요소에 지정되며 bottom 속성은 Label
구성요소에 지정되었습니다.FlowPanel
구성요소는 content
등록 정보를 갖습니다. 이 등록 정보에 하나 이상의 구성요소를 넣을 수 있습니다. 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
내에서 구성요소를 순서대로나
병렬로 정리하기 위해 사용할 수 있는 SequentialCluster
및 ParallelCluster
도 포함됩니다.
진행 표시줄, 검색 텍스트 필드, 축소판 이미지 목록 및 선택된 이미지 표시 영역의 최대 너비 및 높이를 설정하기 위해
JavaFX 스크립트 Layout
구성요소도 활용하기로 결정했습니다.
Layout
구성요소는 UNLIMITED_SIZE
등의 레이아웃 관련 상수를 제공합니다.
UI의 두 번째 버전은 다음 JFXImageSearchUI.fx
파일에 코딩되었습니다.
package com.sun.demo.jfx; |
이 코드의 결과는 훨씬 보기 좋습니다.
그림 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
}
]},
SequentialCluster
는 SequentialCluster
및 ParallelCluster
와 같은 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를 입력할 때 활성화되는 일부 팝업 옵션을 보여줍니다.
UI 생성을 위한 선언적 구문을 살펴보기 위해 기존 애플리케이션의 UI를 이식했습니다.
원래 애플리케이션에서는 구성요소의 배치 및 정렬에 Swing의 GroupLayout
을 사용했습니다.
NetBeans IDE 6.1은 아직 JavaFX 스크립트를 위한 그래픽 설계 도구를 지원하지 않지만
수동으로 인터페이스를 레이아웃하는 것은 처음 걱정했던것 만큼 어렵지 않았습니다.
ClusterPanel
, SequentialCluster
및 ParallelCluster
구성요소의 조합을 통해
JavaFX 스크립트 구현은 원래 UI와 사실상 똑같아 보입니다.
이들 조합에 NetBeans IDE용 JavaFX 플러그인 및 문맥 인식 코드 완성이 더해져 작업은 더욱 쉬워졌습니다.
'개발 이야기 > Java' 카테고리의 다른 글
[JavaFX] 웹 서비스 액세스 :학습 곡선 일지 4편 (0) | 2008.09.25 |
---|---|
[JavaFX] JavaFX 스크립트 함수 : 학습 곡선 일지 3편 (0) | 2008.09.25 |
[JavaFX] 스크립트 탐구 : 학습 곡선 일지 1편 (0) | 2008.09.25 |
SAML을 이용한 SSO Service의 구현 (0) | 2008.04.03 |
SVN 서버 설치 및 거북이(Tortoise) SVN 설치하기 (0) | 2008.03.13 |