直接显示自定义颜色对话框 - JavaFX ColorPicker

时间:2014-11-27 13:29:51

标签: javafx-8 color-picker color-palette


我需要展示一个"连续" ContextMenu中用于颜色选择的调色板。与在ColorPicker上弹出的CustomColorDialog类似。
是否有不同的类用于此目的,或者是否可以通过扩展ColorPicker并直接显示CustomColorDialog而不是首先显示ColorPicker来解决此问题。

ColorPicker JavaFX 8

TIA

3 个答案:

答案 0 :(得分:11)

首先,com.sun.javafx.scene.control.skin.CustomColorDialog是私有API,不建议使用它,因为它可能在将来发生变化,恕不另行通知。

此外,它是Dialog,这意味着您无法将其嵌入ContextMenu,它有自己的窗口,而且是模态。

这是在应用程序中使用此(非常大,不可自定义)对话框的简短示例,而不使用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();
}

您将获得对话框,但您无法发送自定义颜色或检索所选颜色,因为customColorProperty()等属性只能在com.sun.javafx.scene.control.skin包中访问。 / p>

所以我们需要另一种方法来实现我们的自定义颜色选择器。如果您查看CustomColorDialog的源代码,您会发现它是一个相对简单的控件,而且最重要的是,几乎基于公共API:窗格,区域和颜色。

尝试将所有内容放入ContextMenu可能会过度使用,所以我想出了这个基本示例,我将只使用对话框的左侧部分,在顶部显示中央栏。大多数代码都来自课堂。 CSS样式也取自modena.css(在custom-color-dialog CSS选择器下),但是在一些节点旋转90º时进行了自定义。

这是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();
    }
}

这是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%);
}

可以找到here的图片。

最后,我们的应用程序类。

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

}

请注意使用CustomMenuItem允许在不关闭上下文菜单的情况下单击颜色选择器。要关闭它,只需单击弹出窗口外的任何位置。

这就是它的样子:

Custom Color Selector

基于此自定义对话框,您可以改进它并添加您可能需要的功能。

答案 1 :(得分:0)

以下是我如何使用 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();
}

答案 2 :(得分:0)

@Bosko Popovic的showColorDialog()方法对我不起作用。当我从JavaFX线程调用它时(例如,在响应按钮单击时),它将阻止并冻结该应用程序。我仍然认为他的方法有优点,因此这里有一个稍微修改的版本:

public static Optional<Color> showColorDialog(Window owner, String title, Optional<Color> initialColor) {
    AtomicReference<Color> selectedColor = new AtomicReference<>();

    // Create custom-color-dialog.
    CustomColorDialog customColorDialog = new CustomColorDialog(owner);
    Stage dialog = customColorDialog.getDialog();

    // Initialize current-color-property with supplied initial color.
    initialColor.ifPresent(customColorDialog::setCurrentColor);

    // Hide the Use-button.
    customColorDialog.setShowUseBtn(false);

    // Change the Save-button text to 'OK'.
    customColorDialog.setSaveBtnToOk();

    // When clicking save, we store the selected color.
    customColorDialog.setOnSave(() -> selectedColor.set(customColorDialog.getCustomColor()));

    // Exit the nested-event-loop when the dialog is hidden.
    customColorDialog.setOnHidden(event -> {
        Toolkit.getToolkit().exitNestedEventLoop(dialog, null);
    });

    // Show the dialog.
    dialog.setTitle(title);

    // Call the custom-color-dialog's show() method so that the color-pane
    // is initialized with the correct color.
    customColorDialog.show();

    // Need to request focus as dialog can be stuck behind popup-menus.
    dialog.requestFocus();

    // Center the dialog or else it will show up to the right-hand side
    // of the screen.
    dialog.centerOnScreen();

    // Enter nested-event-loop to simulate a showAndWait(). This will
    // basically cause the dialog to block input from the rest of the
    // window until the dialog is closed.
    Toolkit.getToolkit().enterNestedEventLoop(dialog);

    return Optional.ofNullable(selectedColor.get());
}

dialog字段不再需要通过反射来检索。您可以通过调用customColorDialog.getDialog()直接获得它。您也不需要通过反射从customColorProperty字段中获取颜色,因为您可以通过调用customColorDialog.getCustomColor()直接获取颜色。需要使用嵌套事件循环来模拟showAndWait()调用,以防止在显示对话框时输入到后台Window

您可以将此方法存储在实用程序类中,当@JoséPereda提到不赞成(或更改)API的日子到来时,您可以使用他的示例代码来实现自定义颜色对话框。 / p>