Display native color dialog directly - JavaFX ColorPicker


I need to show a "continuous" color palette to pick a color inside a ContextMenu. Similar to the CustomColorDialog that appears on the ColorPicker .
There is another class for this purpose, or you can get by by extending the ColorPicker and showing the CustomColorDialog directly instead of showing the ColorPicker first.

ColorPicker JavaFX 8

TIA

+3


source to share


2 answers


This com.sun.javafx.scene.control.skin.CustomColorDialog

is a private API to start with and is impractical to use as this may change in the future without notice.

Also, it is Dialog

, which means you cannot nest it in ContextMenu

, it has its own window and it is modal.

This is a quick example of using this (very large, not customizable) dialog box in your application without using ColorPicker

.

@Override
public void start(Stage primaryStage) {
    Button btn = new Button();
    btn.setText("Open Custom Color Dialog");
    btn.setOnAction(e -> {
        CustomColorDialog dialog = new CustomColorDialog(primaryStage.getOwner());
        dialog.show();
    });

    Scene scene = new Scene(new StackPane(btn), 300, 250);

    primaryStage.setTitle("CustomColorDialog");
    primaryStage.setScene(scene);
    primaryStage.show();
}

      

You will get a dialog, but you will not get any way to send your own color or get the selected color, since properties such as customColorProperty()

are only available in the package com.sun.javafx.scene.control.skin

.

So, we need another way to implement our custom color selector. If you look at the source code CustomColorDialog

, you can see that it is a relatively simple control and, most importantly, almost based on the public API: panels, areas and colors.

Trying to put everything in ContextMenu

can be overkill, so I gave this basic example where I just use the left side of the dialog, displaying the center pane on top. Most of the code belongs to the class. The CSS styling was also taken from modena.css

(in the custom-color-dialog

CSS Selector section ), but was tweaked since some of the nodes were rotated 90ΒΊ.

This is the short version of the class CustomColorDialog

:

public class MyCustomColorPicker extends VBox {

    private final ObjectProperty<Color> currentColorProperty = 
        new SimpleObjectProperty<>(Color.WHITE);
    private final ObjectProperty<Color> customColorProperty = 
        new SimpleObjectProperty<>(Color.TRANSPARENT);

    private Pane colorRect;
    private final Pane colorBar;
    private final Pane colorRectOverlayOne;
    private final Pane colorRectOverlayTwo;
    private Region colorRectIndicator;
    private final Region colorBarIndicator;
    private Pane newColorRect;

    private DoubleProperty hue = new SimpleDoubleProperty(-1);
    private DoubleProperty sat = new SimpleDoubleProperty(-1);
    private DoubleProperty bright = new SimpleDoubleProperty(-1);

    private DoubleProperty alpha = new SimpleDoubleProperty(100) {
        @Override protected void invalidated() {
            setCustomColor(new Color(getCustomColor().getRed(), getCustomColor().getGreen(), 
                    getCustomColor().getBlue(), clamp(alpha.get() / 100)));
        }
    };

    public MyCustomColorPicker() {

        getStyleClass().add("my-custom-color");

        VBox box = new VBox();

        box.getStyleClass().add("color-rect-pane");
        customColorProperty().addListener((ov, t, t1) -> colorChanged());

        colorRectIndicator = new Region();
        colorRectIndicator.setId("color-rect-indicator");
        colorRectIndicator.setManaged(false);
        colorRectIndicator.setMouseTransparent(true);
        colorRectIndicator.setCache(true);

        final Pane colorRectOpacityContainer = new StackPane();

        colorRect = new StackPane();
        colorRect.getStyleClass().addAll("color-rect", "transparent-pattern");

        Pane colorRectHue = new Pane();
        colorRectHue.backgroundProperty().bind(new ObjectBinding<Background>() {

            {
                bind(hue);
            }

            @Override protected Background computeValue() {
                return new Background(new BackgroundFill(
                        Color.hsb(hue.getValue(), 1.0, 1.0), 
                        CornerRadii.EMPTY, Insets.EMPTY));

            }
        });            

        colorRectOverlayOne = new Pane();
        colorRectOverlayOne.getStyleClass().add("color-rect");
        colorRectOverlayOne.setBackground(new Background(new BackgroundFill(
                new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE, 
                new Stop(0, Color.rgb(255, 255, 255, 1)), 
                new Stop(1, Color.rgb(255, 255, 255, 0))), 
                CornerRadii.EMPTY, Insets.EMPTY)));

