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

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).

Related

JavaFX KeyEvent doesn't do anything

So I have a little program where you can fly your spaceship between planets and I want to use the arrow keys to rotate the ship. First I tried adding the key listener to the panel and the ship did rotate, but only when I pressed ctrl/alt. Then I tried adding the key listener to the scene instead (which people say is the right thing to do because it doesn't depend on focus), but although the function that rotates the ship is called, you can't see anything on the screen.
The main class:
#Override
public void start(Stage stage) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Main.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root, 700, 500);
Controller controller = new Controller();
loader.setController(controller);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
stage.setScene(scene);
stage.setFullScreen(true);
stage.show();
controller.setScene(scene);
} catch(Exception e) {
e.printStackTrace();
}
}
The controller:
#FXML
private BorderPane contentPane;
private OrbiterPanel orbiterPanel = new PerfectCirclePanel(this);
private Scene scene;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
contentPane.setCenter(orbiterPanel);
}
public void setScene(Scene scene) {
this.scene = scene;
scene.addEventFilter(KeyEvent.KEY_PRESSED, e -> keyStrokesPressed(e));
scene.addEventFilter(KeyEvent.KEY_RELEASED, e -> keyStrokesReleased(e));
}
private void keyStrokesPressed(KeyEvent e) {
switch(e.getCode()) {
case LEFT -> {
orbiterPanel.leftPressed();
}
case RIGHT -> {
orbiterPanel.rightPressed();
}
default -> {}
}
}
private void keyStrokesReleased(KeyEvent e) {
switch(e.getCode()) {
case LEFT -> {
orbiterPanel.leftReleased();
}
case RIGHT -> {
orbiterPanel.rightReleased();
}
default -> {}
}
}
The superclass of all my "worlds/maps":
protected AtomicReference<Spaceship> shipReference = new AtomicReference<>();
protected RotateTransition rotatingTransition;
public OrbiterPanel() {
rotatingTransition = new RotateTransition();
rotatingTransition.setCycleCount(RotateTransition.INDEFINITE);
rotatingTransition.setInterpolator(Interpolator.LINEAR);
}
protected void rightPressed() {
if(rotatingTransition.getStatus().equals(Status.RUNNING)) return;
rotatingTransition.stop();
rotatingTransition.setNode(shipReference.get());
rotatingTransition.setByAngle(360);
rotatingTransition.setDuration(Duration.seconds(2));
rotatingTransition.play();
}
protected void leftPressed() {
if(rotatingTransition.getStatus().equals(Status.RUNNING)) return;
rotatingTransition.stop();
rotatingTransition.setNode(shipReference.get());
rotatingTransition.setByAngle(-360);
rotatingTransition.setDuration(Duration.seconds(2));
rotatingTransition.play();
}
protected void rightReleased() {
rotatingTransition.stop();
}
protected void leftReleased() {
rotatingTransition.stop();
}
PerfectCirclePanel, a subclass of OrbiterPanel doesn't really do much besides displaying the spaceship and a planet.
FXML:
<BorderPane prefHeight="607.0" prefWidth="877.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
<center>
<BorderPane fx:id="contentPane" prefHeight="704.0" prefWidth="963.0" style="-fx-background-color: black;">
</BorderPane>
</center>
</BorderPane>
There are two different ways to connect a controller to the UI defined by an FXML file.
Either:
Specify the controller class by providing a fx:controller attribute to the root element of the FXML. In this case, by default, a controller instance will be created from that class by invoking its no-argument constructor, and that instance will be the controller associated with the UI.
Or:
Call setController(...) on the FXMLLoader instance.
When you call loader.load(), if a controller exists, any #FXML-annotated fields in the controller will be initialized from the corresponding FXML elements, and then the initialize() method will be called.
In your code, you create two Controller instances: one is created from the fx:controller attribute, and associated with the UI. The other you create "by hand" in the Java code. The latter is not connected to the UI (the #FXML-annotated fields are not injected, and initialize() is not invoked), because you call setController(...) after calling load().
Because there are two Controller instances, there are two OrbiterPanel instances. The one created from the fx:controller attribute is the one displayed in the UI (because that's the one referenced when initialize() is invoked). The one created from the Controller instance you create in code is not displayed; however that is the one referenced by your event handler.
Remove the fx:controller attribute, and move the call to setController() before the call to load().

converting java fx code into java scenebuilder

I cant convert this code into scene builder...
The problem is in event handler....
I am not getting how to use the confirmCloseEventHandler event handeler in java
fx scene builder...
thanks in advance.
mainly i cant use those event handlers... in fxml controllers...
public class Javafxpopupmessage extends Application {
private Stage mainStage;
#Override
public void start(Stage stage) throws Exception {
this.mainStage = stage;
stage.setOnCloseRequest(confirmCloseEventHandler);
Button closeButton = new Button("Close Application");
closeButton.setOnAction(event ->
stage.fireEvent(
new WindowEvent(
stage,
WindowEvent.WINDOW_CLOSE_REQUEST
)
)
);
StackPane layout = new StackPane(closeButton);
layout.setPadding(new Insets(100));
stage.setScene(new Scene(layout));
stage.show();
}
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
Alert closeConfirmation = new Alert(
Alert.AlertType.CONFIRMATION,
"Are you sure you want to exit?"
);
Button exitButton = (Button)
closeConfirmation.getDialogPane().lookupButton(
ButtonType.OK
);
exitButton.setText("Exit");
closeConfirmation.setHeaderText("Confirm Exit");
closeConfirmation.initModality(Modality.APPLICATION_MODAL);
closeConfirmation.initOwner(mainStage);
// normally, you would just use the default alert positioning,
// but for this simple sample the main stage is small,
// so explicitly position the alert so that the main window can still be
seen.
// closeConfirmation.setX(mainStage.getX());
//closeConfirmation.setY(mainStage.getY() + mainStage.getHeight());
Optional<ButtonType> closeResponse = closeConfirmation.showAndWait();
if (!ButtonType.OK.equals(closeResponse.get())) {
event.consume();
}
};
public static void main(String[] args) {
launch(args);
}
}
Registering some handler for the primary stage via fxml could only be done with a bad hack, since FXMLLoader only has access to objects it creates itself.
You could add a listener to the Node.scene property of some node in your scene and add a listener to the window property of that scene as soon as it's set and access the window as soon as it's assigned, which is quite complex for something that could be done using much simpler code in the start method.
Other than that hack you won't get around registering that event handler in the start method (or passing the Stage to the controller resulting in more complex code than the one posted).
As for the close button onAction event: You can use a method of your controller as handler:
fxml
<StackPane xmlns:fx="http://javafx.com/fxml" fx:controller="mypackage.MyController">
<children>
<Button text="Close Application" onAction="#close"/>
</children>
<padding>
<Insets topRightBottomLeft="100"/>
</padding>
</StackPane>
controller
package mypackage;
...
public class MyController {
#FXML
private void close(ActionEvent event) {
Node source = (Node) event.getSource();
Window window = source.getScene().getWindow();
window.fireEvent(new WindowEvent(
window,
WindowEvent.WINDOW_CLOSE_REQUEST));
}
}

Moving an image using mouse drag events

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.

JavaFX Tab fit full size of header

I want the tabs in my tabpane to fit the complete size of the tabpane which they are in. So basically there shall be no header area visible, everything should be covered by the tabs.
I have 2 problems here:
How can I make the tabs dynamically fit the width of the tabpane?
How can I fit them to the correct height, and remove the little spaces between the tabs? I suppose this is done via css, but I don't quite know how.
greets
You can simply add a listener to the TabPane to detect any changes and adjust the width and/or height of the tabs accordingly.
#FXML
private JFXTabPane tabPane;
Divide the width by number of tabs:
tabPane.widthProperty().addListener((observable, oldValue, newValue) ->
{
tabPane.setTabMinWidth(tabPane.getWidth() / tabPane.getTabs().size());
tabPane.setTabMaxWidth(tabPane.getWidth() / tabPane.getTabs().size());
});
It's true, the property objects of tabs are READ-Only. This means that you cannot manually set the properties. However, the values of the read-only properties can be bound to other properties, also the properties have some manipulation methods e.g. divide, subtract, please check the documentation for extended information.
You can call the .tabminWidthProperty() on the tabpane and bind it to the width-property value of the parent of the layout.
Here's the code to size all available tabs equally filling the whole screen:
tabpane.tabMinWidthProperty().bind(root.widthProperty().divide(tabpane.getTabs().size()).subtract(20));
I subtracted 20 to avoid the horizontal/vertical scrollbar. Surely there is also a way to disable them to pop up at all, but I'm an Android Dev and a greenhorn in JavaFx.
I'm not sure if this is do-able in CSS, and there might be a simpler way in Java to do this, but I wrote a class that extends TabPane in order to stretch the tabs to fill all the space.
public class StretchedTabPane extends TabPane {
public StretchedTabPane() {
super();
setUpChangeListeners();
}
public StretchedTabPane(Tab... tabs) {
super(tabs);
setUpChangeListeners();
}
private void setUpChangeListeners() {
widthProperty().addListener(new ChangeListener<Number>() {
#Override public void changed(ObservableValue<? extends Number> value, Number oldWidth, Number newWidth) {
Side side = getSide();
int numTabs = getTabs().size();
if ((side == Side.BOTTOM || side == Side.TOP) && numTabs != 0) {
setTabMinWidth(newWidth.intValue() / numTabs - (20));
setTabMaxWidth(newWidth.intValue() / numTabs - (20));
}
}
});
heightProperty().addListener(new ChangeListener<Number>() {
#Override public void changed(ObservableValue<? extends Number> value, Number oldHeight, Number newHeight) {
Side side = getSide();
int numTabs = getTabs().size();
if ((side == Side.LEFT || side == Side.RIGHT) && numTabs != 0) {
setTabMinWidth(newHeight.intValue() / numTabs - (20));
setTabMaxWidth(newHeight.intValue() / numTabs - (20));
}
}
});
getTabs().addListener(new ListChangeListener<Tab>() {
public void onChanged(ListChangeListener.Change<? extends Tab> change){
Side side = getSide();
int numTabs = getTabs().size();
if (numTabs != 0) {
if (side == Side.LEFT|| side == Side.RIGHT) {
setTabMinWidth(heightProperty().intValue() / numTabs - (20));
setTabMaxWidth(heightProperty().intValue() / numTabs - (20));
}
if (side == Side.BOTTOM || side == Side.TOP) {
setTabMinWidth(widthProperty().intValue() / numTabs - (20));
setTabMaxWidth(widthProperty().intValue() / numTabs - (20));
}
}
}
});
}
}
This will automatically adjust the width of each tab when the height, width or number of tabs changes.
It can be done directly in fxml file and can be used on any Node no having fitToWidth or fitToHeight property. See small example below, hope it helps somebody.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="somepackage.MainWindowController"
prefHeight="400.0" prefWidth="600.0"
fx:id="mainPane">
<TabPane prefHeight="${mainPane.height}" prefWidth="${mainPane.width}" style="-fx-background-color: blue">
<Tab fx:id="someTab">
<Label text="bla bla">
</Tab>
</TabPane>
I'd like to pinpoint the main part which causes extension of the Tab:
prefHeight="${mainPane.height}" prefWidth="${mainPane.width}"

