JavaFX 8 - GridPane autosizing issue - javafx-8

There is an issue with the GridPane autosizing when setting some specific span configuration.
The following configuration works as I expect:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class GridPaneWidthIssue extends Application {
public static void main(String... arguments) {
launch(arguments);
}
#Override
public void start(Stage primaryStage) throws Exception {
GridPane gridPane = new GridPane();
BorderStroke borderStroke = new BorderStroke(Color.RED,
BorderStrokeStyle.SOLID, CornerRadii.EMPTY,
BorderWidths.DEFAULT);
Border border = new Border(borderStroke);
gridPane.setBorder(border);
gridPane.setGridLinesVisible(true);
gridPane.setHgap(2);
gridPane.setVgap(2);
gridPane.setPadding(new Insets(10));
Label column0 = new Label("Column 0");
Label column1 = new Label("Column 1");
Label column2 = new Label("Column 2");
Label column01 = new Label("Span column 0 and 1");
Label column012 = new Label("Span column 0, 1 and 2");
gridPane.add(column0, 0, 0);
gridPane.add(column1, 1, 0);
gridPane.add(column2, 2, 0);
gridPane.add(column01, 0, 1, 2, 1);
gridPane.add(column012, 0, 2, 3, 1);
Scene scene = new Scene(gridPane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
I cannot post image yet to prove it, but I can verify the following assertion:
**gridpane.width = column0.width + column1.width + column2.width
When I remove the labels in the row 0, I get the following code:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class GridPaneWidthIssue extends Application {
public static void main(String... arguments) {
launch(arguments);
}
#Override
public void start(Stage primaryStage) throws Exception {
GridPane gridPane = new GridPane();
BorderStroke borderStroke = new BorderStroke(Color.RED,
BorderStrokeStyle.SOLID, CornerRadii.EMPTY,
BorderWidths.DEFAULT);
Border border = new Border(borderStroke);
gridPane.setBorder(border);
gridPane.setGridLinesVisible(true);
gridPane.setHgap(2);
gridPane.setVgap(2);
gridPane.setPadding(new Insets(10));
Label column0 = new Label("Column 0");
Label column1 = new Label("Column 1");
Label column2 = new Label("Column 2");
Label column01 = new Label("Span column 0 and 1");
Label column012 = new Label("Span column 0, 1 and 2");
// gridPane.add(column0, 0, 0);
// gridPane.add(column1, 1, 0);
// gridPane.add(column2, 2, 0);
gridPane.add(column01, 0, 1, 2, 1);
gridPane.add(column012, 0, 2, 3, 1);
Scene scene = new Scene(gridPane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
And the result is not correct:
gridpane.width > column0.width + column1.width + column2.width
I did not verify it, but I guess it is :
gridpane.width = column0.width + column1.width + column0.width + column1.width + column2.width
JavaFX issue or I misunderstood the Gridpane sizing policy ?
Case 1: OK
Case 2: KO (empty space in the GridPane)

You are seeing that extra space to the right of your GridPane because you have a colspan of 3 when adding your column012 Label and a colspan of 2 when adding the column01 Label (and you no longer have three columns but only one). Remember the GridPane is a table and you need to keep it's columns balanced and spans within it's col/row limits. In your case that empty space is simply the third column beyond the reach of any of the labels inside the GridPane.
Change this:
gridPane.add(column012, 0, 2, 3, 1);
To this:
gridPane.add(column012, 0, 2, 2, 1);
And you will see
Ideally if you are planning on having only one column you should remove that colspan and rowspan and just leave this:
gridPane.add(column01, 0, 1);
gridPane.add(column012, 0, 2);
If you keep columns balanced the width of your GridPane will be predictable since it will be computed as: insets + gaps + each column widths
You might also want to take a look at the ColumnConstraints class to customize each column behaviour to your liking.
Hope this helps!

Related

Javafx equally spanned toggle button

I need to make a group of toggle button like the following, with the white background being the selected button, and two buttons take 50% width of the parent container. Two toggle buttons are place inside HBox. The styling
So far I tried, stuck like this.
.viewType .toggle-button {
-fx-padding: 0 2 0 2;
-fx-background-color: #000;
-fx-text-fill: white;
}
.viewType .toggle-button:selected {
-fx-padding: 0;
-fx-background-color: white;
-fx-text-fill: black;
-fx-border-wdith: 2;
-fx-border-color: black;
-fx-border-radius: 4;
}
You can set the buttons maxWidth to max double in java side. This will provide your buttons to same width in HBox. Hope it is useful:
btn1.setMaxWidth(Double.MAX_VALUE);
btn2.setMaxWidth(Double.MAX_VALUE);
You can check the following link for useful information related with sizing and aligning nodes:
Sizing and Aligning Nodes
If you want both buttons to have equal width, use a GridPane instead of a HBox, and use column constraints to make the two columns equal widths:
GridPane grid = new GridPane();
grid.getColumnConstraints().addAll(createCol(), createCol());
ToggleButton toggle1 = new ToggleButton("...");
toggle1.setMaxWidth(Double.MAX_VALUE);
ToggleButton toggle2 = new ToggleButton("...");
toggle2.setMaxWidth(Double.MAX_VALUE);
grid.addRow(0, toggle1, toggle2);
// ...
private ColumnConstraints createCol() {
ColumnConstraints col = new ColumnConstraints();
col.setPercentWidth(50);
col.setFillWidth(true);
return col ;
}
You can further control how big the grid pane is in its parent by configuring the parent (details depend on what type of pane is used for the parent).
SSCCE:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class EqualSizedButtons extends Application {
#Override
public void start(Stage primaryStage) {
GridPane grid = new GridPane();
grid.getStyleClass().add("viewType");
grid.getColumnConstraints().addAll(createCol(), createCol());
ToggleButton toggle1 = new ToggleButton("A");
toggle1.setMaxWidth(Double.MAX_VALUE);
ToggleButton toggle2 = new ToggleButton("This is really big button B");
toggle2.setMaxWidth(Double.MAX_VALUE);
grid.addRow(0, toggle1, toggle2);
new ToggleGroup().getToggles().addAll(toggle1, toggle2);
BorderPane root = new BorderPane();
root.setBottom(grid);
Scene scene = new Scene(root, 600, 600);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private ColumnConstraints createCol() {
ColumnConstraints col = new ColumnConstraints();
col.setPercentWidth(50);
col.setFillWidth(true);
return col ;
}
public static void main(String[] args) {
launch(args);
}
}

How to make JavaFX gradually changing effect on a Image

In my JavaFX application I have a ImageView on the stage. I wanted to add both effects Glow and Sepia, and both should be gradually increasing (0 to 1) within duration 5 sec.
How do I do this with code?
Both effects should be applied in parallel. Would it make the animation slower?
Use a Timeline with one KeyFrame at the start and one at the end. For each KeyFrame just set the value of each effect:
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO,
new KeyValue(glow.levelProperty(), 0),
new KeyValue(sepia.levelProperty(), 0)),
new KeyFrame(Duration.seconds(5),
new KeyValue(glow.levelProperty(), 1),
new KeyValue(sepia.levelProperty(),1))
);
SSCCE:
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.effect.Glow;
import javafx.scene.effect.SepiaTone;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class AnimatedEffects extends Application {
private static final String IMAGE_URL = "http://www.nasa.gov/sites/default/files/styles/full_width/public/thumbnails/image/nh-charon-neutral-bright-release.jpg?itok=20aE3TAH";
#Override
public void start(Stage primaryStage) {
ImageView image = new ImageView(new Image(IMAGE_URL, 400, 400, true, true));
Glow glow = new Glow(0);
SepiaTone sepia = new SepiaTone(0);
sepia.setInput(glow);
image.setEffect(sepia);
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO,
new KeyValue(glow.levelProperty(), 0),
new KeyValue(sepia.levelProperty(), 0)),
new KeyFrame(Duration.seconds(5),
new KeyValue(glow.levelProperty(), 1),
new KeyValue(sepia.levelProperty(),1))
);
Button effectButton = new Button("Add effect");
effectButton.setOnAction(e -> timeline.play());
BorderPane root = new BorderPane(image, null, null, effectButton, null);
BorderPane.setAlignment(effectButton, Pos.CENTER);
BorderPane.setMargin(effectButton, new Insets(10));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Javafx 8 drawing a line between translated nodes

How do you draw a line between the centers of translated nodes? Given for example the following code snippet:
public class Test extends Application{
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
Circle circle1=new Circle(10, Color.GREEN);
root.getChildren().add(circle1);
Circle circle2=new Circle(10, Color.RED);
root.getChildren().add(circle2);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
circle1.setTranslateX(100);
Line line=new Line(circle1.getCenterX(), circle1.getCenterY(), circle2.getCenterX(), circle2.getCenterY());
root.getChildren().add(line);
}
public static void main(String[] args) {
launch(args);
}
}
Running this application will clearly show a red and a green circle. However, there won't be a line because each of the centers of the circles are at the coordinates (0,0). Nevertheless, the circles do not cover each other because one of the circles is translated. This doesn't work:
Line line=new Line(circle1.getCenterX()+circle1.getTranslateX(), circle1.getCenterY()+circle1.getTranslateY(), circle2.getCenterX()+circle2.getTranslateX(), circle2.getCenterY()+circle2.getTranslateY());
Finally, let's assume that there is an approach to draw a line connecting the centers of the two circles. If, after the line is drawn, I would invoke circle2.setTranslateX(50);, how do I ensure that the endpoint of the line on the side of circle2 moves accordingly?
A StackPane is a managed layout pane, meaning that it manages the positions of its child nodes (by default it centers them); the translation is applied after the StackPane positions the nodes. This is why the circles appear in different locations but the line is not where you expect. Using a Pane instead of a StackPane will make things work as you expect.
To keep the line in the correct position relative to the circles when the circles are repositioned dynamically, bind the startX, startY, endX, and endY properties, instead of just setting them.
import javafx.animation.Animation;
import javafx.animation.ParallelTransition;
import javafx.animation.SequentialTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import javafx.util.Duration;
public class LineConnectingCircles extends Application{
#Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Circle circle1=new Circle(10, Color.GREEN);
root.getChildren().add(circle1);
Circle circle2=new Circle(10, Color.RED);
root.getChildren().add(circle2);
// move circles so we can see them:
circle1.setTranslateX(100);
circle2.setTranslateY(50);
Line line = new Line();
// bind ends of line:
line.startXProperty().bind(circle1.centerXProperty().add(circle1.translateXProperty()));
line.startYProperty().bind(circle1.centerYProperty().add(circle1.translateYProperty()));
line.endXProperty().bind(circle2.centerXProperty().add(circle2.translateXProperty()));
line.endYProperty().bind(circle2.centerYProperty().add(circle2.translateYProperty()));
root.getChildren().add(line);
// create some animations for the circles to test the line binding:
Button button = new Button("Animate");
TranslateTransition circle1Animation = new TranslateTransition(Duration.seconds(1), circle1);
circle1Animation.setByY(150);
TranslateTransition circle2Animation = new TranslateTransition(Duration.seconds(1), circle2);
circle2Animation.setByX(150);
ParallelTransition animation = new ParallelTransition(circle1Animation, circle2Animation);
animation.setAutoReverse(true);
animation.setCycleCount(2);
button.disableProperty().bind(animation.statusProperty().isEqualTo(Animation.Status.RUNNING));
button.setOnAction(e -> animation.play());
BorderPane.setAlignment(button, Pos.CENTER);
BorderPane.setMargin(button, new Insets(10));
Scene scene = new Scene(new BorderPane(root, null, null, button, null), 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Show only default columns in table

I added into my table the option table.setTableMenuButtonVisible(true); in order to show and hide columns.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class MainApp extends Application
{
private TableView table = new TableView();
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage stage)
{
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(300);
stage.setHeight(500);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
TableColumn firstNameCol = new TableColumn("First Name");
TableColumn lastNameCol = new TableColumn("Last Name");
TableColumn emailCol = new TableColumn("Email");
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
table.setTableMenuButtonVisible(true);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
}
Can I for example show the columns First Name and Last Name by default and to hide Email?
Is there any option for this?

How to delay the Drag-Start to combine move and Drag-And-Drop dynamically

I want to provide an application that:
allows to move images around (here a rectangle)
if that object is moved out of the working area, start Drag-and-Drop for transfere to other applications
So the javafx DragDetected() would come too soon during the object move on the canvas area, I suppress the onDragDetected() handling and in the onMouseDragged() handler I tried to convert the MouseDrag event into a Drag event using
event.setDragDetect(true);
But the onDragDetected() comes never again..... what can I do?
The full sample application is:
package fx.samples;
import java.io.File;
import java.util.LinkedList;
import javax.imageio.ImageIO;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class DragRectangle extends Application {
Point2D lastXY = null;
public void start(Stage primaryStage) {
Pane mainPane = new Pane();
Scene scene = new Scene(mainPane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
Rectangle area = new Rectangle(0, 0, 500 , 500);
Rectangle rect = new Rectangle(0, 0, 30, 30);
rect.setFill(Color.RED);
mainPane.getChildren().add(rect);
rect.setOnMouseDragged(event -> {
System.out.println("Move");
Node on = (Node)event.getTarget();
if (lastXY == null) {
lastXY = new Point2D(event.getSceneX(), event.getSceneY());
}
double dx = event.getSceneX() - lastXY.getX();
double dy = event.getSceneY() - lastXY.getY();
on.setTranslateX(on.getTranslateX()+dx);
on.setTranslateY(on.getTranslateY()+dy);
lastXY = new Point2D(event.getSceneX(), event.getSceneY());
if (!area.intersects(event.getSceneX(), event.getSceneY(), 1, 1)) {
System.out.println("->Drag");
event.setDragDetect(true);
} else {
event.consume();
}
});
rect.setOnDragDetected(event -> {
System.out.println("Drag:"+event);
if (area.intersects(event.getSceneX(), event.getSceneY(), 1, 1)) { event.consume(); return; }
Node on = (Node)event.getTarget();
Dragboard db = on.startDragAndDrop(TransferMode.COPY);
db.setContent(makeClipboardContent(event, on, null));
event.consume();
});
rect.setOnMouseReleased(d -> lastXY = null);
}
public static ClipboardContent makeClipboardContent(MouseEvent event, Node child, String text) {
ClipboardContent cb = new ClipboardContent();
if (text != null) {
cb.put(DataFormat.PLAIN_TEXT, text);
}
if (!event.isShiftDown()) {
SnapshotParameters params = new SnapshotParameters();
params.setFill(Color.TRANSPARENT);
Bounds b = child.getBoundsInParent();
double f = 10;
params.setViewport(new Rectangle2D(b.getMinX()-f, b.getMinY()-f, b.getWidth()+f+f, b.getHeight()+f+f));
WritableImage image = child.snapshot(params, null);
cb.put(DataFormat.IMAGE, image);
try {
File tmpFile = File.createTempFile("snapshot", ".png");
LinkedList<File> list = new LinkedList<File>();
ImageIO.write(SwingFXUtils.fromFXImage(image, null),
"png", tmpFile);
list.add(tmpFile);
cb.put(DataFormat.FILES, list);
} catch (Exception e) {
}
}
return cb;
}
public static void main(String[] args) {
launch(args);
}
}
Ok, I spend some hours reading the JavaFX sources and playing arround with EventDispatcher etc... its finally easy:
In Short:
Suppress the system drag start proposal in the onMouseDragged() handler and set that flag on your behalf:
onMouseDragged(e -> {
e.setDragDetect(false); // clear the system proposal
if (...) e.setDragDetect(true); // trigger drag on your own decision
}
Long text:
The mechanism to start a DragDetected is consequently using the MouseEvent MOUSE_DRAGGED. The system Drag detection will apply some rules to determine if the current mouse-drag will be interpreted as a drag, here the original code:
if (dragDetected != DragDetectedState.NOT_YET) {
mouseEvent.setDragDetect(false);
return;
}
if (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED) {
pressedX = mouseEvent.getSceneX();
pressedY = mouseEvent.getSceneY();
mouseEvent.setDragDetect(false);
} else if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED) {
double deltaX = Math.abs(mouseEvent.getSceneX() - pressedX);
double deltaY = Math.abs(mouseEvent.getSceneY() - pressedY);
mouseEvent.setDragDetect(deltaX > hysteresisSizeX ||
deltaY > hysteresisSizeY);
}
}
and it set
mouseEvent.setDragDetect(true)
in the normal MOUSE_DRAG event. That event is passed down and is being processed by all 'down-chain' EventDispatchers... only if this events finally arrives for processing and if the isDragDetect flag is still true, a follow up DragDetected event will be generated.
So I am able to delay the DragDetected by clearing the isDragDetect flag on its way down using an EventDispatcher:
mainPane.setEventDispatcher((event, chain) -> {
switch (event.getEventType().getName()) {
case "MOUSE_DRAGGED":
MouseEvent drag = (MouseEvent)event;
drag.setDragDetect(false);
if (!area.intersects(drag.getSceneX(), drag.getSceneY(), 1, 1)) {
System.out.println("->Drag down");
drag.setDragDetect(true);
}
break;
}
return chain.dispatchEvent(event);
});
And if this code decides that a drag condition is reached, it simply sets the flag.
drag.setDragDetect(true);
Now I am able to move precisely my objects and start the Drag if they are moved outside the application area.
And after some minutes of thinking: the EventDispatcher is not necessary, all can be done in the onMouseDragged handler...
Full code:
package fx.samples;
import java.io.File;
import java.util.LinkedList;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
public class DragRectangle extends Application {
Point2D lastXY = null;
public void start(Stage primaryStage) {
Pane mainPane = new Pane();
Scene scene = new Scene(mainPane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
Rectangle area = new Rectangle(0, 0, 500 , 500);
Rectangle rect = new Rectangle(0, 0, 30, 30);
rect.setFill(Color.RED);
mainPane.getChildren().add(rect);
rect.setOnMouseDragged(event -> {
System.out.println("Move");
event.setDragDetect(false);
Node on = (Node)event.getTarget();
if (lastXY == null) {
lastXY = new Point2D(event.getSceneX(), event.getSceneY());
}
double dx = event.getSceneX() - lastXY.getX();
double dy = event.getSceneY() - lastXY.getY();
on.setTranslateX(on.getTranslateX()+dx);
on.setTranslateY(on.getTranslateY()+dy);
lastXY = new Point2D(event.getSceneX(), event.getSceneY());
if (!area.intersects(event.getSceneX(), event.getSceneY(), 1, 1)) event.setDragDetect(true);
event.consume();
});
rect.setOnDragDetected(event -> {
System.out.println("Drag:"+event);
Node on = (Node)event.getTarget();
Dragboard db = on.startDragAndDrop(TransferMode.COPY);
db.setContent(makeClipboardContent(event, on, "red rectangle"));
event.consume();
});
rect.setOnMouseReleased(d -> lastXY = null);
}
public static ClipboardContent makeClipboardContent(MouseEvent event, Node child, String text) {
ClipboardContent cb = new ClipboardContent();
if (text != null) {
cb.put(DataFormat.PLAIN_TEXT, text);
}
if (!event.isShiftDown()) {
SnapshotParameters params = new SnapshotParameters();
params.setFill(Color.TRANSPARENT);
Bounds b = child.getBoundsInParent();
double f = 10;
params.setViewport(new Rectangle2D(b.getMinX()-f, b.getMinY()-f, b.getWidth()+f+f, b.getHeight()+f+f));
WritableImage image = child.snapshot(params, null);
cb.put(DataFormat.IMAGE, image);
try {
File tmpFile = File.createTempFile("snapshot", ".png");
LinkedList<File> list = new LinkedList<File>();
ImageIO.write(SwingFXUtils.fromFXImage(image, null),
"png", tmpFile);
list.add(tmpFile);
cb.put(DataFormat.FILES, list);
} catch (Exception e) {
}
}
return cb;
}
public static void main(String[] args) {
launch(args);
}
}
Based on this:
The default drag detection mechanism uses mouse movements with a pressed button in combination with hysteresis. This behavior can be augmented by the application. Each MOUSE_PRESSED and MOUSE_DRAGGED event has a dragDetect flag that determines whether a drag gesture has been detected. The default value of this flag depends on the default detection mechanism and can be modified by calling setDragDetect() inside of an event handler. When processing of one of these events ends with the dragDetect flag set to true, a DRAG_DETECTED MouseEvent is sent to the potential gesture source (the object on which a mouse button has been pressed). This event notifies about the gesture detection.
your assumption that just enabling setDragDetect(true) at some point after the mousedragged has started will fire the drag event, does not work.
The reason for that is there is some private DragDetectedState flag, that is used to set the drag state at the beginning of the gesture. And once it's already selected, it can't be changed.
So if the event it's not triggered, what you could do is manually trigger it yourself:
if (!area.intersects(event.getSceneX(), event.getSceneY(), 1, 1)) {
System.out.println("->Drag");
event.setDragDetect(true);
Event.fireEvent(rect, new MouseEvent(MouseEvent.DRAG_DETECTED, 0, 0, 0, 0, MouseButton.PRIMARY, 0, false, false, false, false, true, false, false, true, true, true, null));
}
This would effectively trigger a drag dectected event, and rect.setOnDragDetected() will be called.
But this is what you will get:
Exception in thread "JavaFX Application Thread" java.lang.IllegalStateException:
Cannot start drag and drop outside of DRAG_DETECTED event handler
at javafx.scene.Scene.startDragAndDrop(Scene.java:5731)
at javafx.scene.Node.startDragAndDrop(Node.java:2187)
Basically, you can't combine DragDetected with MouseDragged, since the startDragAndDrop method has to be called within a Drag_Detected event:
Confirms a potential drag and drop gesture that is recognized over this Node. Can be called only from a DRAG_DETECTED event handler.
So instead of trying to combine two different events, my suggestion is you just use drag detected event, allowing your scene or parts of it to accept the drop, and translating your node there, while at the same time, if the drag&drop leaves the scene, you could drop the file on the new target.
Something like this:
#Override
public void start(Stage primaryStage) {
Pane mainPane = new Pane();
Rectangle rect = new Rectangle(0, 0, 30, 30);
rect.setFill(Color.RED);
mainPane.getChildren().add(rect);
Scene scene = new Scene(mainPane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
rect.setOnDragDetected(event -> {
Node on = (Node)event.getSource();
Dragboard db = on.startDragAndDrop(TransferMode.ANY);
db.setContent(makeClipboardContent(event, on, null));
event.consume();
});
mainPane.setOnDragOver(e->{
e.acceptTransferModes(TransferMode.ANY);
});
mainPane.setOnDragExited(e->{
rect.setLayoutX(e.getSceneX()-rect.getWidth()/2d);
rect.setLayoutY(e.getSceneY()-rect.getHeight()/2d);
});
}