How to change or override the tooltip in JavaFX ColorPicker - javafx-8

I am using JavaFX ColorPicker in my application. As per my requirements, I have mapped the default colors on the color picker to a number. I want this number to be displayed as tooltip on hover over the color instead of hex value of the color. How can I achieve this?
//Part of Code
public void handleNodes(Circle circularNode) {
final Delta offset = new Delta();
circularNode.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
((Circle)(event.getSource())).setCursor(Cursor.HAND);
}
});
circularNode.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if(event.getButton().equals(MouseButton.SECONDARY)) {
System.out.println("Right click");
Circle parent = ((Circle)(event.getSource()));
final ContextMenu contextMenu = new ContextMenu();
MenuItem editLabel = new MenuItem("Edit Label");
editLabel.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Edit Label");
final ColorPicker colorPicker = new ColorPicker();
colorPicker.setStyle("-fx-border-radius: 10 10 10 10;"
+ "-fx-background-radius: 10 10 10 10;");
colorPicker.setValue((Color) parent.getFill());
colorPicker.showingProperty().addListener((obs,b,b1)->{
if(b1){
PopupWindow popupWindow = getPopupWindow();
javafx.scene.Node popup = popupWindow.getScene().getRoot().getChildrenUnmodifiable().get(0);
popup.lookupAll(".color-rect").stream()
.forEach(rect->{
Color c = (Color)((Rectangle)rect).getFill();
Tooltip.install(rect.getParent(), new Tooltip("Custom tip for "+c.toString()));
});
}
});
panelMain.getChildren().add(colorPicker);
}
});

This is really a hacky answer.
The first problem: you have to find the popup node on the scene once it shows up. But you won't... since its not in the same window!
Having a deep look at how ScenicView does it, the trick is getting the list of windows at that moment, but using a deprectated method:
private PopupWindow getPopupWindow() {
#SuppressWarnings("deprecation") final Iterator<Window> windows = Window.impl_getWindows();
while (windows.hasNext()) {
final Window window = windows.next();
if (window instanceof PopupWindow) {
return (PopupWindow)window;
}
}
return null;
}
Once you have the popup window, we can now check for all the Rectangle nodes using lookupAll and the CSS selector color-rect, to get their color, and install the tooltip over its parent container:
#Override
public void start(Stage primaryStage) {
ColorPicker picker = new ColorPicker();
StackPane root = new StackPane(picker);
Scene scene = new Scene(root, 500, 400);
primaryStage.setScene(scene);
primaryStage.show();
picker.showingProperty().addListener((obs,b,b1)->{
if(b1){
PopupWindow popupWindow = getPopupWindow();
Node popup = popupWindow.getScene().getRoot().getChildrenUnmodifiable().get(0);
popup.lookupAll(".color-rect").stream()
.forEach(rect->{
Color c = (Color)((Rectangle)rect).getFill();
Tooltip.install(rect.getParent(), new Tooltip("Custom tip for "+c.toString()));
});
}
});
}
This is what it looks like:

