Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

JavaWorld Daily Brew

A SwingSpinnerList component for JavaFX

 

JavaFX 1.0 and 1.1 provide a javafx.ext.swing package of Swing-based component classes that JavaFX scripts can access to build desktop-oriented user interfaces. Although this package contains many useful component classes, it lacks classes for advanced Swing components such as progress bars, trees, and tables. However, it's easy to create classes for these overlooked components.

The information for making an arbitrary Swing component accessible to JavaFX code is found in the Using Custom Swing Components technical tip from Sun Microsystems. According to this tip, you need to perform the following tasks (the first two tasks are mandatory):

  • Create a wrapper class for the Swing component. The wrapper class must extend javafx.ext.swing.SwingComponent.
  • Override SwingComponent's protected abstract createJComponent(): javax.swing.JComponent function to return an instance of the Swing component.
  • If it's necessary to handle events from the Swing component, define appropriate JavaFX variables of function types, and create/register Swing event listeners that delegate event handling to the functions assigned to these variables.

The Swing components section of the Planet JFX Wiki website demonstrates (mostly the first two of) these tasks as it makes Swing's password field, progress bar, tabbed pane, tree, table, menu, and color chooser components accessible to JavaFX. However, it ignores the spinner component.

Introduced by Java 1.4, a spinner is a single-line input field that lets users select number/object values from an ordered sequence. This field is accompanied by a pair of small arrow buttons for stepping through the sequence's values. The spinner's editor can let the user type any value into the input field, but ignores the value if not in the sequence. Unlike combo boxes, spinners don't present drop-down lists.

Swing implements the spinner component via javax.swing.JSpinner and related model and editor classes. To construct a spinner with a specific model, invoke JSpinner's public JSpinner(SpinnerModel model) constructor. For example, invoke this constructor with an instance of the javax.swing.SpinnerListModel class to create a spinner for stepping through a list of objects.

Listing 1 presents the source code to a SwingSpinnerList class that makes the combination of JSpinner and SpinnerListModel accessible to JavaFX.

Listing 1: The SwingSpinnerList class

class SwingSpinnerList extends SwingComponent
{
    var spinner: JSpinner;

    public var value: String = spinner.getValue () as String;

    public var items: Object[] on replace
    {
        var model = new SpinnerListModel (items);
        var listener = ChangeListener
        {
            public override function stateChanged (ce: ChangeEvent)
            {
                value = model.getValue ().toString ()
            }
        }
        model.addChangeListener (listener);
        spinner.setModel (model);

        if (not (items [0] instanceof String))
        {
            var editor = spinner.getEditor () as JSpinner.DefaultEditor;
            editor.getTextField ().setEditable (false)
        }

        value = items [0].toString ()
    }

    public override function createJComponent ()
    {
        spinner = new JSpinner (new SpinnerListModel ())
    }
}

SwingSpinnerList's items attribute identifies the objects managed by the spinner's model. Whenever you assign a new sequence of objects to this attribute, its replace trigger creates a new SpinnerListModel that stores these objects. Because the spinner's editor ignores all entered values for models that don't store Strings, the trigger disables the editor unless items' element type is String.

The value attribute stores the selected item's string representation, and defaults to the single "empty" string literal that a spinner assigns to its model whenever a SwingSpinnerList is created without assigning a sequence to items. When this assignment occurs, however, value is initialized to the string representation of the first item in the sequence. It's also updated whenever the user selects a new item.

Listing 2 presents an excerpt from a script that creates five instances of SwingSpinnerList, and proves that you can assign different types of objects to its items attribute.

Listing 2: SwingSpinnerList demo script excerpt

Stage
{
    title: "SwingSpinnerList Demo"

    width: 250
    height: 250

    scene: Scene
    {
        content: VBox
        {
            spacing: 5
            translateX: 30
            translateY: 30

            content:
            [
                HBox
                {
                    spacing: 5

                    var spinnerRef: SwingSpinnerList
                    content:
                    [
                        SwingLabel
                        {
                            text: "Default:"
                        }
                        spinnerRef = SwingSpinnerList
                        {
                            width: 100
                        }
                        SwingLabel
                        {
                            text: bind spinnerRef.value
                            width: 50
                        }
                    ]
                }
                HBox
                {
                    spacing: 5

                    var spinnerRef: SwingSpinnerList
                    content:
                    [
                        SwingLabel
                        {
                            text: "String:"
                        }
                        spinnerRef = SwingSpinnerList
                        {
                            items: [ "first", "second", "third" ]
                            width: 100
                        }
                        SwingLabel
                        {
                            text: bind spinnerRef.value
                            width: 50
                        }
                    ]
                }
                HBox
                {
                    spacing: 5

                    var spinnerRef: SwingSpinnerList
                    content:
                    [
                        SwingLabel
                        {
                            text: "Boolean:"
                        }
                        spinnerRef = SwingSpinnerList
                        {
                            items: [ true, false ]
                            width: 100
                        }
                        SwingLabel
                        {
                            text: bind spinnerRef.value
                            width: 50
                        }
                    ]
                }
                HBox
                {
                    spacing: 5

                    var spinnerRef: SwingSpinnerList
                    content:
                    [
                        SwingLabel
                        {
                            text: "Integer:"
                        }
                        spinnerRef = SwingSpinnerList
                        {
                            items: [ 1, 2, 3, 4 ]
                            width: 50
                        }
                        SwingLabel
                        {
                            text: bind spinnerRef.value
                            width: 50
                        }
                    ]
                }
                HBox
                {
                    spacing: 5

                    var spinnerRef: SwingSpinnerList
                    content:
                    [
                        SwingLabel
                        {
                            text: "Number:"
                        }
                        spinnerRef = SwingSpinnerList
                        {
                            items: [ 1.5, 2.5, 3.5 ]
                            width: 100
                        }
                        SwingLabel
                        {
                            text: bind spinnerRef.value
                            width: 50
                        }
                    ]
                }
            ]
        }
   }
}

After compiling and running the demo script (see this post's code archive for the script's NetBeans project's Main.fx file), you should see a user interface similar to that shown in Figure 1.

Figure 1: Five instances of SwingSpinnerList. (Click to enlarge.)

As you've just seen, it's not hard to support a limited version of the spinner component. If you're up for a more challenging task, create a SwingSpinner class that generalizes SwingSpinnerList by supporting all of the spinner models. Furthermore, introduce support for specifying custom editors as JavaFX classes and associating a custom editor with a SwingSpinner instance.

Download a source file: cs j32409-src.zip

Like this blog? Subscribe to the CSJ Explorer RSS feed