How to execute code during a drag and drop operation? - java

I have a node I want to implement drag and drop for (this object is the source not the target). I also want the object to move along with the mouse cursor. I managed to do both of these but not at the same time.
It appears that setOnDragDetected and setOnMouseDragged don't work well together. Consider a node with the following handlers:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Example extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Rectangle rect = new Rectangle(20, 20);
rect.setOnMousePressed(e -> System.out.println("Pressed"));
rect.setOnMouseDragged(e -> System.out.println("Dragged"));
rect.setOnDragDetected(e -> {
System.out.println("Detected");
ClipboardContent content = new ClipboardContent();
content.putString("something");
Dragboard db = rect.startDragAndDrop(TransferMode.ANY);
db.setContent(content);
});
Group subGroup = new Group(rect);
Scene scene = new Scene(subGroup, 100, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Example.launch(args);
}
}
Now press the mouse on the node and move the mouse. This is the output:
Pressed
Dragged
Dragged
Dragged
Dragged
Dragged
Dragged
Detected
Once the drag is detected the MouseDragged handler stops.
How do I achieve what I described? One thing i noticed was maybe that I can use a onDragOver for the parent but i want the behavior to be in the node because that's where it really should be.

You are mixing two things up here. In short, when you call the startDragAndDrop method the system switches to drag and drop mode and Java stops delivering MouseEvent to rect.
The MouseEvent documentation has a section "Dragging gestures" which explains the three types of dragging gestures. Here is just a short summary:
simple press-drag-release - when a drag is detected Java continues to deliver MouseEvents to the node where the drag was detected.
full press-drag-release - you can call startFullDrag inside the handler you set with setOnDragDetected. Then Java also starts to deliver MouseDragEvents to other nodes (potential gesture targets).
platform-supported drag-and-drop - if you call startDragAndDrop inside the OnDragDetected handler, Java will stop to deliver MouseEvents and start to deliver DragEvents instead. This is used for drag and drop interaction with other applications.
It is not clear to me what you want to achieve, but as long as you do not want to drag something outside of your application, try using startFullDrag instead.
Also, it might be helpful to have a further look at the DragEvent and MouseDragEvent documentation.

Related

OpenJFX 14 TextField NodeOrientation RIGHT_TO_LEFT Cannot Move Cursor Via Keyboard on Windows 10

I am on a Windows 10 machine with Oracle JDK 14 and OpenJFX 14 installed. When keyboard focus is in a TextField control and that control's orientation is right-to-left, pressing the left and right arrow keys on the keyboard does not move the cursor within the TextField. Here is a simple program that demonstrates the problem.
import javafx.application.Application;
import javafx.geometry.NodeOrientation;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
public class FxTxtFld extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
TextField txtFld = new TextField("sampler");
txtFld.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
Group root = new Group(txtFld);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Run the above code and then simply try to move the cursor by pressing the arrow keys on the keyboard. When I do it, the cursor does not move. I can move the cursor by clicking the mouse within the text contents of the TextField. Also the up-arrow key moves the cursor to before the letter s, i.e. the start of the text and the down-arrow key moves the cursor to after the letter r but pressing the left- and right-arrow keys does not move the cursor at all. Note that if the orientation is left-to-right the arrow keys do move the cursor.
When I print out the Java [System] properties, I see the following.
user.language en
user.country US
I Googled for the terms javafx textfield cursor does not move but I only saw results that talk about manipulating the cursor via code and not via user actions.
EDIT
Perhaps someone can verify whether this problem also exists in earlier JavaFX versions?
UPDATE
For anyone interested, the issue appears to be resolved in JavaFX 15 Windows x64 SDK

Why does instantiating a JavaFX scene control corrupt the layout

