Moving an image using mouse drag events - javafx

I am currently making a game in javafx, and what i need is to be able to make an image move left and right using mouse drag events, while the image is moving down. I have figured the latter part(the moving down part), and require assistance in adding the drag event to my image view object, so that on dragging it to the right is one event and dragging it to left is another event.
Thank you in advance.
This is my fxml file
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
<children>
<ImageView fx:id="player" fitHeight="42.0" fitWidth="98.0" layoutX="47.0" layoutY="27.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="#freefall.png" />
</image>
</ImageView>
<Button fx:id="button" layoutX="44.0" layoutY="21.0" mnemonicParsing="false" prefHeight="54.0" prefWidth="81.0" style="-fx-background-color: transparent; -fx-text-fill: transparent; -fx-border-fill: transparent;" text="Button" />
</children>
</AnchorPane>
This is my Controller Class
package application;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.animation.TranslateTransition;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.image.ImageView;
import javafx.util.Duration;
public class Controller implements Initializable {
#FXML
ImageView player;
TranslateTransition transition = new TranslateTransition();
private double startDragX;
private double startDragY;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
player.setOnMousePressed(e -> {
startDragX = e.getSceneX();
startDragY = e.getSceneY();
});
player.setOnMouseDragged(e -> {
double X = e.getSceneX();
if(X - startDragX > 0)
player.setTranslateX(5);
else if (X - startDragX < 0)
player.setTranslateX(-5);
});
//This code makes the player move downwards continuously
transition.setDuration(Duration.seconds(15));
transition.setNode(player);
transition.setToY(800);
transition.play();
}
}
The problem i am facing after changing the code is that the player moves, when you drag it once to the right. But when u again drag it to the right nothing happens, all I can do is drag it back to its original position (by dragging it back to the left).

