Scrolling Through a Sequence of Labels
I previously demoed thejavafx.scene.control.ScrollBar class in my
Exploring JavaFX's ScrollBar Control article. In this article,
I present a more useful ScrollBar demonstration.
The demo application's user interface provides a horizontal
scrollbar that lets you scroll through a sequence of
javafx.scene.control.Label instances. As shown in
Figure 1, only the label associated with the scrollbar's current
value is displayed.
Figure 1: Scrolling through a sequence of labels.
As you interact with the scrollbar via the keyboard or mouse, the
scrollbar's value variable is updated and its
associated label (which shows a small country flag image and
country name) is revealed above the scrollbar. The scrollbar's
value setting is shown below the scrollbar.
I've created this application as a NetBeans IDE 6.5.1 with JavaFX
1.2 ScrollableStackOfLabels 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 scrollablestackoflabels;
import javafx.geometry.HPos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollBar;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Flow;
import javafx.scene.layout.Stack;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
def countries =
[
"AD,Andorra",
"AE,United Arab Emirates",
"AF,Afghanistan",
"AG,Antigua and Barbuda",
"AI,Anguilla",
"AL,Albania",
"AM,Armenia",
"AN,Netherlands Antilles",
"AO,Angola",
"AR,Argentina",
"AS,American Samoa",
"AT,Austria",
"AU,Australia",
"AW,Aruba",
"AX,Åland Islands",
"AZ,Azerbaijan",
"BA,Bosnia and Herzegovina",
"BB,Barbados",
"BD,Bangladesh",
"BE,Belgium",
"BF,Burkina Faso",
"BG,Bulgaria",
"BH,Bahrain",
"BI,Burundi",
"BJ,Benin",
"BM,Bermuda",
"BN,Brunei Darussalam",
"BO,Bolivia",
"BR,Brazil",
"BS,Bahamas",
"BT,Bhutan",
"BV,Bouvet Island",
"BW,Botswana",
"BY,Belarus",
"BZ,Belize",
"CA,Canada",
"CC,Cocos (Keeling) Islands",
"CD,Congo, the Democratic Republic of the",
"CF,Central African Republic",
"CG,Congo",
"CH,Switzerland",
"CI,Cote d'Ivoire Côte d'Ivoire",
"CK,Cook Islands",
"CL,Chile",
"CM,Cameroon",
"CN,China",
"CO,Colombia",
"CR,Costa Rica",
"CU,Cuba",
"CV,Cape Verde",
"CX,Christmas Island",
"CY,Cyprus",
"CZ,Czech Republic",
"DE,Germany",
"DJ,Djibouti",
"DK,Denmark",
"DM,Dominica",
"DO,Dominican Republic",
"DZ,Algeria",
"EC,Ecuador",
"EE,Estonia",
"EG,Egypt",
"EH,Western Sahara",
"ER,Eritrea",
"ES,Spain",
"ET,Ethiopia",
"FI,Finland",
"FJ,Fiji",
"FK,Falkland Islands (Malvinas)",
"FM,Micronesia, Federated States of",
"FO,Faroe Islands",
"FR,France",
"GA,Gabon",
"GB,United Kingdom",
"GD,Grenada",
"GE,Georgia",
"GF,French Guiana",
"GH,Ghana",
"GI,Gibraltar",
"GL,Greenland",
"GM,Gambia",
"GN,Guinea",
"GP,Guadeloupe",
"GQ,Equatorial Guinea",
"GR,Greece",
"GS,South Georgia and the South Sandwich Islands",
"GT,Guatemala",
"GU,Guam",
"GW,Guinea-Bissau",
"GY,Guyana",
"HK,Hong Kong",
"HM,Heard Island and McDonald Islands",
"HN,Honduras",
"HR,Croatia",
"HT,Haiti",
"HU,Hungary",
"ID,Indonesia",
"IE,Ireland",
"IL,Israel",
"IN,India",
"IO,British Indian Ocean Territory",
"IQ,Iraq",
"IR,Iran, Islamic Republic of",
"IS,Iceland",
"IT,Italy",
"JM,Jamaica",
"JO,Jordan",
"JP,Japan",
"KE,Kenya",
"KG,Kyrgyzstan",
"KH,Cambodia",
"KI,Kiribati",
"KM,Comoros",
"KN,Saint Kitts and Nevis",
"KP,Korea, Democratic People's Republic of",
"KR,Korea, Republic of",
"KW,Kuwait",
"KY,Cayman Islands",
"KZ,Kazakhstan",
"LA,Lao People's Democratic Republic",
"LB,Lebanon",
"LC,Saint Lucia",
"LI,Liechtenstein",
"LK,Sri Lanka",
"LR,Liberia",
"LS,Lesotho",
"LT,Lithuania",
"LU,Luxembourg",
"LV,Latvia",
"LY,Libyan Arab Jamahiriya",
"MA,Morocco",
"MC,Monaco",
"MD,Moldova, Republic of",
"ME,Montenegro",
"MG,Madagascar",
"MH,Marshall Islands",
"MK,Macedonia, the former Yugoslav Republic of",
"ML,Mali",
"MM,Myanmar",
"MN,Mongolia",
"MO,Macao",
"MP,Northern Mariana Islands",
"MQ,Martinique",
"MR,Mauritania",
"MS,Montserrat",
"MT,Malta",
"MU,Mauritius",
"MV,Maldives",
"MW,Malawi",
"MX,Mexico",
"MY,Malaysia",
"MZ,Mozambique",
"NA,Namibia",
"NC,New Caledonia",
"NE,Niger",
"NF,Norfolk Island",
"NG,Nigeria",
"NI,Nicaragua",
"NL,Netherlands",
"NO,Norway",
"NP,Nepal",
"NR,Nauru",
"NU,Niue",
"NZ,New Zealand",
"OM,Oman",
"PA,Panama",
"PE,Peru",
"PF,French Polynesia",
"PG,Papua New Guinea",
"PH,Philippines",
"PK,Pakistan",
"PL,Poland",
"PM,Saint Pierre and Miquelon",
"PN,Pitcairn",
"PR,Puerto Rico",
"PS,Palestinian Territory, Occupied",
"PT,Portugal",
"PW,Palau",
"PY,Paraguay",
"QA,Qatar",
"RE,Reunion Réunion",
"RO,Romania",
"RS,Serbia",
"RU,Russian Federation",
"RW,Rwanda",
"SA,Saudi Arabia",
"SB,Solomon Islands",
"SC,Seychelles",
"SD,Sudan",
"SE,Sweden",
"SG,Singapore",
"SH,Saint Helena",
"SI,Slovenia",
"SJ,Svalbard and Jan Mayen",
"SK,Slovakia",
"SL,Sierra Leone",
"SM,San Marino",
"SN,Senegal",
"SO,Somalia",
"SR,Suriname",
"ST,Sao Tome and Principe",
"SV,El Salvador",
"SY,Syrian Arab Republic",
"SZ,Swaziland",
"TC,Turks and Caicos Islands",
"TD,Chad",
"TF,French Southern Territories",
"TG,Togo",
"TH,Thailand",
"TJ,Tajikistan",
"TK,Tokelau",
"TL,Timor-Leste",
"TM,Turkmenistan",
"TN,Tunisia",
"TO,Tonga",
"TR,Turkey",
"TT,Trinidad and Tobago",
"TV,Tuvalu",
"TW,Taiwan, Province of China",
"TZ,Tanzania, United Republic of",
"UA,Ukraine",
"UG,Uganda",
"UM,United States Minor Outlying Islands",
"US,United States",
"UY,Uruguay",
"UZ,Uzbekistan",
"VA,Holy See (Vatican City State)",
"VC,Saint Vincent and the Grenadines",
"VE,Venezuela",
"VG,Virgin Islands, British",
"VI,Virgin Islands, U.S.",
"VN,Viet Nam",
"VU,Vanuatu",
"WF,Wallis and Futuna",
"WS,Samoa",
"YE,Yemen",
"YT,Mayotte",
"ZA,South Africa",
"ZM,Zambia",
"ZW,Zimbabwe"
];
var scrollBarRef: ScrollBar;
var countryLabels: Label [];
for (i in [0..<sizeof countries])
insert Label
{
graphic: ImageView
{
image: Image
{
url: "{__DIR__}icons/"
"{countries [i].substring (0, 2).toLowerCase ()}"
".png"
}
}
text: countries [i].substring (3)
hpos: HPos.CENTER
opacity: bind if ((scrollBarRef.value as Integer) == i) then 1.0
else 0.0
}
into countryLabels;
Stage
{
title: "ScrollableStackOfLabels"
var sceneRef: Scene
scene: sceneRef = Scene
{
width: 300
height: 200
fill: Color.GOLD
var flowRef: Flow
content: flowRef = Flow
{
layoutX: bind (sceneRef.width-flowRef.layoutBounds.width)/2
-flowRef.layoutBounds.minX
layoutY: bind (sceneRef.height-flowRef.layoutBounds.height)/2
-flowRef.layoutBounds.minY
vertical: true
vgap: 40
content:
[
Stack
{
content: countryLabels
}
scrollBarRef = ScrollBar
{
min: 0
max: sizeof countryLabels-1
unitIncrement: 1
clickToPosition: true
onKeyPressed: function (ke: KeyEvent): Void
{
if (ke.code == KeyCode.VK_LEFT)
scrollBarRef.decrement ()
else
if (ke.code == KeyCode.VK_RIGHT)
scrollBarRef.increment ()
else
if (ke.code == KeyCode.VK_HOME)
scrollBarRef.adjustValue (0.0)
else
if (ke.code == KeyCode.VK_END)
scrollBarRef.adjustValue (1.0)
else
if (ke.code == KeyCode.VK_PAGE_UP)
scrollBarRef.adjustValue ((scrollBarRef.value-10)/
scrollBarRef.max)
else
if (ke.code == KeyCode.VK_PAGE_DOWN)
scrollBarRef.adjustValue ((scrollBarRef.value+10)/
scrollBarRef.max)
}
}
Label
{
text: bind "Value: {scrollBarRef.value}"
}
]
}
}
}
Listing 1: Main.fx (from a
ScrollableStackOfLabels NetBeans IDE 6.5.1 with
JavaFX 1.2 project)
Following various imports, Main.fx creates a sequence
of Labels, assigning a
javafx.scene.image.ImageView node containing a
country's flag image to Label's graphic
variable, and assigning the country name to the label's
text variable.
Main.fx next creates the scene. It places scene
content (a javafx.scene.layout.Stack instance, a
ScrollBar instance, and a Label
instance) inside a javafx.scene.layout.Flow container
instance that lays out this content vertically.
The Stack instance lays out the Labels
sequence such that one label appears over labels below its
sequence position. Each label's content is centered within the
stack (which sizes to contain the largest label) by virtue of
previously assigning HPos.CENTER to
Label's hpos variable.
For the scrolling to work, it's important that the scrollbar's
min variable be set to 0, the index of
the first label in the stack, and its max variable
be set to the index of the last label in the stack.
Except for the currently displayed label, each label has its
opacity variable set to 0.0, which hides
the label. Because the scrollbar's value variable is
part of an expression that's bound to the label's
opacity variable, only the label associated with the
current value is displayed.
The as Integer cast in the (scrollBarRef.value
as Integer) == i expression prevents a problem where the
label disappears when you drag the scrollbar's thumb
(draggable bar within the scrollbar's track).
Without the cast, dragging the thumb causes
scrollBarRef.value to contain a non-zero fraction
that, when compared with i (after i is
converted to a floating-point value with a zero fraction), results
in 0.0 (hide) instead of 1.0 (show)
being assigned to opacity.
The standalone Label that's added to
Flow reveals the current setting of the scrollbar's
value property, which is bound to the
Label's text variable. As Figure 1
reveals, this value may have a non-zero fraction, which proves the
need for the as Integer cast.
Perhaps you're wondering why I subtract
-flowRef.layoutBounds.minX from the expression bound
to flowRef's layoutX variable, and
subtract -flowRef.layoutBounds.minY from
flowRef's layoutY variable.
In her
JavaFX1.2 Layout blog post, Amy Fowler mentioned that
layoutX/layoutY define a translation on
the node's coordinate space to adjust it from its current
layoutBounds.minX/minY values.
Because layoutX and layoutY are not
final position values, you must subtract
layoutBounds.minX and layoutBounds.maxX
from layoutX and layoutY to achieve the
node's final layout position.
Conclusion
This article demonstrated a technique for scrolling through a sequence of labels where only the current label is displayed during the scroll. You can employ this technique when you need to add scrolling to your user interface, but limited space prevents the display of multiple items.
Note: Application created with JavaFX 1.2 (via NetBeans IDE 6.5.1) on top of Java SE 6u16.









