반응형

이제 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
에서 보실 수 있습니다.

+ Recent posts