Showing posts with label JavaFX. Show all posts
Showing posts with label JavaFX. Show all posts

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()
        ));