A few years ago, I developed a Windows-based units-conversion
application that converts between different kinds of units (such
as kilograms and pounds). More recently, I developed a JavaFX 1.2
version of this application, to reinforce my grasp of various
control classes in the javafx.scene.control package,
and to experiment in adapting a desktop-based user interface (UI)
to browser and mobile contexts.
This article presents three versions of my units-conversion application. The first version reveals the basic user interface. The second version adapts the first version's UI to various contexts by scaling fonts (except for the listview control's items font) and other factors. The final version shows you how to scale the listview control's items font to various contexts, and also fixes a focus problem with this control.
I created these versions via NetBeans IDE 6.5.1 with JavaFX
1.2 UnitsConverter1,
UnitsConverter2, and
UnitsConverter3 projects.
|
Units Converter: Version One
The first version of the units-conversion application introduces a UI that's also employed by the second and third versions, albeit with minor changes in these other versions' UIs. As Figure 1 reveals, this UI consists of title text, three labels, a couple of textboxes, a listview, a button, and some more text on a gradient background.
Figure 1: The listview doesn't automatically select its first item, an annoyance that will be fixed in the second version of this application.
Listing 1 presents the application's Main.fx source
code.
/*
* Main.fx
*/
package unitsconverter1;
import java.lang.NumberFormatException;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextBox;
import javafx.scene.layout.HBox;
import javafx.scene.layout.LayoutInfo;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
class Conversion
{
var src: String;
var dst: String;
var func: function (input: Double): Double
}
var conversions =
[
Conversion
{
src: "Millimeters"
dst: "Centimeters"
func: function (input: Double): Double
{
input*0.1
}
}
Conversion
{
src: "Centimeters"
dst: "Millimeters"
func: function (input: Double): Double
{
input*10
}
}
Conversion
{
src: "Miles"
dst: "Meters"
func: function (input: Double): Double
{
input*1609.344
}
}
Conversion
{
src: "Meters"
dst: "Miles"
func: function (input: Double): Double
{
input/1609.344
}
}
Conversion
{
src: "Degrees Celsius"
dst: "Degrees Fahrenheit"
func: function (input: Double): Double
{
input*9.0/5.0+32.0
}
}
Conversion
{
src: "Degrees Fahrenheit"
dst: "Degrees Celsius"
func: function (input: Double): Double
{
(input-32.0)*5.0/9.0
}
}
];
Stage
{
title: "Units Converter"
var sceneRef: Scene
scene: sceneRef = Scene
{
width: 550
height: 350
fill: LinearGradient
{
startX: 0.0
startY: 0.0
endX: 0.0
endY: 1.0
stops:
[
Stop { offset: 0.0 color: Color.YELLOW }
Stop { offset: 1.0 color: Color.PINK }
]
}
content: VBox
{
width: bind sceneRef.width
height: bind sceneRef.height
nodeHPos: HPos.CENTER
hpos: HPos.CENTER
vpos: VPos.CENTER
spacing: 20
var listView: ListView
var textInput: TextBox
var textOutput: TextBox
content:
[
Text
{
content: "Units Converter"
font: Font.font ("Verdana", 24)
}
HBox
{
content:
[
Label
{
text: "Enter number of input units"
}
textInput = TextBox
{
columns: 20
selectOnFocus: true
}
]
spacing: 20
}
VBox
{
spacing: 10
nodeHPos: HPos.CENTER
content:
[
Label
{
text: "Select a conversion"
}
listView = ListView
{
items: for (conversion in conversions)
"{conversion.src} To "
"{conversion.dst}"
layoutInfo: LayoutInfo
{
height: 76
width: 300
}
}
]
}
HBox
{
content:
[
Label
{
text: "Equivalent number of output units"
}
textOutput = TextBox
{
columns: 20
editable: false
}
]
spacing: 20
}
Button
{
text: "Convert"
action: function (): Void
{
try
{
var index = listView.selectedIndex;
var input = textInput.text;
var inVal = Double.parseDouble (input);
var outVal = conversions [index].
func (inVal);
textOutput.text = "{outVal}"
}
catch (nfe: NumberFormatException)
{
textOutput.text = "Error"
}
}
}
Text
{
content: "Created by Jeff Friesen"
font: Font.font ("Verdana", 8)
}
]
}
}
}
Listing 1: Main.fx (from a
UnitsConverter1 NetBeans IDE 6.5.1 with JavaFX 1.2
project)
Following various imports, Listing 1 specifies
Conversion as its model class. This class describes a
conversion in terms of text that identifies the source units to be
converted, text that identifies the resulting destination units,
and a function that performs the conversion. This function takes
the number of source units as its solitary argument, and returns
the equivalent number of destination units as its result.
Listing 1 next specifies a sequence of Conversion
instances. For brevity, I've kept this sequence short (there are
only six instances). Furthermore, I haven't sorted the sequence.
Feel free to add more Conversion instances to the
sequence, and to sort all of these instances into whatever order
you feel is appropriate.
Now that Listing 1 has taken care of the model, it turns its
attention to the UI. After assigning a title to the stage's
title variable, Listing 1 creates the UI's scene,
assigning the result to a local sceneRef variable and
the stage's scene variable. The sceneRef
variable is required so that the scene's layout container
(discussed later) can access scene width and height.
Regarding the scene, the first item of business is to specify the
scene's width and height, by assigning values to the scene's
width and height variables. Unlike the
stage's equivalent width and height
variables, the values assigned to the scene's width
and height variables don't take into account window
decorations such as a titlebar and border.
Because I'm focusing exclusively on the desktop profile for this
version, I've been cavalier in the values Listing 1 assigns to
width and height. In many cases, a
mobile device's screen will be smaller and part of the UI will be
hidden. Part of the UI will also be hidden when presenting the
application as an applet with a smaller width and/or height.
Moving on, the listing assigns a LinearGradient
instance to the scene's fill variable, to render the
scene's background via a gradient -- I chose lighter colors for
the gradient to achieve good contrast with the UI's black text.
Alternatively, you could assign a solid color to this variable, or
perhaps ignore fill in favor of presenting an image
as the scene's background.
The scene's content is specified via a VBox instance,
which is assigned to the scene's content variable. A
vbox is a container that lays out its content
sequence of nodes in a single vertical column. It resizes each
content node whose class implements the
Resizable interface to the node's preferred size.
Within the vbox, Listing 1 first binds this container's
width and height variables to the
scene's width and height variables
(which are accessed via the sceneRef local variable).
The idea is for the container to occupy the entire scene no matter
how the scene is resized, and binding to the scene's width and
height makes this possible.
To achieve the layout shown in Figure 1, it's necessary to center
the entire column of nodes horizontally and vertically. Listing 1
accomplishes this task by assigning HPos.CENTER to
the vbox's hPos variable, and by assigning
VPos.CENTER to the vbox's vPos variable.
It's also necessary to center the nodes within the column, by
assigning HPos.CENTER to the vbox's
nodeHPos variable.
After assigning 20 pixels of vertical space to the
vbox's spacing variable, to leave this much empty
vertical space between each node in this container's
content sequence, Listing 1 focuses on populating
this sequence. The following list highlights some of the more
interesting aspects of this task:
-
Font'spublic font(family: java.lang.String, size: Number): Fontfunction is used to specify a larger Verdana font for the title text. I've discovered that JavaFX uses Verdana as the default font for displaying text. -
trueis assigned to the input textbox'sselectOnFocusvariable, to cause the entire contents of this control to be selected when it receives focus. Doing this makes it easier to erase the control's contents (one keypress) when entering a new number of units for the next conversion. -
A sequence comprehension is used to populate the listview
control's
itemssequence with the source and destination text from the sequence ofConversioninstances. -
By default, the listview occupies too much screen space. To
constrain this control to a smaller region, its preferred
width and height are overridden by assigning a
LayoutInfoinstance to the control'slayoutInfovariable. The height is constrained to 76 pixels, allowing four rows to be shown. Because this constraint greatly shrinks the control's width, the width is specified as 300 pixels, which allows the largest conversion item to be completely shown. -
The output textbox is made uneditable by assigning
falseto itseditablevariable. However, you can still shift focus to this control via the keyboard or mouse. -
The function assigned to the button's
actionvariable performs the conversion. It obtains the listview'sselectedIndexvalue, and obtains the input textbox's value, which is parsed into a double-precision floating-point value. After performing the appropriate conversion, the result is assigned to the output textbox. If the input textbox's value cannot be parsed,Double.parseDouble()throws aNumberFormatException, andErroris output instead.
Now that you've explored how the code works, you'll want to build
and play with this application. Start up NetBeans IDE 6.5.1 with
JavaFX 1.2 and introduce a new UnitsConverter1
project. Then replace the project's skeletal Main.fx
source code with Listing 1. After accomplishing these tasks, press
F6 to compile and run the
application.
After starting the application, enter a numeric value into the
input textbox and (without selecting an item in the listview)
click the Convert button. The output
textbox reveals 0.0 no matter what value you enter
into the input textbox. Why? Hint: Check out the function assigned
to the button's action variable.
The first line in this function, var index =
listView.selectedIndex;, assigns the index of the selected
listview item to index. When no item is selected,
selectedIndex contains -1, which is assigned to
index. This variable is subsequently used to index
conversions. Instead of throwing an exception, JavaFX
ignores the -1 index and assigns 0.0 to outVal, which
is subsequently assigned to the output textbox.
This strange behavior, which results from JavaFX silently ignoring
an invalid sequence index, is bound to confuse the application's
users. Although you could fix this problem by using an if
statement to check selectedIndex's value for -1, and
displaying an error message if this is the case, I'll show you a
better solution in the next section.
Suppose that you change the application's profile to mobile and re-run the application. Figure 2 shows you the resulting UI on the mobile emulator's screen.
Figure 2: Because the UI is partly hidden, you can only scroll through the listview via the keyboard.
Now suppose that you change the profile to browser and re-run the application. Figure 3 shows you the resulting UI when displayed via a NetBeans-generated applet at its default 200-by-200-pixel dimensions.
Figure 3: Once again, you can only scroll through the listview via the keyboard.
The fact that the application's UI looks terrible on mobile and browser screens is more than an annoyance: It's a real problem that must be overcome before we can deploy this application to these other contexts. Fortunately, there's a solution that lets us adapt the UI to whatever screen size we're faced with. I'll reveal this solution in the next section.
Units Converter: Version Two
The second version of the units-conversion application corrects an annoyance with the first version where no item is selected in the listview at startup. Ideally, the first item should be selected. The second version also adapts the desktop UI so that it scales nicely to a smaller size when viewed in browser or mobile profiles (where the size is typically smaller). For example, Figure 4 shows the UI adapted to the browser profile.
Figure 4: Although it looks better than Figure 3, the browser-based UI reveals that the listview's text size is greatly out of proportion to the rest of the text.
Listing 2 presents the application's Main.fx source
code.
/*
* Main.fx
*/
package unitsconverter2;
import java.lang.NumberFormatException;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextBox;
import javafx.scene.layout.HBox;
import javafx.scene.layout.LayoutInfo;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.util.Math;
class Conversion
{
var src: String;
var dst: String;
var func: function (input: Double): Double
}
var conversions =
[
Conversion
{
src: "Millimeters"
dst: "Centimeters"
func: function (input: Double): Double
{
input*0.1
}
}
Conversion
{
src: "Centimeters"
dst: "Millimeters"
func: function (input: Double): Double
{
input*10
}
}
Conversion
{
src: "Miles"
dst: "Meters"
func: function (input: Double): Double
{
input*1609.344
}
}
Conversion
{
src: "Meters"
dst: "Miles"
func: function (input: Double): Double
{
input/1609.344
}
}
Conversion
{
src: "Degrees Celsius"
dst: "Degrees Fahrenheit"
func: function (input: Double): Double
{
input*9.0/5.0+32.0
}
}
Conversion
{
src: "Degrees Fahrenheit"
dst: "Degrees Celsius"
func: function (input: Double): Double
{
(input-32.0)*5.0/9.0
}
}
];
// Assume desktop profile and a 550-by-350-pixel viewing area.
var width = 550.0;
var height = 350.0;
// If desktop profile, narrow the width and height if they exceed the desktop's
// primary screen's current bounds.
if (__PROFILE__ == "desktop")
{
def bounds = Screen.primary.bounds;
width = Math.min (bounds.width, width);
height = Math.min (bounds.height, height)
}
// At this point, the actual width and height of the desktop's viewing area are
// known.
// However, it's possible that the profile is mobile or browser, in which case
// the viewing area is probably smaller. To account for this possibility,
// calculate appropriate horizontal and vertical scaling factors, and choose an
// appropriate size for the default font.
var sceneRef: Scene;
def scale_factorX = bind sceneRef.width/width;
def scale_factorY = bind sceneRef.height/height;
def scale_factor = bind Math.min (scale_factorX, scale_factorY);
// According to the JavaFX Font documentation, the font size defaults to 12
// points. However, I've discovered that the actual setting (on a Windows XP
// platform) is 11 points. This setting can be left as-is for the desktop
// profile, but will need to be shrunk for the mobile and browser profiles.
def font = bind Font.font ("Verdana", 5+scale_factor*6);
def titleFont = bind Font.font ("Verdana", 16+scale_factor*8);
def footerFont = bind Font.font ("Verdana", 4+scale_factor*4);
Stage
{
title: "Units Converter"
scene: sceneRef = Scene
{
width: width
height: height
fill: LinearGradient
{
startX: 0.0
startY: 0.0
endX: 0.0
endY: 1.0
stops:
[
Stop { offset: 0.0 color: Color.YELLOW }
Stop { offset: 1.0 color: Color.PINK }
]
}
content: VBox
{
width: bind sceneRef.width
height: bind sceneRef.height
nodeHPos: HPos.CENTER
hpos: HPos.CENTER
vpos: VPos.CENTER
spacing: bind 20*scale_factorY
var listView: ListView
var textInput: TextBox
var textOutput: TextBox
content:
[
Text
{
content: "Units Converter"
font: bind titleFont
}
HBox
{
content:
[
Label
{
text: "Enter number of input units"
font: bind font
}
textInput = TextBox
{
columns: bind 20*scale_factorX
selectOnFocus: true
font: bind font
}
]
spacing: bind 20*scale_factorX
}
VBox
{
spacing: bind 10*scale_factorY
nodeHPos: HPos.CENTER
content:
[
Label
{
text: "Select a conversion"
font: bind font
}
listView = MyListView
{
items: for (conversion in conversions)
"{conversion.src} To "
"{conversion.dst}"
layoutInfo: LayoutInfo
{
height: bind 76*scale_factorY
width: bind 300*scale_factorX
}
}
]
}
HBox
{
content:
[
Label
{
text: "Equivalent number of output units"
font: bind font
}
textOutput = TextBox
{
columns: bind 20*scale_factorX
editable: false
font: bind font
}
]
spacing: bind 20*scale_factorX
}
Button
{
text: "Convert"
font: bind font
action: function (): Void
{
try
{
var index = listView.selectedIndex;
var input = textInput.text;
var inVal = Double.parseDouble (input);
var outVal = conversions [index].
func (inVal);
textOutput.text = "{outVal}"
}
catch (nfe: NumberFormatException)
{
textOutput.text = "Error"
}
}
}
Text
{
content: "Created by Jeff Friesen"
font: bind footerFont
}
]
}
}
}
class MyListView extends ListView
{
init
{
select (0)
}
}
Listing 2: Main.fx (from a
UnitsConverter2 NetBeans IDE 6.5.1 with JavaFX 1.2
project)
Listing 2 solves the annoyance problem of no listview item being
initially selected, by subclassing ListView and
invoking this superclass's public select(itemIndex:
Integer): Void function with a 0 argument to
select the first item in the listview. Of course,
MyListView must be instantiated instead of
ListView for this feature to take effect.
You might be wondering why I didn't invoke ListView's
public selectFirstRow(): Void function to accomplish
this task. After all, this function is more appropriately named.
However, my decompiler shows that this function executes
select (0) only when ListView's
selectedIndex variable contains a value that's
greater than or equal to 0 -- it defaults to -1.
More challenging than fixing this annoyance problem is the task of adapting the UI to look nice on smaller browser-based applet and mobile screens. I want to maintain Figure 1's desktop layout without having to rearrange the controls. Essentially, the controls and the spacings between them should scale down appropriately and still be readable.
Although I could simply assign fractional values (between 0.0 and
1.0) to a control's/container's scaleX and
scaleY variables, this isn't appropriate because it
results in text that's very hard to read. Instead, I've chosen to
scale down each control's/text node's font, which you've probably
figured out while examining Listing 2.
The code assumes that the desktop profile is current and generates
an appropriate width and height based on the dimensions of the
desktop's primary screen. It then creates scaling factors for
scaling the horizontal/vertical spaces between controls as well as
textbox columns (scale_factorX and
scale_factorY), and for scaling control/text fonts
(scale_factor).
The scaling factors are based on the scene's width
and height variable values. Binding causes these
values to eventually reflect the desktop's, the browser applet
area's, or the mobile emulator screen's dimensions. For the
desktop profile, scale_factorX and
scale_factorY are set to 1.0. For the other profiles,
these variables are set to fractions greater than 0.0 and less
than 1.0.
Moving on, three font instances are created: one instance for
label text, one instance for title text, and one instance for
footer text. These instances are all based on the default Verdana
font, and their sizes are dynamically calculated by taking the
scale_factor value (between 0.0 and 1.0) into
account. For a scale_factor value of 0.5 or higher,
the text should be legible. (Try adjusting the various scaling
constants.)
Finally, Listing 2 takes advantage of the font and scaling variables in a bind context to dynamically size text, various controls, and the spacing between these controls. You've already seen the result of this dynamic activity in Figure 4's browser context. Figure 5 shows the result of running this application in the mobile emulator, in landscape mode.
Figure 5: Once again, the listview's text size is out of proportion to the rest of the text.
Unfortunately, the listview control doesn't let us shrink the size
of the font used to display each item's text -- assigning a
different font to listview's caspian skin's font
variable accomplishes nothing. To overcome this problem, we'll
need to create an improved listview control, which is the focus of
the next section.
Units Converter: Version Three
The third and final version of the units-conversion application extends the second version by scaling the font used by the listview control to present its text. It accomplishes this task by extending this control's caspian-based skin class with a new class that focuses on adjusting each list item's font. Figure 6 reveals the resulting UI when shown in a mobile context (in landscape mode).
Figure 6: Although not perfect (one listview item has its text partly cut off), at least the size of each listview item's text is in keeping with the size of each label's text.
Listing 3 presents the application's Main.fx source
code.
/*
* Main.fx
*/
package unitsconverter3;
import java.lang.NumberFormatException;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.Skin;
import javafx.scene.control.TextBox;
import javafx.scene.layout.HBox;
import javafx.scene.layout.LayoutInfo;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.util.Math;
class Conversion
{
var src: String;
var dst: String;
var func: function (input: Double): Double
}
var conversions =
[
Conversion
{
src: "Millimeters"
dst: "Centimeters"
func: function (input: Double): Double
{
input*0.1
}
}
Conversion
{
src: "Centimeters"
dst: "Millimeters"
func: function (input: Double): Double
{
input*10
}
}
Conversion
{
src: "Miles"
dst: "Meters"
func: function (input: Double): Double
{
input*1609.344
}
}
Conversion
{
src: "Meters"
dst: "Miles"
func: function (input: Double): Double
{
input/1609.344
}
}
Conversion
{
src: "Degrees Celsius"
dst: "Degrees Fahrenheit"
func: function (input: Double): Double
{
input*9.0/5.0+32.0
}
}
Conversion
{
src: "Degrees Fahrenheit"
dst: "Degrees Celsius"
func: function (input: Double): Double
{
(input-32.0)*5.0/9.0
}
}
];
// Assume desktop profile and a 550-by-350-pixel viewing area.
var width = 550.0;
var height = 350.0;
// If desktop profile, narrow the width and height if they exceed the desktop's
// primary screen's current bounds.
if (__PROFILE__ == "desktop")
{
def bounds = Screen.primary.bounds;
width = Math.min (bounds.width, width);
height = Math.min (bounds.height, height)
}
// At this point, the actual width and height of the desktop's viewing area are
// known.
// However, it's possible that the profile is mobile or browser, in which case
// the viewing area is probably smaller. To account for this possibility,
// calculate appropriate horizontal and vertical scaling factors, and choose an
// appropriate size for the default font.
var sceneRef: Scene;
def scale_factorX = bind sceneRef.width/width;
def scale_factorY = bind sceneRef.height/height;
def scale_factor = bind Math.min (scale_factorX, scale_factorY);
// According to the JavaFX Font documentation, the font size defaults to 12
// points. However, I've discovered that the actual setting (on a Windows XP
// platform) is 11 points. This setting can be left as-is for the desktop
// profile, but will need to be shrunk for the mobile and browser profiles.
def font = bind Font.font ("Verdana", 5+scale_factor*6);
def titleFont = bind Font.font ("Verdana", 16+scale_factor*8);
def footerFont = bind Font.font ("Verdana", 4+scale_factor*4);
Stage
{
title: "Units Converter"
scene: sceneRef = Scene
{
width: width
height: height
fill: LinearGradient
{
startX: 0.0
startY: 0.0
endX: 0.0
endY: 1.0
stops:
[
Stop { offset: 0.0 color: Color.YELLOW }
Stop { offset: 1.0 color: Color.PINK }
]
}
content: VBox
{
width: bind sceneRef.width
height: bind sceneRef.height
nodeHPos: HPos.CENTER
hpos: HPos.CENTER
vpos: VPos.CENTER
spacing: bind 20*scale_factorY
var listView: ListView
var textInput: TextBox
var textOutput: TextBox
content:
[
Text
{
content: "Units Converter"
font: bind titleFont
}
HBox
{
content:
[
Label
{
text: "Enter number of input units"
font: bind font
}
textInput = TextBox
{
columns: bind 20*scale_factorX
selectOnFocus: true
font: bind font
}
]
spacing: bind 20*scale_factorX
}
VBox
{
spacing: bind 10*scale_factorY
nodeHPos: HPos.CENTER
content:
[
Label
{
text: "Select a conversion"
font: bind font
}
listView = MyListView
{
items: for (conversion in conversions)
"{conversion.src} To "
"{conversion.dst}"
layoutInfo: LayoutInfo
{
height: bind 76*scale_factorY
width: bind 300*scale_factorX
}
}
]
}
HBox
{
content:
[
Label
{
text: "Equivalent number of output units"
font: bind font
}
textOutput = TextBox
{
columns: bind 20*scale_factorX
editable: false
font: bind font
}
]
spacing: bind 20*scale_factorX
}
Button
{
text: "Convert"
font: bind font
action: function (): Void
{
try
{
var index = listView.selectedIndex;
var input = textInput.text;
var inVal = Double.parseDouble (input);
var outVal = conversions [index].
func (inVal);
textOutput.text = "{outVal}"
}
catch (nfe: NumberFormatException)
{
textOutput.text = "Error"
}
}
}
Text
{
content: "Created by Jeff Friesen"
font: bind footerFont
}
]
}
}
}
class MyListView extends ListView
{
var _skin = MyListViewSkin {}
var _font = bind font on replace
{
_skin._font = _font;
}
init
{
select (0);
}
override function createDefaultSkin (): Skin
{
_skin
}
}
Listing 3: Main.fx (from a
UnitsConverter3 NetBeans IDE 6.5.1 with JavaFX 1.2
project)
Listing 3 extends Listing 2's MyListView class by
introducing a new _skin variable that's assigned a
MyListViewSkin instance (this class is described
shortly), by introducing a new _font variable whose
replace trigger initializes _skin's
_font variable whenever font changes,
and by overriding the ListView superclass's
createDefaultSkin() function to return
_skin's value.
Listing 4 presents the application's
MyListViewSkin.java source code.
/*
* MyListViewSkin.java
*/
package unitsconverter3;
import javafx.scene.control.Label;
import javafx.scene.text.Font;
import com.sun.javafx.scene.control.Cell;
import com.sun.javafx.scene.control.caspian.ListViewSkin;
public class MyListViewSkin extends ListViewSkin
{
Font _font;
MyListViewSkin ()
{
loc$com$sun$javafx$scene$control$caspian$ListViewSkin$vbar.get ().
set$focusTraversable(false);
}
@Override
public Cell createCell(int itemIndex, Cell recycled)
{
Cell cell = super.createCell (itemIndex, recycled);
Label l = (Label) cell.$content;
l.set$font (_font);
return cell;
}
}
Listing 4: MyListViewSkin.java (from
a UnitsConverter3 NetBeans IDE 6.5.1 with JavaFX 1.2
project)
MyListViewSkin's constructor overcomes a focus
problem with the listview control. Specifically, pressing the
Tab key (on Windows) doesn't
transfer focus to the output textbox. In fact, no non-listview
control has the focus (as indicated by surrounding the control
with a blue focus rectangle). You must press
Tab a second time to transfer focus
to the output textbox.
It turns out that the first Tab
press causes focus to shift to the scrollbar control, which is
part of the listview control. The solution to this problem is to
remove the scrollbar from the focus traversal cycle. In JavaFX,
you would simply assign false to scrollbar's
inherited focusTraversable variable. Listing 4 shows
how this task is accomplished in Java.
The reason for implementing MyListViewSkin in Java
instead of JavaFX is that I couldn't implement the
createCell() method in JavaFX. This method ends up
creating a label control for a listview cell. After extracting the
cell's label, createCell() assigns
MyListView's current _font setting to
the label's font variable.
Conclusion
Although I've accomplished my objectives with units converter, I feel like overhauling the listview control (beyond customizing the font and fixing the focus problem). As you work with this control, you'll encounter various problems, such as empty space appearing at the top of the list, or the scrollbar disappearing once you move to the last item in the list (by continually pressing the down-arrow key).Apart from listview, I'm troubled by small text appearing to "bleed" on the mobile emulator's screen. When you observe the smaller text in Figures 4 (browser context) and 5/6 (mobile context), you'll probably notice that it appears ragged in Figures 5/6. Ironically, it looks crisper in Figure 4's smaller screen size, which suggests a problem with the mobile emulator's ability to clearly display small text.
Note: Applications created with JavaFX 1.2 (via NetBeans IDE 6.5.1) on top of Java SE 6u12.



