0

I'm doing a GUI in JavaFX 8.

My inputs go through either

TextFields - all the same size & styling

ComboBoxes - all the same size & styling and same as that on TextFields

Sliders - these are an alternative input method to 2 of the TextFields where the user may want to quickly see the effect of varying 2 angles on the eventual graphic. A "Sliders On/Off" button allows the user to allow slider input.

There's no problem defining just one inner class for all my TextField inputs and applying all the styling, sizing, etc within that, e.g.

// Inner Class To Configure GUI TextField Nodes:
class SMTextField extends TextField
{
    public SMTextField()
    {
        super();
        super.setPrefWidth(150);
        super.setPrefHeight(40);
        super.setBackground(new Background(new BackgroundFill(Color.rgb(255,140,0),
                CornerRadii.EMPTY, Insets.EMPTY)));                         // Orange background                        
        super.setBorder(new Border(new BorderStroke(Color.rgb(255, 255, 0), // Yellow border
                BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(2.0))));
        super.setStyle("-fx-text-fill: blue;"
                     + "-fx-prompt-text-fill: white;"
                     + "-fx-font-family: 'Calibri';"
                     + "-fx-font-size: 11pt;"
                     + "-fx-alignment: center");    
    }
}

But when I try to do something similar for the ComboBox inputs, it's not allowing me to do so the way that I want it. I want to avoid having String as the type of object within my ComboBoxes - I want the security of having a different enum type for each box. If I write this code:

// Inner class for generic ComboBox
class IComboBox extends ComboBox<Object>
{
    .....
    // Sizing & styling code
    .....
}

I get no error. But I see no way to cast a ComboBox of Objects to a ComboBox of enums . . .

The only way I can work this so far is to have 5 separate inner classes - one for each ComboBox, with 95% of the code in each being the exact same. This clearly violates the "write it once" principle of OOP.

Can anyone show a better way of doing a series of enum ComboBoxes with identical styling code without such repetition ?

EDIT

Okay so, for the inner class to define my GUI ComboBox, I'm doing something like:

// Inner class to define combo box layout:
    class SMComboBox<T extends Enum> extends ComboBox<T>
    {
        public SMComboBox()
        {
            super();
            super.getStyleClass().add("sm-combo-box");                      
        }
    }

where the styling, sm-combo-box, resides in a CSS file referenced in the beginning of the start method.

The above inner class shows a warning which I don't understand :-

  • The type parameter, T, is hiding the type, T.
  • Enum is a raw type. References to generic type Enum should be parametrized.

I've thrown all sizing, bordering & coloring into the CSS file, so the inner class merely allows me to avoid referencing sm-combo-box for each instance of ComboBox that I use (5 so far).

All seems to work well, despite the warning.

Trunk
  • 742
  • 9
  • 24
  • 2
    You really don't need to subclass (or extend) controls just to apply style! You can set a style class and do the styling through CSS, or else use a factory method to create controls with the correct styling. Making a new class is complete overkill in this case. – Itai Jul 12 '17 at 13:12
  • As regards styling, CSS can be applied. (The CSS class overhead is less than that of the JavaFX inner class, you suggest ? ) But on the main question of ComboBoxes with enum types . . . ? Just go ahead an define some ComboBox box1 = ComboBox(); and then add CSS style class ? – Trunk Jul 12 '17 at 13:22
  • Yes, SillyFly - the idea of separating style from code is a sound one. Thanks. – Trunk Jul 12 '17 at 13:47

1 Answers1

2

A misconception

ComboBox<Object> is not a good definition for a generic type parameter declaration. A generic type parameter declaration is, by convention, a single letter, but, more importantly, is not the same name as an already defined class (such as Object), as doing that is really confusing.

Sample type definitions

So, rather than:

class IComboBox extends ComboBox<Object>

which says the type of items placed in IComboBox must be Object (and only Object and not a subclass of object), so not particularly useful,

Write the following to say that your ComboBox extension class can have a generic type parameter:

class IComboBox<T> extends ComboBox<T>

Where T is the type parameter name, defined as per standard type parameter naming conventions.

If you want to restrict the type of your custom combo boxes, then you can use an extends clause to place a bound on the type, such as the following which will only permit an Enum type as the concrete type argument.

class EnumComboBox<T extends Enum> extends ComboBox<T> {
}

If you want to do something at runtime based upon the type (such as populate the combo box with the possible enum values), then you need to also pass the type class at runtime (as Java uses type erasure and does not reify types at runtime):

class EnumComboBox<T extends Enum> extends ComboBox<T> {
    EnumComboBox(Class<T> type) {
        super(FXCollections.observableArrayList(type.getEnumConstants()));

        setStyle("-fx-text-base-color: forestgreen;");
    }
}

Sample Code

sample output

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class EnumCombos extends Application {
    class EnumComboBox<T extends Enum> extends ComboBox<T> {
        EnumComboBox(Class<T> type) {
            super(FXCollections.observableArrayList(type.getEnumConstants()));

            setStyle("-fx-text-base-color: forestgreen;");
        }
    }

    @Override
    public void start(Stage stage) throws Exception {
        EnumComboBox<Heavenly> heavenlyCombo = new EnumComboBox<>(Heavenly.class);
        EnumComboBox<Earthly>  earthlyCombo  = new EnumComboBox<>(Earthly.class);

        VBox layout = new VBox(10, heavenlyCombo, earthlyCombo);
        layout.setPadding(new Insets(10));

        stage.setScene(new Scene(layout));
        stage.show();
    }

    public static void main(String[] args) {
        launch(EnumCombos.class);
    }

    enum Heavenly {
        SUN,
        MOON,
        STARS
    }

    enum Earthly {
        EARTH,
        WIND,
        FIRE
    }
}

Note on defining combo box styles in code

Trying to set styles in code for combo boxes is less flexible then trying to do it via styles in stylesheets. The setStyle method cannot use css type selectors to define styles, and selectors are required to set the styles of combo box pop-up window (as far as I know). So use of stylesheets is generally preferred.

If you wish, you can use stylesheets in combination with a subclassing mechanism like defined in this answer (replace the setStyle call with a getStyleClass().add() invocation to do that), so the use of stylesheets and subclassing is not mutually exclusive (indeed, that is how most of the in-built JavaFX controls are defined).

Background on terminology

From the Java generics tutorial:

Type Parameter and Type Argument Terminology: Many developers use the terms "type parameter" and "type argument" interchangeably, but these terms are not the same. When coding, one provides type arguments in order to create a parameterized type. Therefore, the T in Foo<T> is a type parameter and the String in Foo<String> f is a type argument. This lesson observes this definition when using these terms.

jewelsea
  • 150,031
  • 14
  • 366
  • 406