im trying an easy drag-pane setup. My results are funny.
Dragging an Node within an Pane results in an jumping effect?
While dragging the dot jumps to a given position and with the next drag back to the last position.
Any help?
import java.util.concurrent.atomic.AtomicReference;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.PickResult;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class DrawPolygon extends Application {
Group g ;
PickResult pickResult;
Node intersectedNode;
final AtomicReference<MouseEvent> deltaEvent = new AtomicReference<MouseEvent>();
#Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root, 600, 800);
stage.setScene(scene);
g = new Group();
Rectangle blue = new Rectangle();
blue.setFill(Color.BLUE);
blue.setWidth(25);
blue.setHeight(25);
blue.setX(50);
blue.setY(50);
Rectangle red = new Rectangle();
red.setFill(Color.RED);
red.setWidth(25);
red.setHeight(25);
red.setX(150);
red.setY(150);
Rectangle yellow = new Rectangle();
yellow.setFill(Color.YELLOW);
yellow.setWidth(25);
yellow.setHeight(25);
yellow.setX(250);
yellow.setY(250);
blue.addEventFilter(MouseEvent.MOUSE_CLICKED, onMouseClickedEventHandler);
red.addEventFilter(MouseEvent.MOUSE_CLICKED, onMouseClickedEventHandler);
yellow.addEventFilter(MouseEvent.MOUSE_CLICKED, onMouseClickedEventHandler);
blue.addEventFilter(MouseEvent.MOUSE_PRESSED, onMousePressedEventHandler);
red.addEventFilter(MouseEvent.MOUSE_PRESSED, onMousePressedEventHandler);
yellow.addEventFilter(MouseEvent.MOUSE_PRESSED, onMousePressedEventHandler);
blue.addEventFilter(MouseEvent.MOUSE_DRAGGED, onMouseDraggedEventHandler);
red.addEventFilter(MouseEvent.MOUSE_DRAGGED, onMouseDraggedEventHandler);
yellow.addEventFilter(MouseEvent.MOUSE_DRAGGED, onMouseDraggedEventHandler);
blue.addEventFilter(MouseEvent.MOUSE_RELEASED, onMouseReleasedEventHandler);
red.addEventFilter(MouseEvent.MOUSE_RELEASED, onMouseReleasedEventHandler);
yellow.addEventFilter(MouseEvent.MOUSE_RELEASED, onMouseReleasedEventHandler);
g.getChildren().add(blue);
g.getChildren().add(red);
g.getChildren().add(yellow);
scene.setRoot(g);
stage.show();
}
EventHandler<MouseEvent> onMouseClickedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.print("C");
}
};
EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.print("P");
pickResult = event.getPickResult();
intersectedNode = pickResult.getIntersectedNode();
deltaEvent.set(event);
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.print("D");
final double deltaX = event.getX() - deltaEvent.get().getX();
final double deltaY = event.getY() - deltaEvent.get().getY();
intersectedNode.setLayoutX(event.getX() - deltaX);
intersectedNode.setLayoutY(event.getY() - deltaY);
deltaEvent.set(event);
g.layout();
}
};
EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("R");
}
};
public static void main(String[] args) {
launch(args);
}
}
You're using positions relative to the Nodes, which messes up the calculations, and in addition to that the Node is also being moved:
newLayoutX = event.getX() - deltaX
= event.getX() - (event.getX() - deltaEvent.get().getX())
= deltaEvent.get().getX()
which is obviously wrong, since the event coordinates are in the coordinates Node the EventHandler is registered to.
Solution
Use parent coordinates
Additional Notes:
You don't get any benefit from using AtomicReference instead of a non-final field.
You are not filtering events, you're handling them; Therefore addEventHandler should be used instead of addEventFilter. Furthermore you can use the convenience methods which makes your code simpler.
Instead of adding the event handlers to every child node, you could also add the event handler to the Group which also removes the need of transforming the coordinates "manually".
Group g;
Node intersectedNode;
private Point2D dragStart;
private final Set<Node> draggable = new HashSet<>();
#Override
public void start(Stage stage) {
g = new Group();
Scene scene = new Scene(g, 600, 800);
stage.setScene(scene);
Rectangle blue = new Rectangle();
...
yellow.setY(250);
draggable.addAll(Arrays.asList(red, blue, yellow));
g.getChildren().addAll(blue, red, yellow);
g.setOnMouseClicked(onMouseClickedEventHandler);
g.setOnMousePressed(onMousePressedEventHandler);
g.setOnMouseDragged(onMouseDraggedEventHandler);
g.setOnMouseReleased(onMouseReleasedEventHandler);
scene.setRoot(g);
stage.show();
}
EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.print("P");
PickResult pickResult = event.getPickResult();
intersectedNode = pickResult.getIntersectedNode();
System.out.println(intersectedNode);
if (draggable.contains(intersectedNode)) {
dragStart = new Point2D(intersectedNode.getLayoutX() - event.getX(), intersectedNode.getLayoutY() - event.getY());
} else {
intersectedNode = null;
}
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.print("D");
if (intersectedNode != null) {
intersectedNode.setLayoutX(event.getX() + dragStart.getX());
intersectedNode.setLayoutY(event.getY() + dragStart.getY());
}
}
};
Related
I have a simple JavaFX pan and zoom application as show below. The pan and zoom functionality work great, but I would also like to be able to drag and drop the circle node too. The problem I have is that the scrollpane gets all of the mouse events first, so I'm unable to assign a drag and drop to just the circle. Is it possible to have a draggable/zoomable scrollpane and also be able to drag a node inside the pane?
Screenshot
Here us the code that I'm using:
package sample;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private ScrollPane scrollPane = new ScrollPane();
private final DoubleProperty zoomProperty = new SimpleDoubleProperty(1.0d);
private final DoubleProperty deltaY = new SimpleDoubleProperty(0.0d);
private final Group group = new Group();
ImageView bigImageView = null;
PanAndZoomPane panAndZoomPane = null;
Pane featuresPane = new Pane();
#Override
public void start(Stage primaryStage) throws Exception{
bigImageView = new ImageView();
StackPane bigStackpane = new StackPane();
bigStackpane.getChildren().add(bigImageView);
bigStackpane.getChildren().add(featuresPane);
featuresPane.toFront();
featuresPane.setOpacity(.8);
Circle circle = new Circle();
circle.setCenterX(200);
circle.setCenterY(200);
circle.setRadius(100);
circle.setFill(Color.RED);
circle.setOnMouseClicked(e -> {
System.out.println("circle clicked");
});
featuresPane.getChildren().add(circle);
scrollPane.setPannable(true);
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
group.getChildren().add(bigImageView);
group.getChildren().add(bigStackpane);
panAndZoomPane = new PanAndZoomPane();
zoomProperty.bind(panAndZoomPane.myScale);
deltaY.bind(panAndZoomPane.deltaY);
panAndZoomPane.getChildren().add(group);
SceneGestures sceneGestures = new SceneGestures(panAndZoomPane);
scrollPane.setContent(panAndZoomPane);
panAndZoomPane.toBack();
scrollPane.addEventFilter( MouseEvent.MOUSE_CLICKED, sceneGestures.getOnMouseClickedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
scrollPane.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());
AnchorPane bigImageAnchorPane = new AnchorPane();
bigImageAnchorPane.getChildren().add(scrollPane);
Image image = new Image("https://i.imgur.com/8p1XBag.jpg");
bigImageView.setImage(image);
bigImageAnchorPane.setTopAnchor(scrollPane, 1.0d);
bigImageAnchorPane.setRightAnchor(scrollPane, 1.0d);
bigImageAnchorPane.setBottomAnchor(scrollPane, 1.0d);
bigImageAnchorPane.setLeftAnchor(scrollPane, 1.0d);
BorderPane root = new BorderPane(bigImageAnchorPane);
Label label = new Label("Pan and Zoom Test");
root.setTop(label);
Scene scene = new Scene(root, 1000, 1000);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
class PanAndZoomPane extends Pane {
public static final double DEFAULT_DELTA = 1.5d; //1.3d
DoubleProperty myScale = new SimpleDoubleProperty(1.0);
public DoubleProperty deltaY = new SimpleDoubleProperty(0.0);
private Timeline timeline;
public PanAndZoomPane() {
this.timeline = new Timeline(30);//60
// add scale transform
scaleXProperty().bind(myScale);
scaleYProperty().bind(myScale);
}
public double getScale() {
return myScale.get();
}
public void setScale( double scale) {
myScale.set(scale);
}
public void setPivot( double x, double y, double scale) {
// note: pivot value must be untransformed, i. e. without scaling
// timeline that scales and moves the node
timeline.getKeyFrames().clear();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.millis(100), new KeyValue(translateXProperty(), getTranslateX() - x)), //200
new KeyFrame(Duration.millis(100), new KeyValue(translateYProperty(), getTranslateY() - y)), //200
new KeyFrame(Duration.millis(100), new KeyValue(myScale, scale)) //200
);
timeline.play();
}
public double getDeltaY() {
return deltaY.get();
}
public void setDeltaY( double dY) {
deltaY.set(dY);
}
}
/**
* Mouse drag context used for scene and nodes.
*/
class DragContext {
double mouseAnchorX;
double mouseAnchorY;
double translateAnchorX;
double translateAnchorY;
}
/**
* Listeners for making the scene's canvas draggable and zoomable
*/
public class SceneGestures {
private DragContext sceneDragContext = new DragContext();
PanAndZoomPane panAndZoomPane;
public SceneGestures( PanAndZoomPane canvas) {
this.panAndZoomPane = canvas;
}
public EventHandler<MouseEvent> getOnMouseClickedEventHandler() {
return onMouseClickedEventHandler;
}
public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
return onMousePressedEventHandler;
}
public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
return onMouseDraggedEventHandler;
}
public EventHandler<ScrollEvent> getOnScrollEventHandler() {
return onScrollEventHandler;
}
private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
sceneDragContext.mouseAnchorX = event.getX();
sceneDragContext.mouseAnchorY = event.getY();
sceneDragContext.translateAnchorX = panAndZoomPane.getTranslateX();
sceneDragContext.translateAnchorY = panAndZoomPane.getTranslateY();
}
};
private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
panAndZoomPane.setTranslateX(sceneDragContext.translateAnchorX + event.getX() - sceneDragContext.mouseAnchorX);
panAndZoomPane.setTranslateY(sceneDragContext.translateAnchorY + event.getY() - sceneDragContext.mouseAnchorY);
event.consume();
}
};
/**
* Mouse wheel handler: zoom to pivot point
*/
private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double delta = PanAndZoomPane.DEFAULT_DELTA;
double scale = panAndZoomPane.getScale(); // currently we only use Y, same value is used for X
double oldScale = scale;
panAndZoomPane.setDeltaY(event.getDeltaY());
if (panAndZoomPane.deltaY.get() < 0) {
scale /= delta;
} else {
scale *= delta;
}
double f = (scale / oldScale)-1;
double dx = (event.getX() - (panAndZoomPane.getBoundsInParent().getWidth()/2 + panAndZoomPane.getBoundsInParent().getMinX()));
double dy = (event.getY() - (panAndZoomPane.getBoundsInParent().getHeight()/2 + panAndZoomPane.getBoundsInParent().getMinY()));
panAndZoomPane.setPivot(f*dx, f*dy, scale);
event.consume();
}
};
/**
* Mouse click handler
*/
private EventHandler<MouseEvent> onMouseClickedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getButton().equals(MouseButton.PRIMARY)) {
if (event.getClickCount() == 2) {
System.out.println("Image Layer Double Clicked...");
}else{
System.out.println("Image Layer Clicked...");
}
}
}
};
}
}
Your code is adding behavior via event filters. These filters are invoked during the event capturing phase which means they are invoked before the events reach your circle. You should strive to implement your behavior via event handlers, which are invoked during the event bubbling phase. Then you can consume events to prevent them from reaching ancestors, allowing you to drag your circle without scrolling/panning the scroll-pane content. For more information about event handling and propagation, check out this tutorial.
Here's a proof-of-concept which adds the zoom-handling to the scroll-pane's content and still let's you drag around a circle:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
// using your example image
ImageView imageView = new ImageView("https://i.imgur.com/8p1XBag.jpg");
Circle circle = new Circle(100, 100, 25, Color.FIREBRICK);
circle.setOnMousePressed(
e -> {
// prevent pannable ScrollPane from changing cursor on drag-detected (implementation
// detail)
e.setDragDetect(false);
Point2D offset =
new Point2D(e.getX() - circle.getCenterX(), e.getY() - circle.getCenterY());
circle.setUserData(offset);
e.consume(); // prevents MouseEvent from reaching ScrollPane
});
circle.setOnMouseDragged(
e -> {
// prevent pannable ScrollPane from changing cursor on drag-detected (implementation
// detail)
e.setDragDetect(false);
Point2D offset = (Point2D) circle.getUserData();
circle.setCenterX(e.getX() - offset.getX());
circle.setCenterY(e.getY() - offset.getY());
e.consume(); // prevents MouseEvent from reaching ScrollPane
});
// the zoom-able content of the ScrollPane
Group group = new Group(imageView, circle);
// wrap Group in another Group since it's the former that's scaled and
// Groups only take transformations of their **children** into account (not themselves)
StackPane content = new StackPane(new Group(group));
content.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
// due to later configuration, the StackPane will always cover the entire viewport
content.setOnScroll(
e -> {
if (e.isShortcutDown() && e.getDeltaY() != 0) {
if (e.getDeltaY() < 0) {
group.setScaleX(Math.max(group.getScaleX() - 0.1, 0.5));
} else {
group.setScaleX(Math.min(group.getScaleX() + 0.1, 5.0));
}
group.setScaleY(group.getScaleX());
e.consume(); // prevents ScrollEvent from reaching ScrollPane
}
});
// use StackPane (or some other resizable node) as content since Group is not
// resizable. Note StackPane will center content if smaller than viewport.
ScrollPane scrollPane = new ScrollPane(content);
scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER);
scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
scrollPane.setPannable(true);
// ensure StackPane content always has at least the same dimensions as the viewport
scrollPane.setFitToWidth(true);
scrollPane.setFitToHeight(true);
primaryStage.setScene(new Scene(scrollPane, 1000, 650));
primaryStage.show();
}
}
Note this does not exactly replicate the behavior of your example. It does not use animations nor does it zoom on a pivot point. But hopefully it can help you move forward in your application.
Scenario : I have two panel one of them has slider and another has drag gable pane .
Case I : if Pane(i.e Drag gable ) moved in forward or backward direction .node on slider moved with respective dimension in same direction of pane moved.
Case II : this functionality works vice versa .
(Drag gable pane with slider)
(slider with drag gable Pane).
so how i can achieve it..
Thank You!
You create a property for the horizontal direction. Then you change the property whenever the slider or the pane moves. Depending on the property changes you move the pane.
public class Main extends Application {
DoubleProperty xProperty = new SimpleDoubleProperty();
double min = 0;
double max = 100;
#Override
public void start(Stage primaryStage) {
try {
Pane root = new Pane();
// rectangle
Pane pane = new Pane();
pane.setStyle("-fx-background-color:blue");
pane.setPrefSize(50, 50);
pane.relocate(min, 50);
// make rectangle movable
MouseGestures mg = new MouseGestures();
mg.makeDraggable(pane);
// slider
Slider slider = new Slider(min, max, min);
slider.valueProperty().bindBidirectional( xProperty);
root.getChildren().addAll(slider, pane);
// move horizontally, clamp horizontal movement
xProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed( ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
// clamp x
double x = newValue.doubleValue();
if( x < min) {
x = min;
}
if( x > max) {
x = max;
}
pane.relocate( x, pane.getBoundsInParent().getMinY());
}
});
Scene scene = new Scene(root,1024,768);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
public class MouseGestures {
final DragContext dragContext = new DragContext();
public void makeDraggable(final Node node) {
node.setOnMousePressed(onMousePressedEventHandler);
node.setOnMouseDragged(onMouseDraggedEventHandler);
}
EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
Node node = (Node) event.getSource();
dragContext.x = node.getBoundsInParent().getMinX() - event.getScreenX();
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
double offsetX = event.getScreenX() + dragContext.x;
xProperty.set(offsetX);
}
};
class DragContext {
double x;
}
}
}
I want to create button which changes the default picture when I move mouse over. I made this example but it's not working properly:
public class MainApp extends Application
{
#Override
public void start(Stage stage) throws Exception
{
StackPane bp = new StackPane();
bp.getChildren().add(ReportsIcon());
bp.setPrefSize(600, 600);
Scene scene = new Scene(bp);
scene.setFill(Color.ANTIQUEWHITE);
stage.setTitle("JavaFX and Maven");
stage.setScene(scene);
stage.show();
}
private static final ImageView ReportsFirstIcon;
static
{
ReportsFirstIcon = new ImageView(MainApp.class.getResource("/images/monitoring-colour.png").toExternalForm());
}
private static final ImageView RportsIconsSecond;
static
{
RportsIconsSecond = new ImageView(MainApp.class.getResource("/images/monitoring-green.png").toExternalForm());
}
private HBox ReportsIcon()
{
HBox bpi = new HBox();
bpi.setAlignment(Pos.CENTER);
// Add Label to the Icon
Text inftx = new Text("Reports");
inftx.setFont(Font.font("Verdana", FontWeight.NORMAL, 13)); // Set font and font size
inftx.setFill(Color.BLACK); // Set font color
// Zoom into the picture and display only selected area
Rectangle2D viewportRect = new Rectangle2D(0, 0, 0, 0);
ReportsFirstIcon.setViewport(viewportRect);
BorderPane pp = new BorderPane();
pp.setCenter(ReportsFirstIcon);
bpi.getChildren().addAll(pp, inftx);
bpi.setOnMouseEntered(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent t)
{
pp.setCenter(ReportsFirstIcon);
}
});
bpi.setOnMouseExited(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent t)
{
pp.setCenter(RportsIconsSecond);
}
});
bpi.setOnMouseClicked(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent t)
{
// Open new window
}
});
return bpi;
}
private HBox mouseOver(final HBox bp)
{
bp.setOnMouseEntered(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent t)
{
bp.setStyle("-fx-background-color: linear-gradient(#f2f2f2, #f2f2f2);"
+ " -fx-background-insets: 0 0 -1 0, 0, 1, 2;"
+ " -fx-background-radius: 3px, 3px, 2px, 1px;");
}
});
bp.setOnMouseExited(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent t)
{
bp.setStyle("-fx-background-color: linear-gradient(#f2f2f2, #d4d4d4);"
+ " -fx-background-insets: 0 0 -1 0, 0, 1, 2;"
+ " -fx-background-radius: 3px, 3px, 2px, 1px;");
}
});
return bp;
}
public static void main(String[] args)
{
launch(args);
}
}
Now the code in not working properly the original image is not returned back when I move the mouse outside of the Second BorderPane which is used to hold the picture.
Picture is changed when I move the mouse outside of the stage. Any ideas how to fix this?
I want to show by default the first picture and when I move the mouse over it to replace it with the second. When I move the mouse outside I want to restore the original picture.
Solution Approach
You can bind the button's graphic property to an appropriate ImageView based upon the button's hover property.
button.graphicProperty().bind(
Bindings.when(
button.hoverProperty()
)
.then(meatView)
.otherwise(lambView)
);
Unhovered:
Hovered:
Executable Sample
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.StackPane;
import javafx.scene.text.*;
import javafx.stage.Stage;
public class MuttonMorph extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
ImageView lambView = new ImageView(
new Image(
lambLoc
)
);
ImageView meatView = new ImageView(
new Image(
meatLoc
)
);
Button button = new Button("Lamb,\nit's what's for dinner");
button.setContentDisplay(ContentDisplay.TOP);
button.setTextAlignment(TextAlignment.CENTER);
button.setFont(Font.font(16));
button.graphicProperty().bind(
Bindings.when(
button.hoverProperty()
)
.then(meatView)
.otherwise(lambView)
);
StackPane layout = new StackPane(button);
layout.setPadding(new Insets(30));
stage.setScene(new Scene(layout));
stage.show();
}
// Icons are Linkware (Backlink to http://icons8.com required)
private static final String lambLoc = "http://icons.iconarchive.com/icons/icons8/ios7/96/Animals-Sheep-icon.png";
private static final String meatLoc = "http://icons.iconarchive.com/icons/icons8/ios7/96/Food-Lamb-Rack-icon.png";
}
Alternate Approach
You could probably do a similar thing without a binding by setting by defining appropriate CSS style rules based on the button's :hover CSS pseudo-class and -fx-graphic attribute.
I try to make a simple calculator with 20 buttons and one handler. In java I can use 'if' statement with event.getSource() in ActionPerformed to check which button is pressed, but it doesn't work with handler in javafx. Is it possible in javafx that all buttons has one handler? (I don't want to use java 8 Lambdas.)
Last time I tried with setId/getId but it same not work (to me).
public class Calculator extends Application {
public Button b0, b1;
#Override
public void start(Stage primaryStage) {
GridPane grid = new GridPane();
b0 = new Button("0");
b0.setId("0");
b0.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
grid.add(b0, 0, 1);
b0.setOnAction(myHandler);
b1 = new Button("1");
b1.setId("1");
b1.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
grid.add(b1, 0, 0);
b1.setOnAction(myHandler);
Scene scene = new Scene(grid, 365, 300);
scene.getStylesheets().add
(Calculator.class.getResource("calculator.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
final EventHandler<ActionEvent> myHandler = new EventHandler<ActionEvent>(){
#Override
public void handle(final ActionEvent event) {
Button x = (Button) event.getSource();
if (x.getId().equals(b0.getId()))
System.out.println("0");
else if(x.getId().equals(b1.getId()))
System.out.println("1");
}
};
public static void main(String[] args) {
launch(args);
}
}
I tested your code and it seems to work just fine.
There's no real reason to test the ids of the buttons, though. If you really want to use the same handler (which I don't advise), just test for equality between each button and the source of the event:
final EventHandler<ActionEvent> myHandler = new EventHandler<ActionEvent>(){
#Override
public void handle(final ActionEvent event) {
if (event.getSource() == b0)
System.out.println("0");
else if(event.getSource() == b1)
System.out.println("1");
}
};
But it's (almost?) always better to use a different handler for each action. It keeps the code free of all the if/else constructs, which both makes it cleaner and better in terms of performance. Here, since your buttons do almost the same thing, you can use a single implementation but multiple objects.
Here's a complete example:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class Calculator extends Application {
private final IntegerProperty value = new SimpleIntegerProperty();
class NumberButtonHandler implements EventHandler<ActionEvent> {
private final int number ;
NumberButtonHandler(int number) {
this.number = number ;
}
#Override
public void handle(ActionEvent event) {
value.set(value.get() * 10 + number);
}
}
#Override
public void start(Stage primaryStage) {
GridPane grid = createGrid();
for (int n = 1; n<10; n++) {
Button button = createNumberButton(n);
int row = (n-1) / 3;
int col = (n-1) % 3 ;
grid.add(button, col, 2 - row);
}
Button zeroButton = createNumberButton(0);
grid.add(zeroButton, 1, 3);
Button clearButton = createButton("C");
// without lambdas:
// clearButton.setOnAction(
// new EventHandler<ActionEvent>() {
// #Override
// public void handle(ActionEvent event) {
// value.set(0);
// }
// }
// );
// with lambdas:
clearButton.setOnAction(event -> value.set(0));
grid.add(clearButton, 2, 3);
TextField displayField = createDisplayField();
BorderPane root = new BorderPane();
root.setPadding(new Insets(10));
root.setTop(displayField);
root.setCenter(grid);
Scene scene = new Scene(root, 365, 300);
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
private Button createNumberButton(int number) {
Button button = createButton(Integer.toString(number));
button.setOnAction(new NumberButtonHandler(number));
return button ;
}
private Button createButton(String text) {
Button button = new Button(text);
button.setMaxSize(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
GridPane.setFillHeight(button, true);
GridPane.setFillWidth(button, true);
GridPane.setHgrow(button, Priority.ALWAYS);
GridPane.setVgrow(button, Priority.ALWAYS);
return button ;
}
private GridPane createGrid() {
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(5);
grid.setVgap(5);
grid.setPadding(new Insets(10));
return grid;
}
private TextField createDisplayField() {
TextField displayField = new TextField();
displayField.textProperty().bind(Bindings.format("%d", value));
displayField.setEditable(false);
displayField.setAlignment(Pos.CENTER_RIGHT);
return displayField;
}
public static void main(String[] args) {
launch(args);
}
}
Is there a way to modify a polygon in JavaFX? For example if I have a triangle and I press and then drag a point from that triangle, the triangle will modify with the new coordinates of the point.
Layer some control nodes over the corners of the polygon.
Attach appropriate event handlers to the control nodes so that they can be dragged around.
Modify the polygon's points as the control node is moved (using change listeners attached to each of the control node's location properties).
Here is a sample solution:
import javafx.scene.Scene;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.*;
import javafx.collections.*;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
/** Drag the anchors around to change a polygon's points. */
public class TriangleManipulator extends Application {
public static void main(String[] args) throws Exception { launch(args); }
// main application layout logic.
#Override public void start(final Stage stage) throws Exception {
Polygon triangle = createStartingTriangle();
Group root = new Group();
root.getChildren().add(triangle);
root.getChildren().addAll(createControlAnchorsFor(triangle.getPoints()));
stage.setTitle("Triangle Manipulation Sample");
stage.setScene(
new Scene(
root,
400, 400, Color.ALICEBLUE
)
);
stage.show();
}
// creates a triangle.
private Polygon createStartingTriangle() {
Polygon triangle = new Polygon();
triangle.getPoints().setAll(
100d, 100d,
150d, 50d,
250d, 150d
);
triangle.setStroke(Color.FORESTGREEN);
triangle.setStrokeWidth(4);
triangle.setStrokeLineCap(StrokeLineCap.ROUND);
triangle.setFill(Color.CORNSILK.deriveColor(0, 1.2, 1, 0.6));
return triangle;
}
// #return a list of anchors which can be dragged around to modify points in the format [x1, y1, x2, y2...]
private ObservableList<Anchor> createControlAnchorsFor(final ObservableList<Double> points) {
ObservableList<Anchor> anchors = FXCollections.observableArrayList();
for (int i = 0; i < points.size(); i+=2) {
final int idx = i;
DoubleProperty xProperty = new SimpleDoubleProperty(points.get(i));
DoubleProperty yProperty = new SimpleDoubleProperty(points.get(i + 1));
xProperty.addListener(new ChangeListener<Number>() {
#Override public void changed(ObservableValue<? extends Number> ov, Number oldX, Number x) {
points.set(idx, (double) x);
}
});
yProperty.addListener(new ChangeListener<Number>() {
#Override public void changed(ObservableValue<? extends Number> ov, Number oldY, Number y) {
points.set(idx + 1, (double) y);
}
});
anchors.add(new Anchor(Color.GOLD, xProperty, yProperty));
}
return anchors;
}
// a draggable anchor displayed around a point.
class Anchor extends Circle {
private final DoubleProperty x, y;
Anchor(Color color, DoubleProperty x, DoubleProperty y) {
super(x.get(), y.get(), 10);
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
this.x = x;
this.y = y;
x.bind(centerXProperty());
y.bind(centerYProperty());
enableDrag();
}
// make a node movable by dragging it around with the mouse.
private void enableDrag() {
final Delta dragDelta = new Delta();
setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = getCenterX() - mouseEvent.getX();
dragDelta.y = getCenterY() - mouseEvent.getY();
getScene().setCursor(Cursor.MOVE);
}
});
setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
getScene().setCursor(Cursor.HAND);
}
});
setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
double newX = mouseEvent.getX() + dragDelta.x;
if (newX > 0 && newX < getScene().getWidth()) {
setCenterX(newX);
}
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getScene().getHeight()) {
setCenterY(newY);
}
}
});
setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.HAND);
}
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.DEFAULT);
}
}
});
}
// records relative x and y co-ordinates.
private class Delta { double x, y; }
}
}
I derived this solution from: CubicCurve JavaFX