Based on the code posted by the OP after my first answer, and due to the substancial changes in the problem addressed, I'm adding a new answer that covers both situations:
The ColorPicker is embedded in the main scene, as a regular node
The ColorPicker is embedded in a ContextMenu
In the second situation, the proposed solution for the first one is no longer valid, since the window found will be the one with the context menu.
A task is required to keep looking for windows until the one with the ComboBoxPopupControl is found.
This is a full runnable example:
public class ColorPickerFinder extends Application {
ExecutorService findWindowExecutor = createExecutor("FindWindow");
#Override
public void start(Stage primaryStage) {
AnchorPane panCircles = new AnchorPane();
Scene scene = new Scene(panCircles, 400, 400);
final Random random = new Random();
IntStream.range(0,5).boxed().forEach(i->{
final Circle circle= new Circle(20+random.nextInt(80),
Color.rgb(random.nextInt(255),random.nextInt(255),random.nextInt(255)));
circle.setTranslateX(100+random.nextInt(200));
circle.setTranslateY(100+random.nextInt(200));
panCircles.getChildren().add(circle);
});
panCircles.setPrefSize(400, 400);
ColorPicker colorPicker = new ColorPicker();
panCircles.getChildren().add(colorPicker);
primaryStage.setScene(scene);
primaryStage.show();
// We add listeners AFTER showing the stage, as we are looking for nodes
// by css selectors, these will be available only after the stage is shown
colorPicker.showingProperty().addListener((obs,b,b1)->{
if(b1){
// No need for task in this case
getPopupWindow();
}
});
panCircles.getChildren().stream()
.filter(c->c instanceof Circle)
.map(c->(Circle)c)
.forEach(circle->{
circle.setOnMouseClicked(e->{
if(e.getButton().equals(MouseButton.SECONDARY)){
// We need a task, since the first window found is the ContextMenu one
findWindowExecutor.execute(new WindowTask());
final ColorPicker picker = new ColorPicker();
picker.setStyle("-fx-border-radius: 10 10 10 10;"
+ "-fx-background-radius: 10 10 10 10;");
picker.setValue((Color)(circle.getFill()));
picker.valueProperty().addListener((obs,c0,c1)->circle.setFill(c1));
final ContextMenu contextMenu = new ContextMenu();
MenuItem editLabel = new MenuItem();
contextMenu.getItems().add(editLabel);
editLabel.setGraphic(picker);
contextMenu.show(panCircles,e.getScreenX(),e.getScreenY());
}
});
});
}
private PopupWindow getPopupWindow() {
#SuppressWarnings("deprecation")
final Iterator<Window> windows = Window.impl_getWindows();
while (windows.hasNext()) {
final Window window = windows.next();
if (window instanceof PopupWindow) {
if(window.getScene()!=null && window.getScene().getRoot()!=null){
Parent root = window.getScene().getRoot();
if(root.getChildrenUnmodifiable().size()>0){
Node popup = root.getChildrenUnmodifiable().get(0);
if(popup.lookup(".combo-box-popup")!=null){
// only process ComboBoxPopupControl
Platform.runLater(()->{
popup.lookupAll(".color-rect").stream()
.forEach(rect->{
Color c = (Color)((Rectangle)rect).getFill();
Tooltip.install(rect.getParent(),
new Tooltip("Custom tip for "+c.toString()));
});
});
return (PopupWindow)window;
}
}
}
return null;
}
}
return null;
}
private class WindowTask extends Task<Void> {
#Override
protected Void call() throws Exception {
boolean found=false;
while(!found){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
found=(getPopupWindow()!=null);
}
return null;
}
}
private ExecutorService createExecutor(final String name) {
ThreadFactory factory = r -> {
Thread t = new Thread(r);
t.setName(name);
t.setDaemon(true);
return t;
};
return Executors.newSingleThreadExecutor(factory);
}
public static void main(String[] args) {
launch(args);
}
}
This will be the result after right clicking on a circle, and clicking on the color picker:

Related

Why do I get MouseDown events from a position outside of the control's boundary?