I'm trying to create a simple data entrty application with javafx and ran into a problem when adding a scene control. The display loses it's fill colour even BEFORE i've added the control to the scene! Simply instantiating the control breaks it.
I was running on Oracle java 8 on windows, but I;ve tried OpenJDK 8 on Windows and OpenJDk/OpenJFX 13 on linux and all behave identically. I stripped out the code to the bear minmium to recreate the problem.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
public class BasicTest extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle r = new Rectangle();
r.setWidth(200);
r.setHeight(50);
r.setFill(Color.BLUE);
r.setStroke(Color.WHITE);
r.setStrokeWidth(2);
Text t = new Text();
t.setText("Confirm");
t.setFill(Color.WHITE);
t.setFont(Font.font("null", 40));
StackPane sp = new StackPane(r);
sp.getChildren().add(t);
sp.setMaxWidth(200);
t.setTranslateY(-2);
Label b = new Label("Click me");//Comment this line out after first run
Scene scene = new Scene(sp, 300, 200);
scene.setFill(Color.BLUE);
primaryStage.setScene(scene);
primaryStage.show();
}
}
With the label commented out the scene background is blue so I get a white "Confirm" with white outline. Just adding the label constructor will make the scene background go grey.
Thanks for all the comments. Many apologies if my wording was confusing. I did add some images to show the difference but they seem to have disappeared. Anyway my thanks to #Matt for their very succinct description (which I wish I'd have thought of!). I like the mouse click idea too.
I was probably being very naughty asking this "question" on SO when I firmly believe this is a bug in javafx. Instantiating an object that goes no where near (yet) the scene graph should have no effect on it in my opinion - CSS or no CSS. I will raise a bug against javafx.
However I knew the power of SO would help me and it has!
Thanks to the clue by #jewelsea I simply replaced my scene.setFill() (which was only a test anyway) with CSS and the problem is circumvented. I can even add the control to the scene now and it works as expected. From the JavaFX CSS Reference:
"The Scene object has no settable CSS properties, nor does it have any pseudo-classes. However, the root node of the scene is assigned the style class "root" (in addition to style classes already assigned to the node). "
So I set up in my css:
.root {
-fx-background-color: blue;
}
Another way to circumvent this is to simply set the background of the StackPane to BLUE:
sp.setBackground(new Background(new BackgroundFill(Color.BLUE, null, null)));

JavaFX - Music On/Off Toggle Button (Not Working)

Below is the code sample, with other features left out. The code below encompasses the media player only. It's used on a menu screen for a project I'm working on. My issue is getting the musicButton (which is a toggle button- On/Off) to work properly. Using the following code, when I interact with the music toggle button, the playing music stops. When I click it again to resume playing, it does not resume. It stops once and stops altogether.
You can see I've tried simply using the boolean values of the toggle button in two if statements... If it's off and pressed, pause the music. If its on and pressed, resume the music. The problem is, as stated earlier, pausing the music works but it cannot be resumed. I've tried some combinations with loops, but nothing worked either.
I think if statements are too simple for this. I've scoured the JavaDocs and various online articles but I cannot find anything definitive. I've read a little about listeners, but they seem overly-complex for an on/off switch.
My question:
How do I get the musicButton to pause/play the music, whenver the user clicks it?
Any help is appreciated, thanks.
-Bagger
/* A simple game, the mechanics not yet implemented.
This is simply working on the title screen. */
import java.io.File;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.VBox;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.stage.Stage;
public class MenuFX extends Application {
#Override
public void start (Stage primaryStage) {
// Make the window a set size...
primaryStage.setResizable(false);
// Create media player
// Rather than inputting the entire absolute URI, which would confine the program
// to the creator's device, we create a new file, grab the URI on whatever machine
// the program is running on and convert it to a string... portability.
Media menuMusic = new Media(new File("music/menu.mp3").toURI().toString());
MediaPlayer menuPlayer = new MediaPlayer(menuMusic);
// Want to see the absolute URI? Uncomment the next line
//System.out.println(new File("music/menu.mp3").toURI().toString());
// Adjust the cycles and volume then start playing menu music
// Lazy, but it will suffice
menuPlayer.setCycleCount(999999999);
menuPlayer.setVolume(0.1);
menuPlayer.setAutoPlay(true);
/*
Need assistance here
*/
// Create music toggle button
ToggleButton musicButton = new ToggleButton("Music On/Off");
if (musicButton.isSelected() == false) {
musicButton.setOnAction(e -> menuPlayer.pause());
}
if (musicButton.isSelected() == true) {
musicButton.setOnAction(e -> menuPlayer.play());
}
// Add all nodes to the vbox pane and center it all
// Must be in order from top to bottom
menuVBox.getChildren().add(musicButton);
menuVBox.setAlignment(Pos.CENTER);
// New scene, place pane in it
Scene scene = new Scene(menuVBox, 630, 730);
// Place scene in stage
primaryStage.setTitle("-tiles-");
primaryStage.setScene(scene);
primaryStage.show();
}
// Needed to run JavaFX w/o the use of the command line
public static void main(String[] args) {
launch(args);
}
}
I think you are over thinking it. You should have an EventListener on your ToggleButton to Pause and Play the music.
musicButton.setOnAction(event -> {
if (musicButton.isSelected()) {
menuPlayer.pause();
}else {
menuPlayer.play();
}
});
This should give you the desired effect.
The reason your code was not working is because the ToggleButton is not selected by default, so the only EventListener that gets associated with it is the menuPlayer.pause();. So when you click on it, it only ever pauses. I have moved your code into one EventListener, and used the approriate if-else.

