Move an ImageView with a drag and drop event - javafx

I am trying to create a JavaFX chess board and I encounter a problem with the drag and drop event on a piece.
chess board image
My chess board is composed of Case class extending Label and these Cases can have a Piece as a graphic (this class extends ImageView).
I am trying to make the pieces movable with a drag and drop event (setOnMouseDragged).
I've tried this code without success:
(we have c, a Case and p, a Piece)
c.setOnMousePressed(mouseEvent -> {
p.mouseAnchorX = mouseEvent.getX();
p.mouseAnchorY = mouseEvent.getY();
});
c.setOnMouseDragged(mouseEvent ->{
p.setLayoutX(mouseEvent.getSceneX() - p.mouseAnchorX);
p.setLayoutY(mouseEvent.getSceneY() - p.mouseAnchorY);
/*
p.setX(mouseEvent.getSceneX() - p.mouseAnchorX);
p.setY(mouseEvent.getSceneY() - p.mouseAnchorY);
*/
/*
p.setTranslateX(mouseEvent.getSceneX()/30 - 25.5);
p.setTranslateY(mouseEvent.getSceneY()/30 - 17);
*/
});
Both [setX / setY] and [setLayoutX / setLayoutY] are not moving the piece.
The closest I got was with the setTranslateX and setTranslateY methods : the pieces are moving but not appropriately.
Ideally, I'd like to use the setLayoutX and setLayoutY methods but I don't get why they don't work in my case.

We create a method with the name makeDraggable(), which has a Node as a parameter. In your case, you can use a for-loop to loop through each piece.
In the method, you create a Group which contains the Node. Then you add EventFilters for dragging and pressing. To remember the initial mouse cursor coordinates use DragContext. To disable MouseEvents for the children we need to add the following lines.
// adds EventFilter
wrapGroup.addEventFilter(
// for any MouseEvent
MouseEvent.ANY,
new EventHandler<MouseEvent>() {
public void handle(final MouseEvent mouseEvent) {
if (dragModeActiveProperty.get()) {
// disable mouse events for all children
mouseEvent.consume();
}
}
});
Then you're ready to add the mouse-pressed EventFilter.
// adds EventFilter
wrapGroup.addEventFilter(
// for mouse pressed
MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>() {
public void handle(final MouseEvent mouseEvent) {
if (dragModeActiveProperty.get()) {
// remember initial mouse cursor coordinates
// and node position
dragContext.mouseAnchorX = mouseEvent.getX();
dragContext.mouseAnchorY = mouseEvent.getY();
dragContext.initialTranslateX =
node.getTranslateX();
dragContext.initialTranslateY =
node.getTranslateY();
}
}
});
Afterwards, we only need to add the drag EventFilter.
// adds EventFilter
wrapGroup.addEventFilter(
// for mouse dragged
MouseEvent.MOUSE_DRAGGED,
new EventHandler<MouseEvent>() {
public void handle(final MouseEvent mouseEvent) {
if (dragModeActiveProperty.get()) {
// shift node from its initial position by delta
// calculated from mouse cursor movement
node.setTranslateX(
dragContext.initialTranslateX
+ mouseEvent.getX()
- dragContext.mouseAnchorX);
node.setTranslateY(
dragContext.initialTranslateY
+ mouseEvent.getY()
- dragContext.mouseAnchorY);
}
}
});
According to Oracles article about Working with Event Filters the full method can look like this:
private Node makeDraggable(final Node node) {
final DragContext dragContext = new DragContext();
final Group wrapGroup = new Group(node);
wrapGroup.addEventFilter(
MouseEvent.ANY,
new EventHandler<MouseEvent>() {
public void handle(final MouseEvent mouseEvent) {
if (dragModeActiveProperty.get()) {
// disable mouse events for all children
mouseEvent.consume();
}
}
});
wrapGroup.addEventFilter(
MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>() {
public void handle(final MouseEvent mouseEvent) {
if (dragModeActiveProperty.get()) {
// remember initial mouse cursor coordinates
// and node position
dragContext.mouseAnchorX = mouseEvent.getX();
dragContext.mouseAnchorY = mouseEvent.getY();
dragContext.initialTranslateX =
node.getTranslateX();
dragContext.initialTranslateY =
node.getTranslateY();
}
}
});
wrapGroup.addEventFilter(
MouseEvent.MOUSE_DRAGGED,
new EventHandler<MouseEvent>() {
public void handle(final MouseEvent mouseEvent) {
if (dragModeActiveProperty.get()) {
// shift node from its initial position by delta
// calculated from mouse cursor movement
node.setTranslateX(
dragContext.initialTranslateX
+ mouseEvent.getX()
- dragContext.mouseAnchorX);
node.setTranslateY(
dragContext.initialTranslateY
+ mouseEvent.getY()
- dragContext.mouseAnchorY);
}
}
});
return wrapGroup;
}
Similar to your question: Have a look at this stack overflow discussion.

