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.
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:
I'm working on a very simple program. I have an ImageView and I want to firing to this ImageView a ZoomEvent that I created. In Javafx 8 there 2 constructor that I can use to create ZoomEvent. But the listeners doesn't catch the event.
StackPane root = new StackPane();
final ImageView img = new ImageView(url);
img.setOnZoom(new EventHandler<ZoomEvent>() {
#Override
public void handle(ZoomEvent t) {
System.out.println("Zoom!");
}
});
img.setOnZoomStarted(new EventHandler<ZoomEvent>() {
#Override
public void handle(ZoomEvent t) {
System.out.println("Zoom Started!");
}
});
img.setOnZoomFinished(new EventHandler<ZoomEvent>() {
#Override
public void handle(ZoomEvent t) {
System.out.println("Zoom finished!");
}
});
ZoomEvent evt = new ZoomEvent(ZoomEvent.ANY, 200,200,200,200,false,false,false,false,false, false, 5.0,5.0, null);
root.getChildren().add(img);
Scene scene = new Scene(root, 600, 600);
primaryStage.setTitle("Zoom Sample");
primaryStage.setScene(scene);
primaryStage.show();
This is my code so far and I can't figure out what is wrong. There isn't any output.
You never fired the event with ZoomEvent.fireEvent(target, event) or stage.fireEvent(event).
You created the wrong type of event (create a ZoomEvent.ZOOM, not a ZoomEvent.ANY).
You didn't set the event screen co-ordinates correctly.
Working sample:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.input.ZoomEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Zoom extends Application {
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage stage) {
final ImageView img = new ImageView(
"http://icons.iconarchive.com/icons/kirei-kaze/kikis-delivery-service/256/jiji-icon.png" // image license: License: Free for non-commercial use. The products or characters depicted in these icons are © by Studio Ghibli. Linkback: http://www.iconarchive.com/artist/kirei-kaze.html
);
img.setOnZoom(e -> System.out.println("Zoom!"));
StackPane layout = new StackPane(img);
stage.setScene(new Scene(layout, 600, 600));
stage.show();
Point2D screenCoords = layout.localToScreen(300, 300);
ZoomEvent evt = new ZoomEvent(
ZoomEvent.ZOOM,
300, 300,
screenCoords.getX(), screenCoords.getY(),
false,false,false,false,false, false, 5.0,5.0, null
);
ZoomEvent.fireEvent(img, evt);
}
}
I'm trying to drop my draggable widgets out of the boundary panel (AbsolutePanel). In my case draggable widgets is an image. And I want to drop it, so that there will be visible only a part of the image, but when I drop it, and some parts of image beyond absolute panel, it drop automatically within absolute panel.
I tried :
dragController.setBehaviorConstrainedToBoundaryPanel(false);
and thought it means that I can drop it where ever I want, but it doesn't work.
And the working solution :)
Here is my code:
public class myEntripointClass implement EntryPOint{
AbsolutePanel droper;
public void onModuleLoad() {
Panel main = new AbsolutePanel();
droper = new AbsolutePanel();
droper.setHeight("300px");
droper.setWidth("500px");
main.add(droper);
content=new AbsolutePanel();
bt = new Button("Drag and drop it");
content.add(bt);
lb = new Label("Label drag and drop");
content.add(lb);
main.add(content);
manageDnD();
RootPanel.get().add(main);
}
private void manageDnD() {
PickupDragController dragController = new PickupDragController(
(AbsolutePanel) content, true);
dragController.makeDraggable(bt);
dragController.makeDraggable(lb);
dragController.addDragHandler(new DragHandler() {
#Override
public void onPreviewDragStart(DragStartEvent event)
throws VetoDragException {}
#Override
public void onPreviewDragEnd(DragEndEvent event) throws VetoDragException {}
#Override
public void onDragStart(DragStartEvent event) {
}
#Override
public void onDragEnd(DragEndEvent event) {
// TODO Auto-generated method stub
DragContext context = event.getContext();
int x=context.mouseX;
int y= context.mouseY;
droper.add(context.selectedWidgets.get(0),x,y);
}
});
NameDropController dropController = new NameDropController(droper);
dragController.registerDropController(dropController);
dragController.setBehaviorDragProxy(true);
}
and my DropController class is:
public class NameDropController extends AbstractDropController{
public NameDropController(Widget dropTarget) {
super(dropTarget);
}
#Override
public void onDrop(DragContext context) {
int x=getDiff(getDropTarget().getAbsoluteLeft(), context.mouseX);
int y=getDiff(getDropTarget().getAbsoluteTop(), context.mouseY);
((AbsolutePanel)getDropTarget()).add(context.selectedWidgets.get(0),x,y);
System.out.print("("+context.mouseX+"::,::"+context.mouseY+")");
}
#Override
public void onMove(DragContext context){
}
private int getDiff(int val1,int val2){
return Math.abs(val1-val2);
}
}
I'd like to make a draggable popup control, which is independent of its parent Window, and does not have the system's title bar.
I tried is to rewrite the JavaFx example for draggable panels in order to move an undecorated stage based on mouse events on a node, see code below. Unfortunately this doesn't seem to work properly, it's not smooth and jumps around the screen erratically for multiple screens. I read that some others used similar methods, however is this the only way to do this currently or did anyone find a nice solution ?
Thanks,
private Node makeDraggable(final Node node) {
final DragContext dragContext = new DragContext();
final Node wrapGroup = node;
wrapGroup.addEventFilter(
MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>() {
public void handle(final MouseEvent mouseEvent) {
dragContext.mouseAnchorX = mouseEvent.getX();
dragContext.mouseAnchorY = mouseEvent.getY();
dragContext.initialTranslateX =
stage.getX();
dragContext.initialTranslateY =
stage.getY();
}
});
wrapGroup.addEventFilter(
MouseEvent.MOUSE_DRAGGED,
new EventHandler<MouseEvent>() {
public void handle(final MouseEvent mouseEvent) {
stage.setX(
dragContext.initialTranslateX
+ mouseEvent.getX()
- dragContext.mouseAnchorX);
stage.setY(
dragContext.initialTranslateY
+ mouseEvent.getY()
- dragContext.mouseAnchorY);
dragContext.initialTranslateX =
stage.getX();
dragContext.initialTranslateY =
stage.getY();
}
});
return node;
}
I used this sample method below when making a clock.
I've only tested it on one screen, because I don't have multiple screens available.
/** holder structure for drag delta amounts */
private static class Delta { double x, y; }
/** makes a stage draggable using a given node */
public static void makeDraggable(final Stage stage, final Node byNode) {
final Delta dragDelta = new Delta();
byNode.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = stage.getX() - mouseEvent.getScreenX();
dragDelta.y = stage.getY() - mouseEvent.getScreenY();
byNode.setCursor(Cursor.MOVE);
}
});
byNode.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
byNode.setCursor(Cursor.HAND);
}
});
byNode.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
stage.setX(mouseEvent.getScreenX() + dragDelta.x);
stage.setY(mouseEvent.getScreenY() + dragDelta.y);
}
});
byNode.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
byNode.setCursor(Cursor.HAND);
}
}
});
byNode.setOnMouseExited(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
byNode.setCursor(Cursor.DEFAULT);
}
}
});
}