Thursday, July 31, 2014

JavaFX StyleablePropertyFactory

javafx.css.StyleablePropertyFactory was just added to the code base for JavaFX 8u40, which you can download from the OpenJFX project. This factory class makes it easier to use CSS in your controls by dramatically reducing the StyleableProperty and CssMetaData boilerplate you have to write to make your JavaFX properties styleable. For more details on how to use CSS in your code, see the JavaFX javadoc for the javafx.css package and classes.

For background, consider an implementation of StyleableProperty and CssMetaData as it is now.

public class MyButton extends Button {

   public BooleanProperty fooProperty() { 
       return (BooleanProperty)foo; 
   }
   private StyleableProperty<Boolean> foo = 
       new SimpleStyleableBooleanProperty(fooMetaData);

   private static final CssMetaData<MyButton,Boolean> fooMetaData = 
        new CssMetaData<>("-my-foo", StyleConverter.getBooleanConverter()) {
           @Override 
           public boolean isSettable(MyButton node) { 
               return !node.fooProperty().isBound(); 
           }
           @Override 
           public StyleableProperty<Boolean> getStyleableProperty(MyButton node) {
               return node.foo; 
           }
        };

    static final List<CssMetaData<? extends Styleable, ?>> cssMetaData;
    static {
        List<CssMetaData<? extends Styleable, ?>> temp = 
            new ArrayList<>(Control.getClassCssMetaData());
        temp.addAll(Arrays.asList(fooMetaData));
        cssMetaData = Collections.unmodifiableList(temp);
    }

    @Override
    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
        return cssMetaData;    
    }          
}

From the high-level view, there are two pieces here. First is the StyleableProperty which is used by CSS to set the calculated value of a style on a property. The second piece is the CssMetaData which is used by CSS to (primarily) discover what css properties to look up for a node (e.g., -fx-fill),  and to get the StyleableProperty for setting the value (e.g., get the StyleableProperty that corresponds to -fx-fill).

With StyleablePropertyFactory, the same can be accomplished in fewer lines of code.

public class MyButton extends Button {

   private static final StyleablePropertyFactory<MyButton> FACTORY = 
       new StyleablePropertyFactory<>(Button.getClassCssMetaData());

   public BooleanProperty fooProperty() { 
       return (BooleanProperty)foo; 
   }
   private StyleableProperty<Boolean> foo = 
       new Factory.createStyleableBooleanProperty(this, "foo", "-my-foo");

    @Override
    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
        return cssMetaData;    
    }          
}

Sunday, June 8, 2014

JavaFX TableView with image background in header

I got to wondering how to set the background of a table to an image of some sort. It is pretty straight-forward with CSS. The only trick, really, is setting the background-color of the filler and column-header so the image can be seen. I used a semi-transparent white so the label text is easier to see.

.table-view > .column-header-background {
    -fx-background-image: url("https://duke.kenai.com/models/Duke3DprogressionSmall.jpg");
    -fx-background-size: stretch;
    -my-wash: rgba(255,255,255,.75);
}

.table-view > .column-header-background > .filler {
    -fx-background-color: -my-wash;
}

.table-view > .column-header-background > .nested-column-header > .column-header.table-column {
    -fx-background-color: -my-wash;

}

Sample code to try it on.

    static class Person {
        final StringProperty lastName  = new SimpleStringProperty();
        final StringProperty firstName  = new SimpleStringProperty();
        Person(String lastName, String firstName) {
            this.lastName.set(lastName);
            this.firstName.set(firstName);
        }
        StringProperty lastNameProperty() { return lastName; }
        StringProperty firstNameProperty() { return firstName; }
    }

    @Override
    public void start(Stage stage) throws Exception {

        TableColumn lastName = new TableColumn<>("Last");
        lastName.setCellValueFactory((value) -> value.getValue().lastNameProperty());

        TableColumn firstName = new TableColumn<>("First");
        firstName.setCellValueFactory((value) -> value.getValue().firstNameProperty());

        TableView tableView = new TableView();
        tableView.getColumns().setAll(lastName, firstName);
        tableView.getItems().setAll(
                new Person("Cratchit", "Bob"),
                new Person("Scrooge", "Ebenezer")
        );

        Group root = new Group(tableView);

        Scene scene = new Scene(root, 300, 500);
        scene.getStylesheets().add("/fxtest/test.css");
        stage.setScene(scene);
        stage.show();

    }

Saturday, June 7, 2014

JavaFX CSS selectors for TableView

I had time to kill while on a flight from Boston to Houston and came up with a list of CSS selectors for simple, JavaFX TableView. The comment in each CSS rule states the public API of the selected node.

