Статьи

Прокручиваем свои собственные «пользовательские узлы» JavaFX: пример графического меню

С выходом предварительной версии JavaFX SDK Technology Preview я хотел бы познакомить вас с тем, как создавать свои собственные «пользовательские узлы». Это говорит на JavaFX для виджетов, гаджетов, компонентов пользовательского интерфейса, чего угодно, но цель та же: иметь возможность создавать потенциально многократно используемый пользовательский интерфейс для программ JavaFX. Сегодняшний пример демонстрирует, как создать пользовательский узел (на самом деле, два), и вот скриншот:

Menunodeexample

Если вы хотите попробовать его, нажмите на эту ссылку Java Web Start, помня, что вам понадобится как минимум JRE 5. Кроме того, установка Java SE 6 update 10 ускорит развертывание.

Webstartsmall2

Как я упоминал в статье «Пакеты JavaFX SDK принимают форму» , JavaFX применяет графический «ориентированный на узлы» подход к разработке пользовательского интерфейса, поэтому почти все в пользовательском интерфейсе JavaFX является узлом . Когда вы захотите создать свой собственный пользовательский узел, вы расширите класс CustomNode , предоставив ему нужные атрибуты и поведение. Ниже показан код для пользовательского узла в примере, который отображает изображение и реагирует на события мыши (например, становится более прозрачным и отображает текст при наведении курсора мыши).

Примечание: вас может удивить, почему я не просто использую класс Button, который находится в пакете javafx.ext.swing. Причина в том, что класс Button является Компонентом, а не Узлом, и я думаю, что лучше всего следовать заявленному направлению перехода к подходу, ориентированному на узлы. В какой-то момент появится кнопка, которая подклассов Node, и в этом случае класс 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)
* 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 nonRolloverScale:Number = 0.9;

/**
* The opacity of the button when not in a rollover state
*/
public attribute nonRolloverOpacity:Number = .80;

/**
* A Timeline to control fading behavior when mouse enters or exits a button
*/
private attribute fadeTimeline =
Timeline {
keyFrames: [
KeyFrame {
time: 0ms
values: [
fade => 0.0
]
},
KeyFrame {
time: 600ms
values: [
fade => 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 {
var scale = bind if (mouseInside) fade * (1.0 - nonRolloverScale) +
nonRolloverScale
else 1.0 - fade * (1.0 - nonRolloverScale);
image: btnImage
opacity: bind if (mouseInside) fade * (1.0 - nonRolloverOpacity) + nonRolloverOpacity
else 1.0 - fade * (1.0 - nonRolloverOpacity)
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 if (mouseInside) fade else 1.0 - fade
font:
Font {
name: "Sans serif"
size: 16
style: FontStyle.BOLD
}
},
]
};
}
}

 

Некоторые вещи, которые следует отметить в приведенном выше листинге кода ButtonNode.fx :

  • Наш ButtonNode класс расширяет CustomNode
  • Этот новый класс вводит атрибуты для хранения изображения и текста, которые появятся на пользовательском узле.
  • Функция create () возвращает декларативное выражение внешнего вида и поведения пользовательского интерфейса нашего пользовательского узла.
  • Эффект свечения в пакете javafx.scene.effect используется для увеличения яркости изображения при нажатии.
  • Непрозрачность изображения, размер изображения и заголовок пользовательского узла переходят по мере того, как мышь входит и выходит из кнопки. Timeline используется , чтобы сделать эти переходы постепенно.
  • После настройки непрозрачности и применения эффекта свечения, функция onMouseClicked вызывает атрибут функции action (), определенный ранее в листинге. Это заставляет наш пользовательский узел вести себя как знакомая кнопка .

Расположение экземпляров ButtonNode в «меню»

Как показано в разделе « Установка» для поста JavaFX SDK , класс HBox находится в пакете javafx.scene.layout и является узлом, который размещает в нем другие узлы. MenuNode пользовательский узел показан ниже упорядочивает ButtonNode экземпляров по горизонтали, и он использует Reflection класс в javafx.scene.effects пакете , чтобы добавить эффект хорошего отражения ниже кнопок. Вот код:

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 — привязать пользовательский интерфейс к модели». В этом простом примере, поскольку я действительно хочу сосредоточиться на том, чтобы научить вас создавать собственные узлы, я не собираюсь усложнять ситуацию, создавая модель и привязывая к ней пользовательский интерфейс. Скорее, я просто печатаю строку в консоль всякий раз, когда нажимается экземпляр 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 , как указывалось ранее. Также обратите внимание на __DIR__ выражение в директорию , в которой CLASS файл находится. В этом случае графические изображения находятся в каталоге com / javafxpert / menu_node_example / ui / icons .

Я намерен создать библиотеку полезных пользовательских узлов для предварительного просмотра технологии JavaFX SDK и опубликовать их в категории пользовательских узлов JFX этого блога. Если у вас есть идеи для пользовательских узлов или вы хотели бы поделиться разработанными вами, напишите мне на jim.weaver на lat-inc.com

Кстати, после публикации этого поста Вейци Гао сообщил о некоторых интересных новостях в своем посте Java WebStart Works On Debian GNU / Linux 4.0 AMD64 . Конечно, я неравнодушен к Вейци (произносится как «путь-чи»), потому что он отлично поработал над техническим обзором нашей книги по JavaFX Script ?

Спасибо,
Джим Уивер
JavaFX Script: динамические сценарии Java для многофункциональных интернет-приложений и приложений на стороне клиента.

Немедленная загрузка электронных книг (PDF) доступна на сайте книги Apress.