Exploring JavaFX's ScrollBar Control
JavaFX's scrollbar control lets you add customized scrolling to your user interfaces (UIs). For example, you might provide a scrollbar that lets the user scroll through a list of images, which is something that the listview control doesn't support (you can only scroll through text).
Before you can use the scrollbar control to introduce customized
scrolling, you need to understand the various properties and
functions that are made available by its
javafx.scene.control.ScrollBar class. These
properties and functions are listed below:
-
blockIncrement(of typeNumber) specifies the amount (10.0is the default) that is added to or subtracted fromvaluewhenever the scrollbar's track is clicked. It isn't used ifclickToPositionis set totrue. -
clickToPosition(of typeBoolean) specifies whether (true) or not (false) a click on the scrollbar's track setsvalueto the amount specified by the clicked position in the track. -
max(of typeNumber) specifies the scrollbar's maximum value. This value must be greater than or equal tomin; otherwise, it's set equal tomin. -
min(of typeNumber) specifies the scrollbar's minimum value. This value must be less than or equal tomax; otherwise, it's set equal tomax. -
unitIncrement(of typeNumber) specifies the amount (1.0is the default) that's added to or subtracted fromvaluewhenever the scrollbar's down/right or up/left arrow (respectively) is clicked. -
value(of typeNumber) specifies the scrollbar's current value. It must lie betweenminandmax. If it's ever out of bounds (perhapsmaxorminhave been changed), it will be clamped to remain in bounds. -
vertical(of typeBoolean) specifies the scrollbar's orientation. The scrollbar is vertical whentrueis assigned tovertical; otherwise, the scrollbar is horizontal. -
public adjustValue(position: Number): Voidadjustsvalueso that it's ultimately set to the value located at thepositionfraction (which must range from 0.0 to 1.0) betweenminandmax. IfclickToPositionistrue,valueis set to this value after a single call toadjustValue(). IfclickToPositionisfalse, each call toadjustValue()either increments or decrementsvaluebyblockIncrementuntilvalueis set to this value. For example, assume thatminis set to0.0,maxis set to100.0,valueis set to25.0, andclickToPositionis set totrue. Following a call toadjustValue(),valuewill be set to50.0. However, ifclickToPositionwas set tofalseandblockIncrementwas set to10.0, the first call toadjustValue()would result invaluebeing set to35.0, the second call would result invaluebeing set to45.0, and the third call would result invaluebeing set to50.0. In either case, successive calls toadjustValue()would not changevalue's value -- it would remain set to50.0. -
public decrement(): VoiddecrementsvaluebyunitIncrement. -
public increment(): VoidincrementsvaluebyunitIncrement.
Although these properties and functions should be fairly easy to grasp, I've created a simple application that demonstrates them to you. Before I reveal the application's source code, let's explore its UI (shown in Figure 1).
Figure 1: This UI lets you explore each of
ScrollBar's properties and functions.
The UI defaults to presenting a vertical scrollbar. You can switch
to a horizontal scrollbar by clicking the
Horizontal radio button.
Interestingly, the UI isn't vertically centered when a horizontal
scrollbar is shown.
Below the scrollbar are textboxes that reveal and let you enter min, value, max, block increment, and unit increment values. These controls are followed by a checkbox for setting (when checked) or clearing the click-to-position property.
The three buttons below the checkbox let you explore the
increment(), adjustValue(), and
decrement() functions. The button with the
0.5 label always passes
0.5 to adjustValue() in its action
handler.
I've created this application as a NetBeans IDE 6.5.1 with JavaFX
1.2 ScrollBarDemo project -- the project can be
downloaded via a link at the end of this article. The project's
Main.fx source code appears in Listing 1.
/*
* Main.fx
*/
package scrollbardemo;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TextBox;
import javafx.scene.control.ToggleGroup;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Flow;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
class Model
{
var min: Number on replace
{
smin = "{min}"
}
var smin: String on replace
{
min = Double.parseDouble (smin)
}
var value: Number on replace
{
svalue = "{value}"
}
var svalue: String on replace
{
value = Double.parseDouble (svalue)
}
var max: Number on replace
{
smax = "{max}"
}
var smax: String on replace
{
max = Double.parseDouble (smax)
}
var blockincr: Number on replace
{
sblockincr = "{blockincr}"
}
var sblockincr: String on replace
{
blockincr = Double.parseDouble (sblockincr)
}
var unitincr: Number on replace
{
sunitincr = "{unitincr}"
}
var sunitincr: String on replace
{
unitincr = Double.parseDouble (sunitincr)
}
}
def model = Model
{
min: 0.0
value: 50.0
max: 100.0
blockincr: ScrollBar {}.blockIncrement
unitincr: ScrollBar {}.unitIncrement
}
Stage
{
title: "ScrollBar Demo"
var sceneRef: Scene
scene: sceneRef = Scene
{
width: 450
height: 450
fill: Color.GOLD
var flowRef: Flow
content: flowRef = Flow
{
vertical: true
vgap: 20
layoutX: bind (sceneRef.width-flowRef.layoutBounds.width)/2-
flowRef.layoutBounds.minX
layoutY: bind (sceneRef.height-flowRef.layoutBounds.height)/2-
flowRef.layoutBounds.minY
var scrollBarRef: ScrollBar
var toggleGroup = ToggleGroup {}
var checkBoxRef: CheckBox
content:
[
Flow
{
hgap: 10
content:
[
RadioButton
{
text: "Horizontal"
toggleGroup: toggleGroup
}
RadioButton
{
text: "Vertical"
selected: true
toggleGroup: toggleGroup
}
]
}
scrollBarRef = ScrollBar
{
min: bind model.min with inverse
value: bind model.value with inverse
max: bind model.max with inverse
blockIncrement: bind model.blockincr with inverse
unitIncrement: bind model.unitincr with inverse
vertical: bind if (toggleGroup.selectedButton.text ==
"Vertical") then true else false
clickToPosition: bind if (checkBoxRef.selected) then true
else false
onKeyPressed: function (ke: KeyEvent): Void
{
if (ke.code == KeyCode.VK_UP or
ke.code == KeyCode.VK_LEFT)
scrollBarRef.decrement ()
else
if (ke.code == KeyCode.VK_DOWN or
ke.code == KeyCode.VK_RIGHT)
scrollBarRef.increment ()
else
if (ke.code == KeyCode.VK_PAGE_UP)
scrollBarRef.value -= scrollBarRef.blockIncrement
else
if (ke.code == KeyCode.VK_PAGE_DOWN)
scrollBarRef.value += scrollBarRef.blockIncrement
else
if (ke.code == KeyCode.VK_HOME)
scrollBarRef.value = scrollBarRef.min
else
if (ke.code == KeyCode.VK_END)
scrollBarRef.value = scrollBarRef.max
}
}
Flow
{
hgap: 10
content:
[
Label
{
text: "Min"
}
TextBox
{
text: bind model.smin with inverse
}
Label
{
text: "Value"
}
TextBox
{
text: bind model.svalue with inverse
}
Label
{
text: "Max"
}
TextBox
{
text: bind model.smax with inverse
}
]
}
Flow
{
hgap: 10
content:
[
Label
{
text: "Block Increment"
}
TextBox
{
text: bind model.sblockincr with inverse
}
Label
{
text: "Unit Increment"
}
TextBox
{
text: bind model.sunitincr with inverse
}
]
}
checkBoxRef = CheckBox
{
text: "ClickToPosition"
}
Flow
{
hgap: 10
content:
[
Button
{
text: "+"
action: function (): Void
{
scrollBarRef.increment ()
}
}
Button
{
text: "0.5"
action: function (): Void
{
scrollBarRef.adjustValue (0.5)
}
}
Button
{
text: "-"
action: function (): Void
{
scrollBarRef.decrement ()
}
}
]
}
]
}
}
}
Listing 1: Main.fx (from a
ScrollBarDemo NetBeans IDE 6.5.1 with JavaFX 1.2
project)
Following a list of imports, Main.fx declares a
Model class that describes the application's model.
This model consists of various numeric and string variables that
repectively bind with the UI's scrollbar and textboxes.
I provide numeric and string variables to overcome a limitation with binding. Basically, I want the value textbox to always reflect the scrollbar's current value. Also, I want the scrollbar's current value to always reflect whatever legal value is entered into the textbox.
The limitation is that I cannot directly bind two values of
different types. Specifically, I cannot bind the
ScrollBar instance's value property (of
type Number) to the TextBox instance's
text property (of type String) and
vice-versa.
I overcome this limitation by binding to model variables (of the
appropriate types), using replace triggers to convert between
types whenever a model variable changes, and specifying with
inverse to update a model variable whenever a control
property changes.
When initializing the Model instance's variables, I
specify blockincr: ScrollBar {}.blockIncrement and
unitincr: ScrollBar {}.unitIncrement. This is a
convenient way to initialize blockincr and
unitincr to ScrollBar's default values.
I chose to organize the UI around a flow container that lays out
the UI in terms of rows. I assigned bound expressions to the
container's layoutX and layoutY
variables to keep the UI centered, although the UI doesn't
vertically center when you choose the horizontal scrollbar.
By default, the scrollbar control doesn't have a keyboard
interface (although you can shift focus to this control via the
keyboard). However, it's not hard to add keyboard support: Simply
add an onKeyPressed function handler to accomplish
this task.
Conclusion
TheScrollBarDemo application's scrollbar is not like
the other input controls in that no Caspian border surrounds this
control when it receives the focus. However, with the help of a
good decompiler, it's possible to achieve this border, but that's
the subject for another article.
Note: Application created with JavaFX 1.2 (via NetBeans IDE 6.5.1) on top of Java SE 6u16.