I have a Composite containing a number of Text controls. I have attached MouseListeners to each control.
What surprises me is that sometimes, when I click on a control, I get a MouseDown event from its neighbour. The Event position is outside of the control's boundary and I get no event from the other control which I thought I had clicked on.
What can cause this to happen?
SNIPPET
Run. Press Esc to close the MessageBox. Click in field BBBB. Press Esc to close MessageBox. Click in field AAAA. The event is generated from field BBBB.
public class Test
{
public class MyListener implements MouseListener, FocusListener
{
private boolean active;
#Override
public void focusGained(FocusEvent e)
{
System.out.println(e);
message((Text) e.widget, "FocusGained");
}
#Override
public void focusLost(FocusEvent e)
{
System.out.println(e);
}
#Override
public void mouseDoubleClick(MouseEvent e)
{
}
#Override
public void mouseDown(MouseEvent e)
{
System.out.println(e);
}
private void message(final Text t, final String m)
{
if (active == false)
{
active = true;
MessageBox mb = new MessageBox(t.getShell());
mb.setText(m);
mb.setMessage(t.getMessage() + "\n\n" + m);
mb.open();
active = false;
}
}
#Override
public void mouseUp(MouseEvent e)
{
System.out.println(e);
message((Text) e.widget, e.toString());
}
}
private MyListener listener = null;
public static void main(String[] args)
{
Display display = new Display();
Shell shell = new Shell(display);
new Test(shell);
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
public Test(Composite parent)
{
listener = new MyListener();
create(parent);
}
private void create(Composite parent)
{
parent.setLayout(new GridLayout(2, true));
createText(parent, "AAAA");
createText(parent, "BBBB");
parent.layout(true);
}
private Text createText(Composite parent, String message)
{
Text t = new Text(parent, SWT.NONE);
t.setMessage(message);
GridData gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
t.setLayoutData(gd);
t.addFocusListener(listener);
t.addMouseListener(listener);
return t;
}
}
This is indeed strange. When traversing from B to A, the MouseUp event is marked as sent from field B.
If you replace the MessageBox with something non-interrupting i.e. System.out, the mouse event senders are the right ones.
To me, this seems more of a theoretical corner case. Decent applications would not interrupt the users field traversal with a modal window. However, if this is relevant for your, I'd report a bug to SWT.
I managed to get this working by de-coupling the pop-up window from the events. The message method now looks like this:
private void message(final Text t, final String m)
{
//NB active is declared as 'volatile'
if (active == false)
{
active = true;
t.getDisplay().asyncExec(new Runnable()
{
#Override
public void run()
{
MessageBox mb = new MessageBox(t.getShell());
mb.setText(m);
mb.setMessage(t.getMessage() + "\n\n" + m);
mb.open();
active = false;
}
});
}
else
{
System.out.println("Already active: " + t.getMessage());
}
}
This seems to give the events the opportunity to continue uninterrupted by the pop-up window. The pop-up will be activated a short while later. So far it works ok.

KeyEvent on Dialog not consumed when closing in JavaFX

I have a requirement to navigate a table when the user hits the ENTER key. For this I have created an event filter similar to:
private EventHandler<KeyEvent> keyReleasedFilter = event -> {
if ((event.getCode() == KeyCode.ENTER || event.getCode() == KeyCode.TAB)) {
previousPosition = table.getFocusModel().getFocusedCell();
//do my navigation
}
}
I have run into an issue where JavaFX Modal Dialogs used during navigation of the table to indicate an error, cause issues with this filter. If the user closed the dialog with the ENTER key, that event is trapped by my event filter on the parent stage. I am not sure how to prevent that. It is causing inconsistent navigation.
Here is a simple application that demonstrates the behavior:
public void start(Stage primaryStage) {
final Alert d = new Alert(Alert.AlertType.ERROR);
d.initModality(Modality.WINDOW_MODAL);
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.addEventFilter(KeyEvent.KEY_RELEASED, event -> {
if ((event.getCode() == KeyCode.ENTER || event.getCode() == KeyCode.TAB)) {
d.showAndWait();
}
});
Scene scene = new Scene(new StackPane(btn), 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
I have noticed that the dialog is closed with the KEY_PRESSED event, and will not capture the KEY_RELEASED event.
I have tried adding an EVENT FILTER to the dialog (Button/DialogPane/Scene and even Stage) - none intercept the KEY_RELEASED event.
Thanks.
Unless there is another reason to use the KEY_RELEASED event, then I would recommend switching to triggering on KEY_PRESSED for navigation as well and switch your EventFilter to an EventHandler. The example below will allow you to toggle the Alert on and off. When it's on, you'll notice that the button text doesn't change. When it's off, the button text will change. Take a look at how JavaFX constructs Event chains here if you haven't already.
boolean error = false;
int i = 0;
#Override
public void start(Stage primaryStage)
{
final Alert d = new Alert(Alert.AlertType.ERROR);
d.initModality(Modality.WINDOW_MODAL);
Button err = new Button();
err.setText("Error off");
err.addEventHandler(ActionEvent.ACTION, t ->
{
error = !error;
if (error)
err.setText("Error on");
else
err.setText("Error off");
});
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.addEventHandler(KeyEvent.KEY_PRESSED, event ->
{
if ((event.getCode() == KeyCode.ENTER || event.getCode() == KeyCode.TAB)) {
if (error)
{
d.showAndWait();
}
else
{
i++;
btn.setText(String.valueOf(i));
}
}
});
Scene scene = new Scene(new VBox(btn, err), 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
So I have a hack that works - I really don't like it, so I am hoping that there is a better solution (a clean one) to solve this issue....
Basically, right before I open my dialog, I set a boolean so I know it's open. Then in my event filter, kick out if that is set to true.
public class EventTester extends Application{
public static void main(String[] args){
launch(args);
}
private boolean modalWasShowing = false;
#Override
public void start(Stage primaryStage) {
final Alert d = new Alert(Alert.AlertType.ERROR);
d.initModality(Modality.WINDOW_MODAL);
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.addEventFilter(KeyEvent.KEY_RELEASED, event -> {
if(modalWasShowing){
modalWasShowing=false;
return;
}
if ((event.getCode() == KeyCode.ENTER || event.getCode() == KeyCode.TAB)) {
modalWasShowing = true;
d.showAndWait();
}
});
Scene scene = new Scene(new StackPane(btn), 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
Please let me know if you know of a better way to handle this.

Display custom color dialog directly- JavaFX ColorPicker

I need to show a "continuous" color palette for color selection inside a ContextMenu. Similar to CustomColorDialog that pops up on ColorPicker.
Is there a different class for this purpose or is it possible to work around by extending ColorPicker and showing directly CustomColorDialog instead of first showing ColorPicker.
TIA
For starters, com.sun.javafx.scene.control.skin.CustomColorDialog is private API, and it's not advisable to use it, as it may change in the future without notice.
Besides, it is a Dialog, what means you can't embed it into a ContextMenu, it has its own window and it's modal.
This is a short example of using this (very big, not customizable) dialog in your application, without using a 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'll get the dialog, but you won't get any possibility to send a custom color or retrieve the selected color, since properties like customColorProperty() are only accesible within the com.sun.javafx.scene.control.skin package.
So we need another way to implement our custom color selector. If you have a look at the source code of CustomColorDialog you'll see that it's relatively a simple control, and most important, almost based on public API: panes, regions and color.
Trying to put all in a ContextMenu could be overkilling, so I've come up with this basic example, where I'll just use the left part of the dialog, displaying the central bar on top. Most of the code is from the class. The CSS styling was also taken from modena.css (under custom-color-dialog CSS selector), but was customized as some of the nodes were rotated 90º.
This is a short version of CustomColorDialog class:
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 color.css file:
.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 of CustomMenuItem to allow clicking on the color selectors without closing the context menu. To close it just click anywhere outside the popup window.
This is how it looks like:
Based on this custom dialog, you can improve it and add the functionality you may need.
Here is how I use 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();
}
The showColorDialog() method of #Bosko Popovic didn't work for me. When I called it from the JavaFX thread (for example, on response to a button-click) it blocks and freezes the application. I still think there is merit in his approach, so here is a slightly modified version:
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());
}
The dialog field no longer has to be retrieved via reflection. You can get it directly by calling customColorDialog.getDialog(). You also don't need to get the color from the customColorProperty field via reflection as you can directly get it by calling customColorDialog.getCustomColor(). The nested-event-loop is needed to simulate a showAndWait() call to prevent input to the background Window when the dialog is shown.
You can store this method in a utility class, and when the day comes where the API is deprecated (or changed) as #José Pereda mentions, you can then implement a custom color dialog by making use of his example code.

Why is a MenuItem not responding?

There is a ContextMenu which has two options and when the second option (item2 in the code) is pressed with the right mousebutton I want it to print out some text so I know I did actually activate it. Up till now nothing happens when I click on the second mousebutton.
I haven't had much experience yet with Eventhandlers so my apologies if I made a noobish mistake.
private void maakContextMenu() {
menu = new ContextMenu();
MenuItem item = new MenuItem("Kleur Assen");
MenuItem item2 = new MenuItem("tweede optie");
final LissajousCanvas canvas = this;
item.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
new KiesKleur(canvas).show();
}
});
item2.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent t) {
System.out.println("in the loop");
if(t.getSource()==MouseButton.SECONDARY){
System.out.println("in too deep");
}
new KiesKleur(canvas).show();
}
});
menu.getItems().addAll(item, item2);
}
A MenuItem is not actually a Node, so it's not part of the scene graph in the way that Nodes are. So I'm not really sure if this is a bug or not; I think it probably only implements EventTarget so it can specifically generate ActionEvents. You'll have noticed there is no setOnMouseClicked(...) method available.
Here's a workaround. I'm not sure why it only works with MOUSE_PRESSED and not with MOUSE_CLICKED, but it's likely something to do with the default mouse event handling that generates the action events:
private void maakContextMenu() {
menu = new ContextMenu();
MenuItem item = new MenuItem("", new Label("Kleur Assen"));
Label menuItem2Label = new Label("tweede optie");
MenuItem item2 = new MenuItem("", menuItem2Label);
final LissajousCanvas canvas = this;
item.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
new KiesKleur(canvas).show();
}
});
menuItem2Label.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent t) {
System.out.println("in the loop");
if(t.getButton()==MouseButton.SECONDARY){
System.out.println("in too deep");
}
new KiesKleur(canvas).show();
}
});
menu.getItems().addAll(item, item2);
}