Related

JavaFX detect when scrollbar movement is complete

Swing provided java.awt.event.AdjustmentEvent.getValueIsAdjusting() which could be used to detect when a JScrollBar movement was completed. This was used in a legacy application so that it only requested data once rather than on every little scroll movement.
JFrame frame = new JFrame();
JScrollBar comp = new JScrollBar();
comp.addAdjustmentListener(e -> {
if (!e.getValueIsAdjusting()) {
System.out.println("Finished " + e.getValue());
}
});
frame.add(comp);
comp.setPreferredSize(new Dimension(25, 300));
frame.pack();
frame.setVisible(true);
I'm looking for an equivalent method in JavaFX with ScrollBar. It appears that ScrollBar only has a way of monitoring the current value. I've reviewed the code in com.sun.javafx.scene.control.behavior.ScrollBarBehavior but can see no obvious way to do this.
My workaround for now is fairly ugly. It uses a pair of event listeners to drag if the mouse is pressed, this state is used to maintain another valueProperty() which is only updated when the mouse is released.
Is there a better/easier way to do this?
#Override
public void start(Stage primaryStage) throws Exception {
ScrollBar scrollbar = new ScrollBar();
new ScrollBarFinishedAdjusting(scrollbar).valueProperty().addListener((obs, oldValue, newValue) -> {
System.out.println("Finished " + newValue);
});
primaryStage.setScene(new Scene(scrollbar));
primaryStage.show();
}
private class ScrollBarFinishedAdjusting {
private boolean mouseDown;
private DoubleProperty value = new SimpleDoubleProperty();
private ScrollBar scrollbar;
public ScrollBarFinishedAdjusting(ScrollBar scrollbar) {
this.scrollbar = scrollbar;
scrollbar.valueProperty().addListener((obs, oldValue, newValue) -> {
if (!mouseDown) {
update();
}
});
scrollbar.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
mouseDown = true;
});
scrollbar.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> {
mouseDown = false;
update();
});
update();
}
private void update() {
value.set(scrollbar.getValue());
}
public ReadOnlyDoubleProperty valueProperty() {
return value;
}
}

don't recieve mouse press event on child node JavaFX

