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