Drag and Drop vbox element with show snapshot in javafx

I want drag an element in vbox as a parent and show node moving during drag and drop of element, how can do this with The slightest change.
Just register mouse listeners with the elements of the VBox. You want to call startFullDrag() on the node on a dragDetected event, and rotate the child nodes of the VBox on a dragReleased event. You can use the dragEntered and dragExited events if you want to give visual hints to the user about the drag.
See the API docs for more.
Simple example (code is way cleaner in JavaFX 8, btw):
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseDragEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
final VBox root = new VBox(5);
final ScrollPane scroller = new ScrollPane();
scroller.setContent(root);
final Scene scene = new Scene(scroller,400,200);
for (int i=1; i<=20; i++) {
final Label label = new Label("Item "+i);
addWithDragging(root, label);
}
// in case user drops node in blank space in root:
root.setOnMouseDragReleased(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
int indexOfDraggingNode = root.getChildren().indexOf(event.getGestureSource());
rotateNodes(root, indexOfDraggingNode, root.getChildren().size()-1);
}
});
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
private void addWithDragging(final VBox root, final Label label) {
label.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
label.startFullDrag();
}
});
// next two handlers just an idea how to show the drop target visually:
label.setOnMouseDragEntered(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
label.setStyle("-fx-background-color: #ffffa0;");
}
});
label.setOnMouseDragExited(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
label.setStyle("");
}
});
label.setOnMouseDragReleased(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
label.setStyle("");
int indexOfDraggingNode = root.getChildren().indexOf(event.getGestureSource());
int indexOfDropTarget = root.getChildren().indexOf(label);
rotateNodes(root, indexOfDraggingNode, indexOfDropTarget);
event.consume();
}
});
root.getChildren().add(label);
}
private void rotateNodes(final VBox root, final int indexOfDraggingNode,
final int indexOfDropTarget) {
if (indexOfDraggingNode >= 0 && indexOfDropTarget >= 0) {
final Node node = root.getChildren().remove(indexOfDraggingNode);
root.getChildren().add(indexOfDropTarget, node);
}
}
public static void main(String[] args) {
launch(args);
}
}
This is an addendum to #James_D's excellent answer
This shows how to add an image preview to the draggable node as #James_D suggests in his comment:
private void addPreview(final VBox root, final Label label) {
ImageView imageView = new ImageView(label.snapshot(null, null));
imageView.setManaged(false);
imageView.setMouseTransparent(true);
root.getChildren().add(imageView);
root.setUserData(imageView);
root.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
imageView.relocate(event.getX(), event.getY());
}
});
}
private void removePreview(final VBox root) {
root.setOnMouseDragged(null);
root.getChildren().remove(root.getUserData());
root.setUserData(null);
}
Call addPreview() in label.setOnDragDetected(). Call removePreview() in label.setOnMouseDragReleased() and root.setOnMouseDragReleased().
There is a much better solution that is far cleaner now.
// Root is the node you want to drag, not the scene root.
root.setOnDragDetected(mouseEvent -> {
final ImageView preview = new ImageView(root.snapshot(null, null));
final Dragboard db = root.startDragAndDrop(TransferMode.ANY);
db.setContent( // Set your content to something here.
);
db.setDragView(preview.getImage());
mouseEvent.consume();
});