I want draw path between two imageview same as picture
.
this path start from one of these imageview by mouse press, continue by mouse press and move event on pane and must be end in another imageview by mouse press.here is the problem after first mouse press didn't recieve any mouse press event on imageviews, the event just recieves on the pane becuase of that draw line didn't stop. what is wrong in my code ?
here's my controller code :
public class DrawLine {
#FXML
ImageView imageView1 ;
#FXML
ImageView imageView2 ;
#FXML
AnchorPane pane ;
private Line currentLine ;
private String state ;
private DoubleProperty mouseX = new SimpleDoubleProperty();
private DoubleProperty mouseY = new SimpleDoubleProperty();
#FXML
private void initialize(){
state = "dfkl" ;
imageView1.setPreserveRatio( false);
imageView2.setPreserveRatio( false);
imageView1.setOnMousePressed( event -> {
imageMousePress( event);
});
imageView2.setOnMousePressed( event -> {
imageMousePress( event);
});
pane.setOnMousePressed( event -> {
paneMousePress( event) ;
});
imageView2.setPickOnBounds(false);
imageView1.setPickOnBounds(false);
pane.setOnMouseMoved( event -> {
paneMouseMove( event);
});
}
public void paneMouseMove( MouseEvent e) {
if( this.state.equals("DRAWLINE") && this.currentLine != null) {
makeLine( e);
}
}
public void paneMousePress( MouseEvent e) {
if( this.state.equals("DRAWLINE") && this.currentLine != null) {
endLine(e);
startLine(e);
}
}
private void startLine( MouseEvent e ){
currentLine = new Line();
currentLine.setStyle( "-fx-stroke: #a86a6a ; -fx-stroke-width: 5");
currentLine.setStartX( e.getSceneX());
currentLine.setStartY(e.getSceneY());
mouseX.set( e.getSceneX()) ;
mouseY.set( e.getSceneY());
currentLine.endXProperty().bind(mouseX);
currentLine.endYProperty().bind(mouseY);
pane.getChildren().add(currentLine);
}
private void endLine ( MouseEvent e){
currentLine.endXProperty().unbind();
currentLine.endYProperty().unbind();
currentLine.setEndX(e.getX());
currentLine.setEndY(e.getY());
currentLine = null;
}
private void makeLine( MouseEvent e){
mouseX.set(e.getX());
mouseY.set(e.getY());
}
private void imageMousePress( MouseEvent event){
if( currentLine == null){
startLine(event);
state = "DRAWLINE" ;
}else if( currentLine != null & state.equals("DRAWLINE")){
endLine( event);
}
}
}
help me please.
When dragging the end point of the line around, the end is positioned below the mouse cursor. This way the target of the mouse event is the Line, not the ImageView and since there is no event handler for the event for the Line that consumes it, the event is delivered to the parent of the Line which is the AnchorPane, not the ImageView.
To fix this set the mouseTransparent property of the Line to true:
private void startLine(MouseEvent e) {
currentLine = new Line();
currentLine.setMouseTransparent(true);
...
}
Also you should consume the events for the ImageViews to not trigger the event handler for the AnchorPane too:
imageView1.setOnMousePressed(event -> {
imageMousePress(event);
event.consume();
});
imageView2.setOnMousePressed(event -> {
imageMousePress(event);
event.consume();
});
Also note that x and y properties of the MouseEvent are relative to the coordinate system of the Node where the handler is added.
private void endLine(MouseEvent e) {
currentLine.endXProperty().unbind();
currentLine.endYProperty().unbind();
currentLine.setEndX(e.getX());
currentLine.setEndY(e.getY());
currentLine = null;
}
needs to be changed to
private void endLine(MouseEvent e) {
currentLine.endXProperty().unbind();
currentLine.endYProperty().unbind();
currentLine = null;
}
Furthermore if there are a limited number of states, I recommend using a enum instead since this way you get compile time checks for typos. Using strings for this purpose you could accidentally add bugs, e.g. if you accidentally use "DRAWLlNE" instead of "DRAWLINE" which can be hard to spot. Additionally enum constants can be compared using ==.
private enum States {
DRAWLINE
}

JavaFX mouseEvent method getSceneX() stops after holding mouse buttons

public void setListeners() {
for(Scene s : Org.scenes) {
s.setOnMouseMoved(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
mouseX = event.getSceneX();
mouseY= event.getSceneY();
}
});
}
}
this code works well, however whenever mouse1 or mouse2 is held down, the code does not work, and the mouseX and mouseY variables stay at the same value, despite the mouse being moved around. I cannot understand why holding the mouse buttons pause the updating of the variables.
When you press any button on the mouse that's no more considered as mouse move event, instead it becomes a mouse click event and if you hold the mouse button and move the cursor it will be a mouse drag event. So try adding new listeners setOnMouseClicked() or setOnMouseDragged()
s.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
//what you want to do
}
});
s.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
//what you want to do
}
});

