我需要展示一个"连续" ContextMenu中用于颜色选择的调色板。与在ColorPicker上弹出的CustomColorDialog类似。
是否有不同的类用于此目的,或者是否可以通过扩展ColorPicker并直接显示CustomColorDialog而不是首先显示ColorPicker来解决此问题。
TIA
答案 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
允许在不关闭上下文菜单的情况下单击颜色选择器。要关闭它,只需单击弹出窗口外的任何位置。
这就是它的样子:
基于此自定义对话框,您可以改进它并添加您可能需要的功能。
答案 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>