Saving shapes to file drawn on border pane in javafx

I have a basic application, sort of like a toy box, it doesn't really do anything except let you move shapes around a screen, if you double click the shape, it brings it into the foreground. There is also a menu with menu Items "New, "open", "Save" -
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class RandCMove extends Application
{
public static void main(String [] args)
{
launch(args);
}
public void start(Stage primaryStage)
{
BorderPane root = new BorderPane();
MenuBar menuBar = new MenuBar();
menuBar.prefWidthProperty().bind(primaryStage.widthProperty());
Menu fileMenu = new Menu("File");
MenuItem newMenuItem = new MenuItem("New");
MenuItem openMenuItem = new MenuItem("Open");
MenuItem saveMenuItem = new MenuItem("Save");
MenuItem exitMenuItem = new MenuItem("Exit");
fileMenu.getItems().addAll(newMenuItem,openMenuItem,saveMenuItem,
new SeparatorMenuItem(),exitMenuItem);
menuBar.getMenus().add(fileMenu);
Rectangle rect = new Rectangle();
rect.setWidth(200);
rect.setHeight(200);
rect.setArcHeight(20);
rect.setArcWidth(20);
rect.setFill(Color.RED);
rect.setX(200);
rect.setY(100);
root.setTop(menuBar);
root.getChildren().add(rect);
Circle circle = new Circle(
300,300,100);
Text text = new Text(150,150,"Text");
Font phosphate = Font.font("Phosphate",150);
text.setFont(phosphate);
text.setTranslateY(circle.getBoundsInParent().getMinY()+10);
root.getChildren().add(text);
//Positions the circle under the rectangle
circle.setTranslateY(rect.getBoundsInParent().getMinY()+30);
root.getChildren().add(circle);
// Moves shapes depending on if the cursor on the particular shape
// Brings shape to the front using double click
root.setOnMouseMoved(e ->
{
if(rect.contains(e.getX(),e.getY()))
rect.setOnMouseDragged(f ->{
rect.setX(f.getX());
rect.setY(f.getY());
});
rect.setOnMouseClicked(f ->{
if(f.getClickCount() >= 2)
rect.toFront();
});
if(circle.contains(e.getX(),e.getY()))
circle.setOnMouseDragged(f->{
circle.setCenterX(f.getX());
circle.setCenterY(f.getY());
});
circle.setOnMouseClicked(f ->{
if(f.getClickCount() >= 2)
circle.toFront();
});
if(text.contains(e.getX(),e.getY()))
text.setOnMouseDragged(f ->{
text.setX(f.getX());
text.setY(f.getY());
});
text.setOnMouseClicked(f ->{
if(f.getClickCount() >= 2)
text.toFront();
});
});
Scene scene = new Scene(root,600,600);
primaryStage.setScene(scene);
primaryStage.show();
}
}
I want to be able to save the current state of the shapes on screen, and have them load up as they were upon pressing "Open".
I can't seem to find anything useful on this, can anyone provide some guidance or point me in the right direction? Any assistance would be appreciated!
Applications require preference and configuration data to adapt to the needs of different users and environments. The java.util.prefs package provides a way for applications to store and retrieve user and system preference and configuration data. The data is stored persistently in an implementation-dependent backing store. There are two separate trees of preference nodes, one for user preferences and one for system preferences.
More at:
http://docs.oracle.com/javase/8/docs/technotes/guides/preferences/
This is a non-trivial task. I am not going to provide code for it here, but only a general approach outline:
Serialize your shapes to FXML (you will need to code the serializer yourself).
Each time the user edits the shapes, serialize the to FXML again and save the FXML as a preference (as outlined in Frank Fotangs answer).
When the user first opens the application, read the current preference containing the FXML for the current shapes and load them up using an FXMLLoader.
The same technique can be used for implementing your Open and Save menu options, only instead of writing the serialized data to a preference you will write it to a file location chosen by a FileChooser.
I would really prefer to do this the FX way rather than Fxml.
I think you misunderstand the problem that you need to solve. There is no "FX way" of doing this.
Your problem is that you need to serialize and deserialize nodes from a scene graph, for which the core JavaFX API does not provide a full implementation.
When you create a scene graph using JavaFX using JavaFX APIs, it is an in-memory representation of your scene, only available while your program is executing. What you need to be able to do is persist that scene to a file so that when you shut down your application the data representing the scene is stored somewhere. Then you need to read the data from the file and recreate the scene in memory when the user starts the application back up again. The only way to do this is to serialize the objects to storage when needed and deserialize the objects from storage when the application starts back up again. Read the wikipedia article on serialization to understand the conceptual process.
Now you don't need to use FXML for your serialized format, you could use other formats such as JSON or YAML and other serialization support libraries such as Jackson. However, to me, FXML seems the most logical format to choose because JavaFX ships with the FXMLLoader, which provides an easy to use API to transfer your serialized data (an FXML file representing your scene) into an in-memory structure (the scene graph), via a simple call to the load method.
What makes this task tricky rather than trivial is that the JavaFX API only ships with an FXMLLoader, it does not ship with a corresponding class (e.g. an FXMLSaver), which does the opposite work of taking the in-memory structure (the scene graph) and creating serialized data from it (an FXML file). So you will need to write such logic yourself.
Now, SceneBuilder is an open source project and does include logic to save a scene graph to an FXML file. For instance you can find source code for an FXMLSaver in the SceneBuilder source. Perhaps you could use or adapt that code to save your scene graph objects into a serialized format. In doing so, you wouldn't be using the scene builder application to create and save your scene, instead you would be adapting SceneBuilder source code to be included in your own application which would be creating and saving your scene.