JavaFX Spinner change is slow with click and hold of mouse button

The speed of Spinner update is slow when I click and hold the up/down arrow buttons. Is there a way to increase the change speed?
When I click, click, click with the mouse, the spinner values change as fast as I click. It also changes fast if I use the up/down arrows on the keyboard for each key press or if I hold down the up/down arrow keys. I want the values to change that fast when I click and hold on the arrow buttons.
Anyone know a way to do that?
The SpinnerBehavior of the SpinnerSkin triggers updates every 750 ms. Unfortunately there is no way to simply set/modify this behavour without using reflection to access private members. Therefore the only way to do this without reflection is using event filters to trigger the updates at a faster rate:
private static final PseudoClass PRESSED = PseudoClass.getPseudoClass("pressed");
#Override
public void start(Stage primaryStage) {
Spinner<Integer> spinner = new Spinner(Integer.MIN_VALUE, Integer.MAX_VALUE, 0);
class IncrementHandler implements EventHandler<MouseEvent> {
private Spinner spinner;
private boolean increment;
private long startTimestamp;
private static final long DELAY = 1000l * 1000L * 750L; // 0.75 sec
private Node button;
private final AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
if (now - startTimestamp >= DELAY) {
// trigger updates every frame once the initial delay is over
if (increment) {
spinner.increment();
} else {
spinner.decrement();
}
}
}
};
#Override
public void handle(MouseEvent event) {
if (event.getButton() == MouseButton.PRIMARY) {
Spinner source = (Spinner) event.getSource();
Node node = event.getPickResult().getIntersectedNode();
Boolean increment = null;
// find which kind of button was pressed and if one was pressed
while (increment == null && node != source) {
if (node.getStyleClass().contains("increment-arrow-button")) {
increment = Boolean.TRUE;
} else if (node.getStyleClass().contains("decrement-arrow-button")) {
increment = Boolean.FALSE;
} else {
node = node.getParent();
}
}
if (increment != null) {
event.consume();
source.requestFocus();
spinner = source;
this.increment = increment;
// timestamp to calculate the delay
startTimestamp = System.nanoTime();
button = node;
// update for css styling
node.pseudoClassStateChanged(PRESSED, true);
// first value update
timer.handle(startTimestamp + DELAY);
// trigger timer for more updates later
timer.start();
}
}
}
public void stop() {
timer.stop();
button.pseudoClassStateChanged(PRESSED, false);
button = null;
spinner = null;
}
}
IncrementHandler handler = new IncrementHandler();
spinner.addEventFilter(MouseEvent.MOUSE_PRESSED, handler);
spinner.addEventFilter(MouseEvent.MOUSE_RELEASED, evt -> {
if (evt.getButton() == MouseButton.PRIMARY) {
handler.stop();
}
});
Scene scene = new Scene(spinner);
primaryStage.setScene(scene);
primaryStage.show();
}
I modified the answer of fabian a little bit to decrease the speed of the spinner while holding mouse down:
private int currentFrame = 0;
private int previousFrame = 0;
#Override
public void handle(long now)
{
if (now - startTimestamp >= initialDelay)
{
// Single or holded mouse click
if (currentFrame == previousFrame || currentFrame % 10 == 0)
{
if (increment)
{
spinner.increment();
}
else
{
spinner.decrement();
}
}
}
++currentFrame;
}
And after stopping the timer we adjust previousFrame again:
public void stop()
{
previousFrame = currentFrame;
[...]
}
A small improvement to Fabian's answer. Making the following mod to the MOUSE_RELEASED addEventerFilter will stop a NullPointerException caused when clicking the textfield associated with the spinner. Cheers Fabian!
spinner.addEventFilter(MouseEvent.MOUSE_RELEASED, evt -> {
Node node = evt.getPickResult().getIntersectedNode();
if (node.getStyleClass().contains("increment-arrow-button") ||
node.getStyleClass().contains("decrement-arrow-button")) {
if (evt.getButton() == MouseButton.PRIMARY) {
handler.stop();
}
}
});
An alternative to changing the update speed might in some cases be adjusting the amount by which the value increments/decrements per update.
SpinnerValueFactory.IntegerSpinnerValueFactory intFactory =
(SpinnerValueFactory.IntegerSpinnerValueFactory) spinner.getValueFactory();
intFactory.setAmountToStepBy(100);
Reference: http://news.kynosarges.org/2016/10/28/javafx-spinner-for-numbers/