I think you already have the basics in your example.
There is something that I don't understand though: why is there a transition? If you want the ImageView to follow your mouse cursor as you drag, then it would be weird to mix transition in (i.e. it would appear like a lag to users).
This is the basic way to move your ImageView with drag event.
public class Controller implements Initializable {
#FXML
ImageView player;
private double startDragX;
private double startDragY;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
button.setMousePressed(e -> {
startDragX = e.getSceneX();
startDragY = e.getSceneY();
});
button.setMouseDragged(e -> {
button.setTranslateX(e.getSceneX() - startDragX);
button.setTranslateY(e.getSceneY() - startDragY);
});
}
}
Of course, you can choose to add the events in FXML and inject the event methods with #FXML annotation.
Edit
After having understood the question, I think this is what you want (or at least almost). I assumed that you want to achieve an effect similar to "flick" on a touch mobile device, except that this is done with mouse.
public class Controller implements Initializable {
#FXML
ImageView player;
private static final double MIN_FLICK_PIXELS = 10;
private static final double FLICK_MOVEMENT = 5;
private enum Direction {
LEFT, RIGHT
}
private double lastXPosition;
private Direction lastFlickDirection = null;
TranslateTransition transition = new TranslateTransition();
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
player.setOnMousePressed(e -> {
lastXPosition = e.getSceneX();
});
player.setOnMouseDragged(e -> {
double currentX = e.getSceneX();
// Detect as "right flick" if the previous flick direction is not right, and the dragged pixels is more than 10
if (lastFlickDirection != Direction.RIGHT && currentX - lastXPosition > MIN_FLICK_PIXELS) {
direction = Direction.RIGHT;
player.setTranslateX(player.getTranslateX() + FLICK_MOVEMENT);
lastXPosition = currentX;
}
// Detect as "left flick" if the previous flick direction is not left, and the dragged pixels is more than -10
else if (lastFlickDirection != Direction.LEFT && currentX - lastXPosition < -MIN_FLICK_PIXELS) {
direction = Direction.LEFT;
player.setTranslateX(player.getTranslateX() -FLICK_MOVEMENT);
lastXPosition = currentX;
}
});
player.setOnMouseReleased(e -> {
lastFlickDirection = null;
});
//This code makes the player move downwards continuously
transition.setDuration(Duration.seconds(15));
transition.setNode(player);
transition.setToY(800);
transition.play();
}
}
Doing this will:
Shift the image 5 pixels to the right or left when user drag at least 10 pixels to the corresponding direction.
If the user drags 20 pixels continuously to the same direction, it only shift once.
If the user, within a single action, dragged 10 pixels to the right, then drag 10 pixels back to the left, the image will shift 5 pixels right, then 5 pixels left (which is original position).
One possible obvious flaw here is that the events are set on the ImageView, so the events may be lost if the cursor goes outside the bounds of the ImageView (it might not be lost, I didn't test this). If a problem occurs, shift the events out to perhaps its parent.

Related

Add a node into a split pane divider

I'm trying to find a solution of how to add a label into a split pane divider or at least create the illusion of that. My approach was to add three panes into the split pane and drag the middle pane along with the two divider. The problem is that on rapid mouse movements the middle pane gets bigger, but if I define a max height then the dragging doesn't work at all.
My question is if there is a way of directly inserting a label onto the divider or if somebody has a better solution as mine to accomplish something like this.
MWE:
Class
public class MainSplit extends Application {
#FXML
private SplitPane splitPane;
#FXML
private Pane dividerPane;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/splitpane.fxml"));
loader.setController(this);
Parent root = loader.load();
AtomicReference<Double> start = new AtomicReference<>((double) 0);
dividerPane.setOnMousePressed(event -> {
start.set((1.0 / splitPane.getScene().getHeight()) * event.getSceneY());
});
dividerPane.setOnMouseDragged(event -> {
double p = (1.0 / splitPane.getScene().getHeight()) * event.getSceneY();
double diff = p - start.get();
start.set(p);
double[] d = splitPane.getDividerPositions();
splitPane.setDividerPosition(0, d[0] + diff);
splitPane.setDividerPosition(1, d[1] + diff);
});
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
FXML
<SplitPane fx:id="splitPane" dividerPositions="0.36, 0.43" orientation="VERTICAL" prefHeight="400.0" prefWidth="400.0"
xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
<AnchorPane/>
<Pane fx:id="dividerPane" minHeight="-Infinity" prefHeight="20.0" style="-fx-background-color: #4545;">
<cursor>
<Cursor fx:constant="V_RESIZE"/>
</cursor>
</Pane>
<AnchorPane/>
</SplitPane>

Javafx8 stackpane children blocking mouse events

I have a javafx8 app with a complex scene. It consists of a splitpane where, in the top segment I have a stackpane with multitple levels (children). The rear most is a background that would only need redraws on resizing, another with gridlines, lat/long. Another for drawing and, finally, another with a sliding composite control. When the composite (buttons, labels, comboboxes, and textfields, in the last child of the stackframe, it properly intercepted and acted on mouse and keyboard events. However, with it on top no events were getting propagated to the child panes below. I switched the order and now the buttons and axis control work but nothing now gets propagated to the composite control below it. Buttons fail to see the mouse action.
This seems similar to Stackpane - MouseClick to be listened by both its children but it's not real clear that it is nor is it clear that has a correct answer.
I simplified my scenario with a very simple test, source to follow. In this I have a StackPane with two children, both VBox, one if which with top left alignment, the other with top right. Only the second button, top right responds to being pressed.
Why. The reason I have my app with multiple panes is performance. Both drawing actions are expensive and freq and I didn't want to have to constantly redraw static or near static layouts.
fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<StackPane fx:id="containerPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="mouseeventdemo.MouseEventDemoController">
<children>
<VBox fx:id="container1" prefHeight="200.0" prefWidth="100.0">
<children>
<Button fx:id="leftButton" mnemonicParsing="false" onAction="#leftButtonDown" text="left button" />
</children>
</VBox>
<VBox fx:id="container2" alignment="TOP_RIGHT" prefHeight="200.0" prefWidth="100.0">
<children>
<Button fx:id="rightButton" mnemonicParsing="false" onAction="#rightButtonDown" text="Right Button" />
</children>
</VBox>
</children>
</StackPane>
Controller:
package mouseeventdemo;
/**
* Sample Skeleton for 'FXMLDocument.fxml' Controller Class
*/
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
public class MouseEventDemoController {
#FXML // ResourceBundle that was given to the FXMLLoader
private ResourceBundle resources;
#FXML // URL location of the FXML file that was given to the FXMLLoader
private URL location;
#FXML // fx:id="containerPane"
private StackPane containerPane; // Value injected by FXMLLoader
#FXML // fx:id="container1"
private VBox container1; // Value injected by FXMLLoader
#FXML // fx:id="leftButton"
private Button leftButton; // Value injected by FXMLLoader
#FXML // fx:id="container2"
private VBox container2; // Value injected by FXMLLoader
#FXML // fx:id="rightButton"
private Button rightButton; // Value injected by FXMLLoader
#FXML
void leftButtonDown(ActionEvent event) {
System.out.println("leftButtonPressed");
}
#FXML
void rightButtonDown(ActionEvent event) {
System.out.println("rightButtonPressed");
}
#FXML // This method is called by the FXMLLoader when initialization is complete
void initialize() {
assert containerPane != null : "fx:id=\"containerPane\" was not injected: check your FXML file 'FXMLDocument.fxml'.";
assert container1 != null : "fx:id=\"container1\" was not injected: check your FXML file 'FXMLDocument.fxml'.";
assert leftButton != null : "fx:id=\"leftButton\" was not injected: check your FXML file 'FXMLDocument.fxml'.";
assert container2 != null : "fx:id=\"container2\" was not injected: check your FXML file 'FXMLDocument.fxml'.";
assert rightButton != null : "fx:id=\"rightButton\" was not injected: check your FXML file 'FXMLDocument.fxml'.";
}
}
main
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mouseeventdemo;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
/**
*
* #author walt
*/
public class MouseEventDemo extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
In my app the rear most pane is a background canvas. It need be drawn but once and redrawn when the window is resized.
The next is a canvas that had lat/long grids. It need only be redrawn when the window size changes or either the vertical or horizontal scales change.
The next is the main drawing canvas in the following
<vbox>
<hbox>
drawing canvas
vertical scale
</hbox>
horizontal scale
</vbox>
The next carries one or more flagpoles where the vertical pole is a slide rule like reticle. These need to be dragable. If this layer is on top it slides perfectly but the two scales fail to get focus. If the top two panes are reversed, the scales work perfectly but the flagpole objects never receive events.
The two button example duplicates very simply what I see.
Thanks!
In the eventhandling of javafx only the topmost node (in your example container2) under the mouseposition will be determined as the target for the mouse click event, that's why container1 doesn't receive the event.
The target node is the end node in the event dispatch chain. When you click on leftButton, the mouse clicked event travels from the root node of your scene graph to the target node(container2), and back if it doesn't get consumed. Container1 as not being on the dispatch chain, won't receive the event:
http://docs.oracle.com/javafx/2/events/processing.htm
You can try to use container.setPickOnBounds(false)

JavaFX: how to create slide in animation effect for a pane (inside a transparent stage)

I would like some guidelines on how to implement a slide in transition for a pane when user presses a button, just like Material Design does it for sliding menus.
This is a video link that illustrates my need.
I tried ScaleTransition, TranslateTransition, but they didn't do the trick.
The way I'm trying to implement it is not efficient.
// swipeMenuPane is builded in SceneBuilder and it is hidden,
// opacity = 0.0 and setX() = -getPrefWidth();
#FXML AnchorPane swipeMenuPane;
#FXML Button menuButton;
menuButton.setOnMouseClicked(e-> {
swipeMenuPane.setOpacity(1.0);
swipeTransition.play()
});
TranslateTransition swipeTransition = new TranslateTransition();
swipeTransition.setNode(swipeMenuPane);
swipeTransition.setDuration(Duration.millis(500));
swipeTransition.setToX(swipeMenuPane.getPrefWidth());
--- UPDATE ---
Here is the sample Gluon Application downloaded from here. It's a gradle project and I modified it to display a button instead of the default label.
I want to shrink the AnchorPane when user clicks the button.
What am I missing?
package com.helloworld;
import com.gluonhq.charm.glisten.animation.ShrinkExpandAnimation;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application {
ShrinkExpandAnimation anim;
#Override
public void start(Stage stage) {
Button btn = new Button("Click Me!");
btn.setOnMouseClicked(e-> {
System.out.println("swiping...");
anim.play();
});
AnchorPane pane = new AnchorPane();
pane.setStyle("-fx-background-color: coral");
pane.getChildren().add(btn);
// false to shrink or true to expand
anim = new ShrinkExpandAnimation(pane, false);
Scene scene = new Scene(new StackPane(pane), 640, 480);
stage.setScene(scene);
stage.show();
}
}
--- UPDATE 2 ---
I managed to implement something similar to what I want using native JavaFX API and no external libraries.
Although, I ran into some issues.
Shrinking an AnchorPane does NOT shrink/move ANY of its children nodes, because they stay in their layout positions.
Shrinking any other Pane except AnchorPane DOES shrink/move its children nodes except from ImageView nodes.
The next two images illustrate the 1st issue I ran into.
This is an AnchorPane (with coral color at its full width; expanded) inside an AnchorPane (root pane with grey color).
And this is what happens when you click Menu button to shrink/hide it. As you can see the coral-colored pane got shrinked/hidden, but NOT its nodes (Label, ImageView)
I post the whole code to reproduce the issue yourself:
public class SwipeMenuDemo extends Application {
AnchorPane swapPane;
Button btnMenu;
boolean isExpanded = true;
#Override
public void start(Stage stage) {
Label swapPaneLabel = new Label("Expandable Pane");
swapPaneLabel.setMinWidth(0);
ImageView swapPaneImage = new ImageView("http://vignette1.wikia.nocookie.net/jfx/images/5/5a/JavaFXIsland600x300.png");
swapPaneImage.setLayoutY(100);
Label rootPaneLabel = new Label("Root Pane");
rootPaneLabel.setStyle("-fx-font-size: 60;");
rootPaneLabel.setLayoutX(180);
rootPaneLabel.setLayoutY(180);
swapPane = new AnchorPane();
swapPane.setPrefSize(640, 440);
swapPane.setMinWidth(0);
swapPane.setLayoutY(40);
swapPane.setStyle("-fx-background-color: coral; -fx-font-size: 52;");
swapPane.getChildren().addAll(swapPaneImage, swapPaneLabel);
btnMenu = new Button("Menu");
btnMenu.setLayoutX(5);
btnMenu.setLayoutY(5);
btnMenu.setOnMouseClicked(e -> {
if (isExpanded) hideSwapPane().play();
else showSwapPane().play();
});
Button btnClose = new Button("Close");
btnClose.setLayoutX(590);
btnClose.setLayoutY(5);
btnClose.setOnMouseClicked(e -> Platform.exit());
AnchorPane rootPane = new AnchorPane();
rootPane.setStyle("-fx-background-color: grey;");
rootPane.getChildren().addAll(btnMenu, btnClose, rootPaneLabel, swapPane);
Scene scene = new Scene(rootPane, 640, 480);
stage.setScene(scene);
stage.initStyle(StageStyle.UNDECORATED);
stage.show();
}
private Animation hideSwapPane() {
btnMenu.setMouseTransparent(true);
Animation collapsePanel = new Transition() {
{
setCycleDuration(Duration.millis(2500));
}
#Override
protected void interpolate(double fraction) {
swapPane.setPrefWidth(640 * (1.0 - fraction));
}
};
collapsePanel.setOnFinished(e-> {
isExpanded = false;
btnMenu.setMouseTransparent(false);
});
return collapsePanel;
}
private Animation showSwapPane() {
btnMenu.setMouseTransparent(true);
final Animation expandPanel = new Transition() {
{
setCycleDuration(Duration.millis(2500));
}
#Override
protected void interpolate(double fraction) {
swapPane.setPrefWidth(640 * fraction);
}
};
expandPanel.setOnFinished(e-> {
isExpanded = true;
btnMenu.setMouseTransparent(false);
});
return expandPanel;
}
}
--- UPDATE 3 ---
I modified the code Felipe Guizar Diaz provide me, according to my needs, since I want a dropshadow effect on my transparent stage window.
When I click the menu button to show the left pane it shows up in front of the shadow. Even though in SceneBuilder I've placed the StackPane with the dropshadow effect in front of all nodes.
This is the "artifact" when I press to show the menu and starts playing the open transition...
How can I fix it?
I am the author of the example video. I'll repeat the response that I did in the video comments:
"you should think of it as a navigation drawer in android, the navigation drawer in JavaFX would be an AnchorPane with 2 children, first a StackPane that is equivalent to a FrameLayout working as our main content, where transitions of pane are made depending of the chosen item from the left side menu, and ultimately a ListView as our left side menu with a negative translateX that equals to the Listview width. Then when the user presses a button you must play an animation that sest the value of translateX to 0."
You shouldn't use prefWidth() in the interpolate method of the two animations (collapse Panel, expand Pane), because the children don't resize, the margin arrangement is the only constraint that the AnchorPane has.
Check out this example that I did.
https://github.com/marconideveloper/leftsidemenuexample
public class FXMLDocumentController implements Initializable {
#FXML
private Button menu;
#FXML
private AnchorPane navList;
#Override
public void initialize(URL url, ResourceBundle rb) {
//navList.setItems(FXCollections.observableArrayList("Red","Yellow","Blue"));
prepareSlideMenuAnimation();
}
private void prepareSlideMenuAnimation() {
TranslateTransition openNav=new TranslateTransition(new Duration(350), navList);
openNav.setToX(0);
TranslateTransition closeNav=new TranslateTransition(new Duration(350), navList);
menu.setOnAction((ActionEvent evt)->{
if(navList.getTranslateX()!=0){
openNav.play();
}else{
closeNav.setToX(-(navList.getWidth()));
closeNav.play();
}
});
}
}
Here is the fxml:
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" id="AnchorPane" prefWidth="500" prefHeight="500" fx:controller="leftslidemenusample.FXMLDocumentController">
<children>
<ToolBar AnchorPane.topAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" minHeight="56.0" >
<Button text="menu" fx:id="menu" />
</ToolBar>
<StackPane fx:id="mainContent" style="-fx-background-color:rgba(0,0,0,0.30)" AnchorPane.bottomAnchor="0.0" AnchorPane.topAnchor="56.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" >
<children>
</children>
</StackPane>
<AnchorPane fx:id="navList" style="-fx-background-color:white" AnchorPane.topAnchor="56.0" AnchorPane.bottomAnchor="0.0" prefWidth="180.0" translateX="-180" >
<children>
<Label text="left side menu"/>
</children>
</AnchorPane>
</children>
</AnchorPane>
Finally, I get it done.
They key features are:
Set the shadow effect on the root pane using a custom pane that drows a shadow outside its layout bounds and crops its inside content, so it has a transparent content.
The root pane can be anything else than AnchorPane.
Clip the pane that holds the main content to its inside bounds.
Below is a snippet of the source code that controls these effects:
#Override
public void initialize(URL url, ResourceBundle rb) {
...
Rectangle clip = new Rectangle(rootPaneWidth, rootPaneHeight);
rootPane.setClip(clip);
rootPane.getChildren().add(setupShadowPane());
}
private Pane setupShadowPane() {
Pane shadowPane = new Pane();
shadowPane.setStyle(
"-fx-background-color: white;" +
"-fx-effect: dropshadow(gaussian, black, " + shadowSize + ", 0, 0, 0);" +
"-fx-background-insets: " + shadowSize + ";"
);
Rectangle innerBounds = new Rectangle();
Rectangle outerBounds = new Rectangle();
shadowPane.layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
innerBounds.relocate(newBounds.getMinX() + shadowSize, newBounds.getMinY() + shadowSize);
innerBounds.setWidth(newBounds.getWidth() - shadowSize * 2);
innerBounds.setHeight(newBounds.getHeight() - shadowSize * 2);
outerBounds.setWidth(newBounds.getWidth());
outerBounds.setHeight(newBounds.getHeight());
Shape clip = Shape.subtract(outerBounds, innerBounds);
shadowPane.setClip(clip);
});
return shadowPane;
}
Slide Menu semi opened
Slide Menu fully opened
Slide Menu closed

javaFX cannot access Scene elements

I am new to javafX, have run through various tutorials, and googled extensively. I am attempting to write a multiscreen javaFX program whose second screen has a split pane, whose right pane should display a grid and a button. The framework I am using for multiple screens is copied form Angela Caiedo's MultipleScreens Framework tutorial ( https://github.com/acaicedo/JFX-MultiScreen/tree/master/ScreensFramework) .
What works: The screens framework is successful in terms of flipping between my multiple screens.
My problem: I click the button on screen 1 to move to screen 2. Screen 2 successfully shows. On screen 2 I click a button to populate data in my vBox. The code in my controller class for screen 2 is unable to access the vbox (or any other defined containers on this screen).
My main class sets up the controller screens.
public class FieldMapCreator extends Application {
public static String mainID = "main";
public static String mainFile = "MainMapFXMLDocument.fxml";
public static String initialMapID = "initialMap";
public static String initialMapFile = "FXMLMapPicture.fxml";
#Override
public void start(Stage stage) throws Exception {
ScreensController mainContainer = new ScreensController();
mainContainer.loadScreen(FieldMapCreator.mainID, FieldMapCreator.mainFile);
mainContainer.loadScreen(FieldMapCreator.initialMapID, FieldMapCreator.initialMapFile);
mainContainer.setScreen(FieldMapCreator.mainID);
Group root = new Group();
root.getChildren().addAll(mainContainer);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
}
I get to the second screen by clicking a button on screen 1. Button1 code looks like:
#FXML
private void initialMapButtonAction(ActionEvent event) {
myController.setScreen(FieldMapCreator.initialMapID);
}
Screen 2 has a button that when clicked executes a method that creates data for the vbox. The fxml for screen 2 is this:
<AnchorPane id="AnchorPane" fx:id="mainAnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="FieldMapCreator.FXMLMapPictureController">
<children>
<SplitPane fx:id="mapSplitPane" dividerPositions="0.29797979797979796" layoutX="3.0" layoutY="7.0" prefHeight="400.0" prefWidth="600.0">
<items>
<AnchorPane fx:id="anchorPaneLeft" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<Button fx:id="backButton" layoutX="14.0" layoutY="348.0" mnemonicParsing="false" onAction="#goToMainScreen" text="Back" />
</children>
</AnchorPane>
<AnchorPane fx:id="anchorPaneRight" minHeight="0.0" minWidth="0.0" prefHeight="364.0" prefWidth="401.0">
<children>
<Button fx:id="vBoxShowMap" layoutX="24.0" layoutY="346.0" mnemonicParsing="false" onAction="#buildMapFromNurseries" prefHeight="26.0" prefWidth="91.0" text="show map" />
<VBox fx:id="mapVBox" layoutX="24.0" layoutY="24.0" onMouseClicked="#vBoxMouseClicked" prefHeight="200.0" prefWidth="309.0" />
</children></AnchorPane>
</items>
</SplitPane>
</children>
</AnchorPane>
The code that fails is in the "showInitialMap()" method of the controller class for screen 2. When executed, the last line of this method "mapVBox.getChildren().add(root)", results in a null pointer exception. I expected this to be resolved via the fxml injection. I also tried the commented out code to retrieve "mapVBox" using scene lookup, but it also results in a null pointer exception. The controller code is below:
public class FXMLMapPictureController implements Initializable, ControlledScreen {
ScreensController myController;
static MyNode[][] mainField ;
#FXML
private AnchorPane mainAnchorPane;
#FXML
private static SplitPane mapSplitPane;
#FXML
private AnchorPane anchorPaneLeft;
#FXML
private static AnchorPane anchorPaneRight;
#FXML
private static VBox mapVBox;
#FXML
private Button vBoxShowMap;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
// Should something be in here ??
}
public void goToMainScreen() {
myController.setScreen(FieldMapCreator.mainID);
}
public void showInitialMap() {
int row = userFieldMap.getNumRows();
int col = userFieldMap.getNumCols();
double gridWidth = 309/col;
double gridHeight = 200/row;
Group root = new Group();
// initialize the field
for (int idx = 0; idx < col; idx++) {
for (int jdx = 0; jdx < row; jdx++) {
// create plot
Plot plot = new Plot(idx, jdx, "Plot" + idx + "/" + jdx);
// Create node to hold the Plot
MyNode node = new MyNode(idx*gridWidth, jdx*gridHeight, gridWidth, gridHeight, plot);
// add plot to group
root.getChildren().add(node);
}
}
//Scene myScene = myController.getScene();
//VBox mapVBox = (VBox)myScene.lookup("#mapVBox");
mapVBox.getChildren().add(root);
}
#Override
public void setScreenParent(ScreensController screenParent) {
myController = screenParent;
}
Is there something missing from the initialize() method? I'm wondering if I am not setting something correctly when I change screens. I am able to access the initial screen's containers without problem from all screen 1 methods including the initialize class. When I attempt the same in screen 2, I get the message "screen hasn't been loaded !!!"
Thanks for any help you can provide.
You are using static fields as FXML injection targets which don't work in Java 8 and should not have been implemented to work in any other version, as static UI elements just don't make sense. Here's a bit more detailed answer. Having resorted to a static field most of the time means the software design of the application needs to be reconsidered.

Javafx, TextArea caret doesn't move back to first line on clear

I'm having a hard time by setting back the cursor to position 0 in the first line, after clearing the text from the textarea.
Problem Background
I have a textarea, which listens to keyevents. The textarea only listens to Enter key, if found, either submits the text or clears the text if there is a "\n" in the text area.
I tried all of the following, but none really worked out.
textArea.setText("")
textArea.clear()
textArea.getText().replace("\n", "")
Remove focus from it and put it back again.
Here is a test project that is runnable and demonstrates the problem.
The Main class:
public class Main extends Application {
Stage primaryStage;
AnchorPane pane;
public void start(Stage primaryStage){
this.primaryStage = primaryStage;
initMain();
}
public void initMain(){
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("main.fxml"));
pane = loader.load();
Controller controller = loader.getController();
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String args[]){
launch();
}
}
The Controller class:
public class Controller {
#FXML
TextArea textArea;
public void initialize() {
textArea.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent keyEvent) {
if (keyEvent.getCode() == KeyCode.ENTER) {
if (!textArea.getText().equals("")
&& !textArea.getText().contains("\n")) {
handleSubmit();
}
if (textArea.getText().contains("\n")) {
handleAsk();
}
}
}
});
}
/**
* After the user gives in a short input, that has no \n, the user submits by hitting enter.
* This method will be called, and the cursor jumps over to the beginning of the next line.
*/
public void handleSubmit(){
System.out.println("Submitted");
}
/**
* When this method is calls, the cursor is not in the first line.
* This method should move the cursor to the first position in the first line in the completely
* cleared text area.
*/
public void handleAsk(){
System.out.println("Asking and clearing text area.");
textArea.setText("");
}
}
The fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="317.0" prefWidth="371.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller">
<children>
<TextArea fx:id="textArea" layoutX="78.0" layoutY="59.0" prefHeight="114.0" prefWidth="200.0" />
</children>
</AnchorPane>
My problem is, that the cursor won't jump back...
I found a short solution: after I called the desired method (handleAsk()), that should clear the text area when its finished, I call: keyEvent.consume();
This consumes the default effect of ENTER.
So: First, the event handler that you explicitly define does it's job, and then you can decide if you want to have as a "side effect" the default effect of the given key event, if not, you can consume it.
So, either your key handler will be invoked before the text is changed (due to something being typed), or it will be invoked after the text is changed.
You are presumably hoping it will be invoked before, since otherwise
textArea.getText().contains("\n")
would always evaluate to true (since the user just pressed the Enter key).
But in this case, on the second press of Enter, you will clear the text before the text is then modified. So you clear the text, then the new line is added (from the user pressing Enter). Hence the blank line in the text area.
You probably don't want to rely on the order of the events being processed anyway. It turns out that the text is modified on keyTyped events (I think), but that's not documented, and so there's really no guarantee of it. The safer way is to listen to changes in the text, and then to count the number of newlines:
textArea.textProperty().addListener((obs, oldText, newText) -> {
int oldNewlines = countNewlines(oldText); // note, can be more efficient by caching this
int numNewlines = countNewlines(newText);
if (numNewlines == 1 && oldNewlines != 1) {
// submit data
} else if (numNewlines == 2) {
textArea.clear();
}
});
With
private int countNewlines(String text) {
int newlines = 0 ;
while (text.indexOf("\n") >= 0) {
newlines++ ;
text = text.substring(text.indexOf("\n") + 1);
}
return newlines ;
}
(or some other implementation, eg using regex).

Resources