JavaFX transparent window only receives mouse events over drawn pixels

I'd like a Stage that is the same size as the screen which is fully transparent and receives mouse events anywhere. In the example below I get mouse events only when the mouse is over the circle. I see this issue on both Windows XP and Windows 7 using Java 8u11
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class TransparentTest extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage ignored) throws Exception {
Stage stage = new Stage(StageStyle.TRANSPARENT);
stage.setTitle("Transparent app test");
Rectangle2D screenBounds = Screen.getPrimary().getBounds();
stage.setX(0);
stage.setY(0);
stage.setWidth(screenBounds.getWidth());
stage.setHeight(screenBounds.getHeight());
Circle circle = new Circle(100);
circle.setFill(Color.RED);
Rectangle rectangle = new Rectangle(screenBounds.getWidth(),
screenBounds.getHeight());
rectangle.setFill(Color.TRANSPARENT);
Scene scene = new Scene(new StackPane(circle, rectangle));
scene.setFill(null);
stage.setScene(scene);
scene.setOnMouseMoved((e) -> {
System.out.println("Mouse over rectangle " + e);
});
stage.show();
}
}
Interestingly if I set the alpha part of the fill color to its absolute minimum then I get mouse events. However I'd prefer not to use this workaround and actually get to the bottom of the issue. My conclusion is somewhere in JavaFX or a Windows library there is some hit-detection code that filters mouse events based on the pixel value of the mouse event.
rectangle.setFill(Color.rgb(0, 0, 0, 1d / 255d)); // receives mouse events
rectangle.setFill(Color.rgb(0, 0, 0, 0)); // does not receive mouse events
Research
JavaFx Transparent window - yes please. Mouse transparent - no thanks describes a similar problem, however it does not address the issue of mouse events in completely transparent areas
Debugging - using a breakpoint in the setOnMouseMoved() I've examined the preceding stackframes to try to find the hit-detection code.
Used JNA to test different styles such as WS_EX_TRANSPARENT and WS_EX_LAYERED. Interestingly WS_EX_TRANSPARENT made the window fully mouse transparent - no mouse events over the painted pixels.
Tried putting the mouse listener on the rectangle/StackPane instead - no difference
MSDN article Layered Windows hints at this functionality being part of Windows rather than JavaFX. If this is true is there any workaround?
Hit testing of a layered window is based on the shape and transparency
of the window. This means that the areas of the window that are
color-keyed or whose alpha value is zero will let the mouse messages
through. If the layered window has the WS_EX_TRANSPARENT extended
window style, the shape of the layered window will be ignored and the
mouse events will be passed to the other windows underneath the
layered window.
In summary only known solution is to set the background to be "not quite" transparent to fool JavaFX into sending events.
rectangle.setFill(Color.rgb(0, 0, 0, 1d / 255d)); // receives mouse events

Categories

Resources