JavaFX MouseEvent not firing after moving nodes in GridPane

I am attempting to create a "draggable" histogram UI with JavaFX. I have a ScrollPane containing a GridPane with 1 column and lots of rows. In each row is an HBox containing a label. Every 10 rows, there is also an HBox containing a Line.
I tried to make the HBoxes containing lines draggable by setting onMousePressed, onMouseDragged, and onMouseReleased event handlers (shown below). It works if I drag and release an hbox-line above its starting point - it ends up in whatever grid row I put it in, and I can click and drag it again. However, if I drag and release a line below its starting point, I can't get any more mouseEvents for that hBox. I tried adding log statements everywhere, nothing. I tried setting onMouseOver, it also was not fired.
Why would moving an hbox around the grid like this work for dragging up but not down?
lineContainer.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
EventTarget target = mouseEvent.getTarget();
lastY = mouseEvent.getSceneY();
}
});
lineContainer.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
Node target = (Node) mouseEvent.getTarget();
HBox hBox = null;
if (target instanceof HBox) {
hBox = (HBox) target;
}
else if (target instanceof Line) {
hBox = (HBox) target.getParent();
}
else { //should never happen
log.info("target not hbox or line: " + target.getClass());
}
if (mouseEvent.getSceneY() <= (lastY - 15)) {
int row = GridPane.getRowIndex(hBox);
GridPane.setRowIndex(hBox, --row);
lastY = mouseEvent.getSceneY();
lastRow = row - 1;
} else if (mouseEvent.getSceneY() >= (lastY + 15)) {
int row = GridPane.getRowIndex(hBox);
GridPane.setRowIndex(hBox, ++row);
lastRow = row - 1;
lastY = mouseEvent.getSceneY();
}
}
});
lineContainer.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
Node tar = (Node) mouseEvent.getTarget();
HBox hBox = null;
if (tar instanceof HBox) {
hBox = (HBox) tar;
}
else if (tar instanceof Line && tar.getParent() instanceof HBox) {
hBox = (HBox) tar.getParent();
}
else { //should never happen
log.info(mouseEvent.getTarget().getClass().toString());
}
}
});
UPDATE: I managed to get it working by creating a new HBox, resetting the onMouse... handlers, and copying its children every time the mouse is released. But I still don't know what was causing the original issue...
The following isn't a direct solution for you, but I wanted to say I have a similar problem and share my observations.
My application allows dragging in both axes (X, Y). All I've been able to figure out that some invisible element is 'obscuring' the MouseEvent hitbox. Testing it using an 'MS minesweeper' approach, shows this interfering area to extend from coords (0,0) of the root to (maxX,maxY) of another Node I have in the scene which is a layer above.
My problem was solved by changing z-order of Parent objects (let's call them layers) containing the Nodes that didn't receive their MouseEvents.
JavaFX MouseEvent doc explains that it is only the top level node which receives the event:
https://docs.oracle.com/javafx/2/api/javafx/scene/input/MouseEvent.html
Also look at the pickOnBounds property: JavaFX: How to make a Node partially mouse transparent?

Resources