        EventHandler<MouseEvent> rectMouseHandler = event -> {
            final double x = event.getX();
            final double y = event.getY();
            sat.set(clamp(x / colorRect.getWidth()) * 100);
            bright.set(100 - (clamp(y / colorRect.getHeight()) * 100));
            updateHSBColor();
        };

        colorRectOverlayTwo = new Pane();
        colorRectOverlayTwo.getStyleClass().addAll("color-rect");
        colorRectOverlayTwo.setBackground(new Background(new BackgroundFill(
                new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE, 
                new Stop(0, Color.rgb(0, 0, 0, 0)), new Stop(1, Color.rgb(0, 0, 0, 1))), 
                CornerRadii.EMPTY, Insets.EMPTY)));
        colorRectOverlayTwo.setOnMouseDragged(rectMouseHandler);
        colorRectOverlayTwo.setOnMousePressed(rectMouseHandler);

        Pane colorRectBlackBorder = new Pane();
        colorRectBlackBorder.setMouseTransparent(true);
        colorRectBlackBorder.getStyleClass().addAll("color-rect", "color-rect-border");

        colorBar = new Pane();
        colorBar.getStyleClass().add("color-bar");
        colorBar.setBackground(new Background(new BackgroundFill(createHueGradient(), 
                CornerRadii.EMPTY, Insets.EMPTY)));

        colorBarIndicator = new Region();
        colorBarIndicator.setId("color-bar-indicator");
        colorBarIndicator.setMouseTransparent(true);
        colorBarIndicator.setCache(true);

        colorRectIndicator.layoutXProperty().bind(
            sat.divide(100).multiply(colorRect.widthProperty()));
        colorRectIndicator.layoutYProperty().bind(
            Bindings.subtract(1, bright.divide(100)).multiply(colorRect.heightProperty()));
        colorBarIndicator.layoutXProperty().bind(
            hue.divide(360).multiply(colorBar.widthProperty()));
        colorRectOpacityContainer.opacityProperty().bind(alpha.divide(100));

        EventHandler<MouseEvent> barMouseHandler = event -> {
            final double x = event.getX();
            hue.set(clamp(x / colorRect.getWidth()) * 360);
            updateHSBColor();
        };

        colorBar.setOnMouseDragged(barMouseHandler);
        colorBar.setOnMousePressed(barMouseHandler);

        newColorRect = new Pane();
        newColorRect.getStyleClass().add("color-new-rect");
        newColorRect.setId("new-color");
        newColorRect.backgroundProperty().bind(new ObjectBinding<Background>() {
            {
                bind(customColorProperty);
            }
            @Override protected Background computeValue() {
                return new Background(new BackgroundFill(customColorProperty.get(), CornerRadii.EMPTY, Insets.EMPTY));
            }
        });

        colorBar.getChildren().setAll(colorBarIndicator);
        colorRectOpacityContainer.getChildren().setAll(colorRectHue, colorRectOverlayOne, colorRectOverlayTwo);
        colorRect.getChildren().setAll(colorRectOpacityContainer, colorRectBlackBorder, colorRectIndicator);
        VBox.setVgrow(colorRect, Priority.SOMETIMES);
        box.getChildren().addAll(colorBar, colorRect, newColorRect);

        getChildren().add(box);

        if (currentColorProperty.get() == null) {
            currentColorProperty.set(Color.TRANSPARENT);
        }
        updateValues();

    }

    private void updateValues() {
        hue.set(getCurrentColor().getHue());
        sat.set(getCurrentColor().getSaturation()*100);
        bright.set(getCurrentColor().getBrightness()*100);
        alpha.set(getCurrentColor().getOpacity()*100);
        setCustomColor(Color.hsb(hue.get(), clamp(sat.get() / 100), 
                clamp(bright.get() / 100), clamp(alpha.get()/100)));
    }

    private void colorChanged() {
        hue.set(getCustomColor().getHue());
        sat.set(getCustomColor().getSaturation() * 100);
        bright.set(getCustomColor().getBrightness() * 100);
    }

    private void updateHSBColor() {
        Color newColor = Color.hsb(hue.get(), clamp(sat.get() / 100), 
                        clamp(bright.get() / 100), clamp(alpha.get() / 100));
        setCustomColor(newColor);
    }

    @Override 
    protected void layoutChildren() {
        super.layoutChildren();            
        colorRectIndicator.autosize();
    }

    static double clamp(double value) {
        return value < 0 ? 0 : value > 1 ? 1 : value;
    }

    private static LinearGradient createHueGradient() {
        double offset;
        Stop[] stops = new Stop[255];
        for (int x = 0; x < 255; x++) {
            offset = (double)((1.0 / 255) * x);
            int h = (int)((x / 255.0) * 360);
            stops[x] = new Stop(offset, Color.hsb(h, 1.0, 1.0));
        }
        return new LinearGradient(0f, 0f, 1f, 0f, true, CycleMethod.NO_CYCLE, stops);
    }

    public void setCurrentColor(Color currentColor) {
        this.currentColorProperty.set(currentColor);
        updateValues();
    }

    Color getCurrentColor() {
        return currentColorProperty.get();
    }

    final ObjectProperty<Color> customColorProperty() {
        return customColorProperty;
    }

    void setCustomColor(Color color) {
        customColorProperty.set(color);
    }

    Color getCustomColor() {
        return customColorProperty.get();
    }
}

      