.table-view > .column-header-background {
    /* StackPane */
}

.table-view > .column-header-background > .filler {
    /* Region - area of header not taken by column headers */
}

.table-view > .column-header-background > .column-drag-header {
    /* StackPane */
}

.table-view > .column-header-background > .column-drag-header > .label {
    /* Label */
}

.table-view > .column-header-background > .column-drag-header > .label > .text {
    /* Text */
}

.table-view > .column-header-background > .nested-column-header {
    /* Region */
}

.table-view > .column-header-background > .nested-column-header > .column-header.table-column {
    /* Region */
}

.table-view > .column-header-background > .nested-column-header > .column-header.table-column > .label {
    /* Label */
}

.table-view > .column-header-background > .nested-column-header > .column-header.table-column > .label > .text {
    /* Text */
}

.table-view > .column-header-background > .nested-column-header > .column-header.table-column > Grid {
    /* Grid */
}

.table-view > .column-header-background > .nested-column-header > .column-header.table-column > Grid > .arrow {
    /* Region */
}

.table-view > .column-header-background > .nested-column-header > Rectangle {
    /* Rectangle - separates column headers. Resize icon on :hover */
}

.table-view > .column-header-background > .show-hide-columns-button {
    /* StackPane */
}

.table-view > .column-header-background > .show-hide-columns-button > .show-hide-column-image {
    /* StackPane */
}

.table-view > .column-overlay {
    /* Region - highlights column being dragged */
}

.table-view > .column-resize-line {
    /* Region - displays where the dragged column will drop  */
}

.table-view > .virtual-flow {
    /* Region */
}

.table-view > .virtual-flow > .clipped-container {
    /* Region */
}

.table-view > .virtual-flow > .clipped-container > .sheet {
    /* Group */
}

.table-view > .virtual-flow > .clipped-container > .sheet > .cell.indexed-cell.table-row-cell {
    /* TableRow */
}
.table-view > .virtual-flow > .clipped-container > .sheet > .cell.indexed-cell.table-row-cell > .cell.indexed-cell.table-cell.table-column {
    /* TableCell */
}

.table-view > .virtual-flow > .clipped-container > .sheet > .cell.indexed-cell.table-row-cell > .cell.indexed-cell.table-cell.table-column > .text {
    /* Text */
}

.table-view > .virtual-flow > .scroll-bar {
    /* ScrollBar - has :vertical and :horizontal (default) pseudo-class state */
}

.table-view > .virtual-flow > .scroll-bar > .track-background {
    /* StackPane */
}

.table-view > .virtual-flow > .scroll-bar > .track {
    /* StackPane */
}

.table-view > .virtual-flow > .scroll-bar > .thumb {
    /* StackPane */
}

.table-view > .virtual-flow > .scroll-bar > .decrement-button {
    /* Region */
}

.table-view > .virtual-flow > .scroll-bar > .decrement-arrow {
    /* Region */
}

.table-view > .virtual-flow > .scroll-bar > .increment-button {
    /* Region */
}

.table-view > .virtual-flow > .scroll-bar > .increment-arrow {
    /* Region */
}

Wednesday, May 28, 2014

Styling Cells in JavaFX

When working with Cells, in a ListView or TableView for example, it is best to use CSS to style the look of the cell. While it is possible to set the value of a property directly, the way cells are reused means that you have to be careful to undo or redo the value when the cell is reused.

Here is an example of a ListView that sets the text fill to red for prime numbers. Notice the use of the pseudo-class state. Adding and removing a style-class is also an option. But a psuedo-class state change has much less overhead.


package fxtest;

import javafx.application.Application;
import javafx.css.PseudoClass;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.stage.Stage;

import java.util.Arrays;
import java.util.function.IntFunction;
import java.util.stream.IntStream;

public class Main extends Application {

    public static void main(String[] args) {
        launch(args);
    }
 
    final static int MAX_N = 100;
    final static boolean isPrime[] = new boolean[MAX_N];

    static {
        Arrays.fill(isPrime, true);
        for (int i = 2; i*i < MAX_N; i++) {
            if (isPrime[i]) {
                for (int j = i*i; j < MAX_N; j += i) {
                    isPrime[j] = false;
                }
            }
        }
    }

    final static PseudoClass prime = PseudoClass.getPseudoClass("prime");

    final Integer[] items = IntStream.range(0, MAX_N).boxed().toArray(n -> { return new Integer[n]; });

