JavaFX - component creation
Join the DZone community and get the full member experience.
Join For FreeThis article describes how to create new components for the JavaFX platform with an emphasis on the use of graphical facilities in building GUI.
Task Statement
The commonly used date chooser is not one of the widgets included in the
JavaFX distributive. Here you will learn how to create this widget from scratch.
Like in most implementations, our component should display dates using names for week days and months in the user's native language.
Development Environment
To create the component you will need the free NetBeans IDE with the installed JavaFX plug-in.
To prepare a prototype and process graphics you can use any raster graphics editor. In this article we use the Gimp editor.
Prototype Preparation
The component's appearance can be prepared in the graphics editor.
Suppose that our prototype appears as follows:
On the drawing you can see the appearance and arrangement of GUI elements. You should save an image of your prototype in a format that allows the elements to be stored in separate layers. The most commonly used format of this type is .psd provided by Adobe Photoshop (the Gimp editor has its own similar format).
Next you will reproduce the drawing using the JavaFX facilities and add functionality.
Component Creation
In order to select dates, you can use a separate dialog window or a lightweight-form. This example shows a lightweight component that uses JPopupMenu.
Create the FxDateChooser.fx JavaFX class:
class FxDateChooser {
operation choose(parent:Widget);
attribute choosen:String;
attribute color:Color;
private attribute popupChooser:JPopupMenu;
private attribute days:Button*;
private attribute currentMonth:Number;
private attribute currentMonthName:String;
private attribute currentYear:Number;
operation fillDays();
operation setMonth();
}
The popupChooser initializes in a trigger, is shown in the choose operation, and a result is placed into the chosen attribute.
Choice Between Graphics and Code
This prototype's elements can be created by using different JavaFX widgets or by pasting together pieces of a drawing.
For example, the band with buttons used in the prototype looks like a piece of glass. Similar elements became more commonly used with the advent of the Aqua interface for Apple computers:
"Glass" buttons have a spectacular appearance and are easy to render. As a rule, glass buttons have a rectangular shape with rounded corners that are filled with a colored gradient. The colored gradient changes from a darker tone to a lighter one. They also include a shadow, a title and a semi-transparent white band as a flare.
Here is an example of a glass button that only uses JavaFX facilities:
import javafx.ui.*;
import javafx.ui.canvas.*;
import javafx.ui.filter.*;
import javafx.ui.Canvas;
import java.awt.Dimension;
Frame {
height: 150
width: 400
visible: true
content: BorderPanel {
border: EmptyBorder {left:32 top:32 right:32 bottom:32}
center: Canvas {
background: new Color(0,0.7,0.9,1)
content: View {
sizeToFitCanvas: true
content: BorderPanel {
var: main
center: Canvas {
content:
[
View{
filter: ShadowFilter{
distance: 0
opacity: 1
radius: 8
}
content: BorderPanel {
border: EmptyBorder {
left: 4
top: 4
right: 4
bottom: 4
}
center: Canvas {
content: Rect {
x:0
y:0
width: bind main.width-16
height: bind main.height-16
arcWidth: bind main.height-16
arcHeight: bind main.height-16
fill: LinearGradient {
x1:0 y1:0.3 x2:0 y2:1
stops:
[
Stop {offset: 0.0
color: new Color(0,0.7,0.2,1)},
Stop {offset: 1.0
color: new Color(1,1,1,1)}
]
}
visible: true
}
}
}
}
,View {
filter: ShadowFilter{
distance: 1
opacity: 1
radius: 5
}
sizeToFitCanvas: true
content: BorderPanel {
border: EmptyBorder {
left: 8
top: 0
right: 8
bottom: 12
}
center: Button {
text: "Press me!"
cursor: HAND
focusPainted: false
contentAreaFilled: false
font: new Font("SanSerif", "BOLD", 12)
background: new Color(1,1,1,0)
foreground: new Color(1,1,1,1)
action: operation() {
MessageDialog {
message: "Click!"
visible: true
};
}
}
}
visible: true
}
,Rect {
x:bind main.height/3
y:10
width: bind main.width-main.height/3-main.height/3
height: bind main.height/3
arcWidth: bind main.height/3
arcHeight: bind main.height/3
fill: new Color(1,1,1,0.3)
visible: true
}
]
}
}
}
}
}
}
This script results in the following:
Although the button's code is not difficult, it's size is quite large. It is more convenient to implement a glass panel by pasting images.
The widget's background has a view of a flare with a stretched out JavaFX title. The title can be inserted as an image. In order to change the component's appearance, background can be filled in with the RadialGradient color.
Panels with day numbers can be easily implemented by means of buttons (the Button component). You will need to remove buttons' standard filling and painting of a focus. See the example below:
foreach(i in [1..49]) Button {
var: me
border: EmptyBorder { left:2 top: 2 right: 2 bottom: 2}
contentAreaFilled: false
focusPainted: false
foreground: bind if i<8 then new Color(1,1,1,1) else color
font: bind if i<8 then new Font("SanSerif", "BOLD", 9)
else if i%7==0
then new Font("SanSerif", "ITALIC", 10)
else if (i+1)%7==0
then new Font("SanSerif", "ITALIC", 10)
else new Font("SanSerif", "BOLD", 10)
text: "{i}"
action: operation() {
if(i>7) {
if(me.text.length()>0) {
choosed="{currentYear}-{currentMonth+1}-{me.text}";
popupChooser.visible=false;
}
}
}
onMouseEntered: operation (e:MouseEvent) {
if(i>7) {
if(me.text.length()>0) {
me.background=new Color(1,1,1,0.7);
}
}
}
onMouseExited: operation (e:MouseEvent) {
if(i>7) {
if(me.text.length()>0) {
me.background=new Color(1,1,1,0.3);
}
}
}
};
This code creates an array of 49 buttons for the names of week days and the numbers of the days of the month. You can set rules to highlight elements in the onMouseEntered and onMouseExited events.
Prototype Slicing
To create a glass panel with buttons, cut the prototype into parts and save each piece in a separate file. You have the following six pieces:
- a button for moving to the previous month
- a panel with a month name
- a button for moving to the next month
- a button for moving to the previous year
- a panel with a year
- a button for moving to the next year
Open the prototype in the editor and on the layers panel make all elements which do not relate to the panel with buttons invisible.
For cutting in the Gimp editor use the following menu item
Image/Transform/Gullotine
Then select the required piece of the image and choose the following menu item:
Image/Crop Image
to cut off unnecessary pieces:
Repeat the process above for all six elements and save them in separate files. Before saving, make the image's background invisible (in this example it is green-blue).
The best format for storing separate image pieces is .png, since this format uses compression without losses and allows you to save the transparency's alpha channel.
Four images of buttons should be redoubled and the arrows' brightness should be increased. Those copies will be used for highlighting.
Here is an example of the code for a button that uses the cut image:
Button {
cursor: HAND
focusPainted: false
contentAreaFilled: false
background: new Color(1,1,1,0.0)
icon: Image { url: "auxiliary/datechooser/images/left.png" }
border: EmptyBorder { top: 0 left: 0 bottom: 0 right: 0 }
rolloverIcon: Image { url: "auxiliary/datechooser/images/leftHover.png" }
action: operation() {
currentMonth=currentMonth-1;
if(currentMonth<0) {
currentMonth=11;
currentYear=currentYear-1;
}
setMonth();
}
}
Composing Elements
Your calendar will have two layers: one layer for the background with the JavaFX title and another for the toolbar buttons and month numbers.
To arrange the calendar by layers, you can use either the StackPanel widget or the Canvas with an array of View elements. Place the BorderPanel, which contains a toolbar in the top area and buttons with numbers in the center area in the upper layer. For convenience, you can place the buttons that show day numbers in the GridPanel.
Gaps between elements will be defined by specifying the panels' EmptyBorder parameters.
Adding Functionality
In a trigger on component's creation add text to the names of week days:
var now = Calendar.getInstance();
var dayNames = new DateFormatSymbols().getShortWeekdays();
while(now.get(Calendar.DAY_OF_WEEK) <> now.getFirstDayOfWeek()) {
now.add(Calendar.DATE, 1);
}
for (i in [0..6]) {
days[i].text=dayNames[now.get(Calendar.DAY_OF_WEEK)];
days[i].background=new Color(1,1,1,0);
now.add(Calendar.DATE, 1);
}
In the same manner you can hide, display, or fill buttons for the days when the current month or year changes. The code for filling in the calendar is common on sites with Java examples.
Localization
Our component will display all titles based on the locale selected on a given computer:
Therefore it is necessary to provide for sufficient text size since titles in some languages may be long and difficult to fit in the allocated cells.
Use Case
In order to test the widget, you can create a simple form with a calendar calling button:
Button
{
var:me
text: bind chooser.choosen
action: operation()
{
chooser.choose(me);
}
}
Then add the link to your component to the text script:
import auxiliary.datechooser.FxDateChooser;
and the initialization code specifying the color and initial value:
var chooser=FxDateChooser
{
choosen: "Test!"
color: new Color(0,0,0.2,1)
};
Source Code
dch.zip - 300kb
- the source code and prototype in the .psd-format.
Opinions expressed by DZone contributors are their own.
Trending
-
Cucumber Selenium Tutorial: A Comprehensive Guide With Examples and Best Practices
-
SRE vs. DevOps
-
5 Key Concepts for MQTT Broker in Sparkplug Specification
-
Getting Started With the YugabyteDB Managed REST API
Comments