This is the file color.css

:

.context-menu{
    -fx-background-color: derive(#ececec,26.4%);
}
.menu-item:focused {
    -fx-background-color: transparent;
}

/* CUSTOM COLOR */

.my-custom-color {
    -fx-background-color: derive(#ececec,26.4%);
    -fx-padding: 1.25em;
    -fx-spacing: 1.25em;
    -fx-min-width: 20em;
    -fx-pref-width: 20em;
    -fx-max-width: 20em;
}

.my-custom-color:focused,
.my-custom-color:selected {
    -fx-background-color: transparent;
}

.my-custom-color > .color-rect-pane {
    -fx-spacing: 0.75em;
    -fx-pref-height: 16.666667em;
    -fx-alignment: top-left;
    -fx-fill-height: true;
}

.my-custom-color .color-rect-pane .color-rect {
    -fx-min-width: 16.666667em;
    -fx-min-height: 16.666667em;
}

.my-custom-color .color-rect-pane .color-rect-border {
    -fx-border-color: derive(#ececec, -20%);
}

.my-custom-color > .color-rect-pane #color-rect-indicator {
    -fx-background-color: null;
    -fx-border-color: white;
    -fx-border-radius: 0.4166667em;
    -fx-translate-x: -0.4166667em;
    -fx-translate-y: -0.4166667em;
    -fx-pref-width: 0.833333em;
    -fx-pref-height: 0.833333em;
    -fx-effect: dropshadow(three-pass-box, black, 2, 0.0, 0, 1);
}

.my-custom-color > .color-rect-pane > .color-bar {
    -fx-min-height: 1.666667em;
    -fx-min-width: 16.666667em;
    -fx-max-height: 1.666667em;
    -fx-border-color: derive(#ececec, -20%);
}

.my-custom-color > .color-rect-pane > .color-bar > #color-bar-indicator {
    -fx-border-radius: 0.333333em;
    -fx-border-color: white;
    -fx-effect: dropshadow(three-pass-box, black, 2, 0.0, 0, 1);
    -fx-pref-height: 2em;
    -fx-pref-width: 0.833333em;
    -fx-translate-y: -0.1666667em;
    -fx-translate-x: -0.4166667em;
}

.my-custom-color .transparent-pattern {
    -fx-background-image: url("pattern-transparent.png"); 
    -fx-background-repeat: repeat;
    -fx-background-size: auto;
}

.my-custom-color .color-new-rect {
    -fx-min-width: 10.666667em;
    -fx-min-height: 1.75em;
    -fx-pref-width: 10.666667em;
    -fx-pref-height: 1.75em;
    -fx-border-color: derive(#ececec, -20%);
}

      

The image can be found here .

And finally, our application class.

public class CustomColorContextMenu extends Application {

    private final ObjectProperty<Color> sceneColorProperty = 
        new SimpleObjectProperty<>(Color.WHITE);

    @Override
    public void start(Stage primaryStage) {

        Rectangle rect = new Rectangle(400,400);
        rect.fillProperty().bind(sceneColorProperty);

        Scene scene = new Scene(new StackPane(rect), 400, 400);
        scene.getStylesheets().add(getClass().getResource("color.css").toExternalForm());
        scene.setOnMouseClicked(e->{
            if(e.getButton().equals(MouseButton.SECONDARY)){
                MyCustomColorPicker myCustomColorPicker = new MyCustomColorPicker();
                myCustomColorPicker.setCurrentColor(sceneColorProperty.get());

                CustomMenuItem itemColor = new CustomMenuItem(myCustomColorPicker);
                itemColor.setHideOnClick(false);
                sceneColorProperty.bind(myCustomColorPicker.customColorProperty());
                ContextMenu contextMenu = new ContextMenu(itemColor);
                contextMenu.setOnHiding(t->sceneColorProperty.unbind());
                contextMenu.show(scene.getWindow(),e.getScreenX(),e.getScreenY());
            }
        });

        primaryStage.setTitle("Custom Color Selector");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

}

      

Note the use CustomMenuItem

to allow clicking on the color selectors without closing the context menu. To close it, simply click anywhere outside the popup.

This is how it looks:

Custom Color Selector

Based on this custom dialog, you can improve it and add functionality you may need.

+10


source


This is how I am using com.sun.javafx.scene.control.skin.CustomColorDialog:



public Color showColorDialog(String title, Color initialColor) {

CountDownLatch countDownLatch = new CountDownLatch(1);

ObjectHolder<Color> selectedColorHolder = new ObjectHolder<>();

Platform.runLater(new Runnable() {
    @Override
    public void run() {
    try {
        final CustomColorDialog customColorDialog = new CustomColorDialog(getWindow());
        customColorDialog.setCurrentColor(initialColor);

        // remove save button
        VBox controllBox = (VBox) customColorDialog.getChildren().get(1);
        HBox buttonBox = (HBox) controllBox.getChildren().get(2);
        buttonBox.getChildren().remove(0);

        Runnable saveUseRunnable = new Runnable() {
        @Override
        public void run() {
            try {
            Field customColorPropertyField = CustomColorDialog.class
                .getDeclaredField("customColorProperty"); //$NON-NLS-1$
            customColorPropertyField.setAccessible(true);
            @SuppressWarnings("unchecked")
            ObjectProperty<Color> customColorPropertyValue = (ObjectProperty<Color>) customColorPropertyField
                .get(customColorDialog);
            selectedColorHolder.setObject(customColorPropertyValue.getValue());
            } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
            LOG.error(e, e);
            }
        }
        };

        customColorDialog.setOnUse(saveUseRunnable);

        customColorDialog.setOnHidden(new EventHandler<WindowEvent>() {
        @Override
        public void handle(WindowEvent event) {
            countDownLatch.countDown();
        }
        });

        Field dialogField = CustomColorDialog.class.getDeclaredField("dialog"); //$NON-NLS-1$
        dialogField.setAccessible(true);
        Stage dialog = (Stage) dialogField.get(customColorDialog);

        dialog.setTitle(title);
        customColorDialog.show();
        dialog.centerOnScreen();
    } catch (Exception e) {
        LOG.error(e, e);
        countDownLatch.countDown();
    }
    }
});

try {
    countDownLatch.await();
} catch (InterruptedException e) {
    LOG.error(e, e);
}

return selectedColorHolder.getObject();
}

      

0


source







All Articles