    @Override
    public void start(final Stage stage) {

        ListView<Integer> listView = new ListView<>();
        listView.getItems().addAll(items);

        listView.setCellFactory(lv -> {
            return new ListCell<Integer>() {

                @Override
                public void updateItem(Integer item, boolean empty) {
                    super.updateItem(item, empty);
                    if (empty || item == null) {
                        setText(null);
                        pseudoClassStateChanged(prime, false);
                    } else {
                        setText(item.toString());
                        int n = item.intValue();
                        boolean bool = ((1 < n) && (n < isPrime.length) && isPrime[n]);
                        pseudoClassStateChanged(prime, bool);
                    }
                }
            };
        });

        Scene scene = new Scene(new Group(listView));
        scene.getStylesheets().add("/fxtest/test.css");
        stage.setWidth(400);
        stage.setHeight(400);
        stage.setScene(scene);
        stage.show();

    }

}

The file fxtest/test.css:

.list-cell:prime {
    -fx-text-fill: red;
}

Friday, May 16, 2014

Styling the progress color a ProgressIndicator along a gradient

I thought it would be fun to have ProgressIndicator that went from red, to yellow, to green as the progress value went from zero to one. So I cooked this up. The color transitions nicely along a gradient from red at 0%, yellow at 50%, and green at 100%. Note the use of Bindings.createStringBinding, too.  Oh, and a lambda thrown in for good measure!

        // styled ProgressIndicator
        final ProgressIndicator progressIndicator = new ProgressIndicator();
        progressIndicator.setPrefSize(100, 100);
        progressIndicator.styleProperty().bind(Bindings.createStringBinding(
                () -> {
                    final double percent = progressIndicator.getProgress();
                    if (percent < 0) return null; // progress bar went indeterminate
                    //
                    // poor man's gradient for stops: red, yellow 50%, green
                    // Based on http://en.wikibooks.org/wiki/Color_Theory/Color_gradient#Linear_RGB_gradient_with_6_segments
                    //
                    final double m = (2d * percent);
                    final int n = (int) m;
                    final double f = m - n;
                    final int t = (int) (255 * f);
                    int r = 0, g = 0, b = 0;
                    switch (n) {
                        case 0:
                            r = 255;
                            g = t;
                            b = 0;
                            break;
                        case 1:
                            r = 255 - t;
                            g = 255;
                            b = 0;
                            break;
                        case 2:
                            r = 0;
                            g = 255;
                            b = 0;
                            break;

                    }
                    final String style = String.format("-fx-progress-color: rgb(%d,%d,%d)", r, g, b);
                    return style;
                },
                progressIndicator.progressProperty()
        ));

Thursday, March 6, 2014

lambda to the rescue

I'm a big fan of lambda expressions in Java 8, but it was only recently that I was able to push some lambda code to OpenJFX.  The thing of it is, lambda expressions helped me in two ways. First, it got me looking at the problem I was trying to solve from a different angle. Second, it helped me eliminate a lot of duplicate code.

The problem I was trying to solve was one where I had two identical code paths. The only difference between the two was the data type of one of the parameters. Basically, I had

    private List<StylesheetContainer> gatherParentStylesheets(Parent parent)
and 
    private List<StylesheetContainer> gatherSceneStylesheets(Scene parent) 

The main difference between these two methods was a call to add the Parent to one list, and a call to add the Scene to a different list (the idea is that there is one list of Parents that refer to the stylesheet and another list of Scenes that refer to the stylesheet). I tried different ways of refactoring these methods into one and kept coming back to the same thought; that if I could pass both the parent or scene and the list as parameters, I'd be set.

I started to think about whether or not lambdas could help me. What if I passed a function that did that for me? What I really wanted to do was say that this Scene or Parent refers to the StylesheetContainer and I came up with this interface. 

    @FunctionalInterface
    static interface Referent {void addReferenceTo(StylesheetContainer c) } 

Now I could pass the "addReferenceTo" function to a method and could refactor the code into a single method.

    private List<StylesheetContainer> processStylesheets(List<String> stylesheets, StylesheetContainer.Referent referent)

In this method, I just do:

    referent.addReferenceTo(container);
 
And call it like so:

    final List<StylesheetContainer> list = processStylesheets(sceneStylesheets, (c) -> c.sceneUsers.add(scene));
or
    final List<StylesheetContainer> list = processStylesheets(parentStylesheets, (c) -> c.parentUsers.add(parent));

In retrospect, I could have done the same thing with an Interface and an anonymous inner class. Or I could have just used 'instance of'.  But I think the lambda solution is rather elegant.

You can see the change here, in the diffs to StyleManager.