JavaFX TabPane selection wont work properly

I have two *.fxml forms with controllers. First is Window, second - ProductPane.
Simplified Window.fxml is:
<BorderPane prefWidth="650.0" prefHeight="450.0" fx:controller="package.WindowController">
<center>
<TabPane fx:id="tabsPane">
<tabs>
<Tab fx:id="productsTab" text="Products"/>
<Tab fx:id="warehouseTab" text="Warehouse"/>
<Tab fx:id="saleTab" text="Sale"/>
</tabs>
</TabPane>
</center>
</BorderPane>
Controller for Window.fxml:
public class WindowController {
#FXML
private TabPane tabsPane;
#FXML
private Tab productsTab;
#FXML
void initialize() {
sout("Main Window initialization...");
tabsPane.getSelectionModel().selectedIndexProperty().addListener((e, o, n) -> {
sout("Changed to " + n);
});
tabsPane.getSelectionModel().selectedItemProperty().addListener((e, o, n) -> {
sout("New item: " + n);
// Load ProductPane content:
if(n == productsTab) {
try {
Parent p = FXMLLoader.load(getClass().getResource("productPane.fxml"));
n.setContent(p);
} catch(IOException ex) {
ex.printStackTrace();
}
}
});
sout("Select first item...");
tabsPane.getSelectionModel().selectFirst();
// This methods also don't work
// tabsPane.getSelectionModel().clearAndSelect();
// tabsPane.getSelectionModel().select(productTab);
// tabsPane.getSelectionModel().select(0);
}
}
The problem is: when I load Window.fxml in main() and launch it appearse window with empty first tab.
Debug output:
Main Window initialization...
Select first item...
But ProductPane not loaded and listener do not call. If I switch between tabs in Window, listeners are triggered and Products tab loaded properly.
What's the problem?
You added a ChangeListener to the tab pane's selection model, which of course gets notified when the selection changes. By default, the first tab is selected, so at the time the change listener is added, the first tab is already selected. This means when you call selectFirst(), the selection doesn't change (because you're asking to select the tab that is already selected), so the listener isn't notified.
The solution is a little ugly: you just need to directly load your products tab content if the products tab is selected at the time you add the listener. I would factor that code into a separate method to avoid too much repetition:
#FXML
void initialize() {
System.out.println("Main Window initialization...");
tabsPane.getSelectionModel().selectedIndexProperty().addListener((e, o, n) -> {
System.out.println("Changed to " + n);
});
tabsPane.getSelectionModel().selectedItemProperty().addListener((e, o, n) -> {
System.out.println("New item: " + n);
// Load ProductPane content:
if(n == productsTab) {
loadProductsTab();
}
});
if (tabPane.getSelectionModel().getSelectedItem() == productsTab) {
loadProductsTab();
}
}
private void loadProductsTab() {
try {
Parent p = FXMLLoader.load(getClass().getResource("productPane.fxml"));
productsTab.setContent(p);
} catch(IOException ex) {
ex.printStackTrace();
}
}
If you find you need this kind of functionality a lot, you might be interested in the ReactFX framework, which (I think) has built-in functionality that handles these kinds of cases.

Resources