How can I move a Label with a KeyEvent? (JavaFX) - user-interface

I just learned how to make a GUI application with JavaFX with a FXML file. There is one thing that I don't understand though. When I try to add a KeyListener to a Label or the layout in my FXML file, the code doesn't get executed. It is a simple task like System.out.println("worked");, nothing complicated(eventually I want to move the Label with the keylistener, but now I just wanted something simple where I could easily see if it worked). I read somewhere that you need to add the listener on Frame level, but I don't know how. I would really appreciate it if someone could help me
Main.java:
package sample;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
Scene scene = new Scene(root, 600, 600);
scene.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>()
{
#Override
public void handle(KeyEvent event) {
Controller controller = new Controller();
controller.moveLabel(event);
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
My controller class:
package sample;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.input.KeyEvent;
public class Controller {
#FXML
private Label label;
#FXML
public void moveLabel(KeyEvent e){
switch (e.getCode()){
case RIGHT:
label.setTranslateX(3);
break;
default:
System.out.println("not possible");
}
}
}
My FXML file:
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.Label?>
<GridPane fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10"
vgap="10">
<Label text="Text" fx:id="label" ></Label>
</GridPane
Error message:
Exception in thread "JavaFX Application Thread"
java.lang.NullPointerException
at sample.Controller.moveLabel(Controller.java:15)
at sample.Main$1.handle(Main.java:22)
at sample.Main$1.handle(Main.java:18)
at
com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handle
BubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent
(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent
(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent
(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent
(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent
(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent
(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent
(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent
(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$KeyHandler.process(Scene.java:3964)
at javafx.scene.Scene$KeyHandler.access$1800(Scene.java:3910)
at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040)
at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run
(GlassViewEventHandler.java:217)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run
(GlassViewEventHandler.java:149)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$352
(GlassViewEventHandler.java:248)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock
(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent
(GlassViewEventHandler.java:247)
at com.sun.glass.ui.View.handleKeyEvent(View.java:546)
at com.sun.glass.ui.View.notifyKey(View.java:966)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147
(WinApplication.java:177)
at java.lang.Thread.run(Thread.java:748)

Add a method to your controller to move the label, e.g.
public class Controller {
private final double moveDelta = 10 ;
#FXML
private Label labelTest ;
public void moveLabel(int deltaX, int deltaY) {
labelTest.setTranslateX(labelTest.getTranslateX() + moveDelta * deltaX);
labelTest.setTranslateY(labelTest.getTranslateY() + moveDelta * deltaY);
}
}
Then get a reference to the controller in the start() method (you need to use the non-static load() method from FXMLLoader to do this), and call the method from the key handler:
#Override
public void start(Stage primaryStage) throws Exception{
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root = loader.load();
primaryStage.setTitle("Hello World");
Scene scene = new Scene(root, 600, 600);
Controller controller = loader.getController();
scene.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
switch(event.getCode()){
case RIGHT:
controller.moveLabel(1, 0);
break;
default:
System.out.println("not possible");
}
}
});
primaryStage.setScene(scene);
primaryStage.show();
}

Related

Dialogue Positioning Javafx

I am trying to create a Javafx game and I want the user to be able to input their name using a dialogue. The problem is that when I try to position the dialogue using setY() or setX() nothing changes. I should note that I am using an online IDE so if this is the problem please somebody tell me.
import javafx.stage.Modality;
import javafx.scene.control.*;
import javafx.scene.control.Dialog;
import javafx.geometry.*;
import javafx.stage.Stage;
import javafx.scene.Scene;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
VBox page = new VBox();
Scene scene = new Scene(page, 600, 700);
primaryStage.setScene(scene);
primaryStage.show();
setUserName();
}
public static void setUserName() {
Dialog<String> dialog = new Dialog<String>();
dialog.setTitle("Dialog");
ButtonType type = new ButtonType("Ok", ButtonData.OK_DONE);
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.initStyle(StageStyle.UNDECORATED);
dialog.setContentText("Hello World");
dialog.setY(100);
dialog.showAndWait();
}
public static void main(String[] args) {
launch(args);
}
}

Javafx mac terminal external process

Using JavaFX, I am trying to run a program a.out on Terminal of Mac OSX. The following code does not work on Mac. The same code works on Windows by writing cmndM={"cmd","/c","start","a.exe"}. What's wrong on Mac?
protected void onRunClick(ActionEvent evt) throws IOException {
String exeM="./a.out";
Runtime runtime=Runtime.getRuntime();
String[] cmndM= {"/bin/sh","-c",exeM}; Process pm=null; File dirM=new File(pth);
try {
pm=runtime.exec(cmndM, null, dirM);}
}catch (IOException e) {
msg.setText("Error in running simulation.");
}
}
It might be related to location of the file.
I'd have tried with fixed location first, just to be sure it works.
Also, make sure the file is actually there, inside pth.
package javafxapplication1;
import java.io.File;
import java.io.IOException;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class JavaFXApplication1 extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
String exeM="/tmp/a.out";
Runtime runtime=Runtime.getRuntime();
String[] cmndM= {"/bin/sh","-c",exeM};
Process pm=null;
File dirM=new File("/tmp");
try {
pm=runtime.exec(cmndM, null, dirM);
}catch (IOException e) {
System.out.println("Error");
}
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX progress bar component activated from Spring Listener Method

I have three layer(gui->services->dao) desktop application (JavaFX+Spring 4).
Some of my methods realized in my services layer (without fx dependency) have long time computing. I'd like to report their progress in gui.
But there is a problem. My service layer hasn't got direct access to gui, make a log of IO operations and returns only summarize of it. GUI layer, doesn't know about progress of intermediate operations.
I try to make a generic progress component. It could be realized as a Spring Bean with listener method located id GUI layer. My services can publish ApplicationEvent, which can be received via progress component in gui. When it gets the event, should activate progress bar and report next events.
In init methods I have started ProgressTask, and bind progressProperty with my gui progress bar control.
Call method of my ProgressTask runs while actual counter is less that max number of steps.
There is my component:
#Component
#Controller
public class ProgressBarComponent {
#FXML
private ProgressBar progressBar;
private ProgressTask actTask;
#FXML
public void initialize(){
}
private void initProgressBar(int aMax){
actTask=new ProgressTask();
actTask.initState(aMax);
progressBar.progressProperty().bind(actTask.progressProperty());
new Thread(actTask).start();
}
#EventListener
public void handleProgressEvent(ProgressEvent event) {
if(event.getGetActStep()==0){
initProgressBar(event.getMaxStep());
}
actTask.changeState(event.getGetActStep());
}
class ProgressTask extends Task<Object>{
private int max=100;
private int act=0;
private volatile boolean cancelled;
public void initState(int aMax){
this.max=aMax;
}
public void changeState(int newVal){
act=newVal;
updateProgress(act, max);
updateMessage("progress: " + act);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new IllegalArgumentException(e);
}
}
#Override
protected Object call() throws Exception {
while(act<max){
}
return null;
}
}
}
But it doesn't work.
Progress bar control is activated but in one step and at once it has a 100 percent value.
I have found a lot of examples Progress Bar FX control, but all of them assume that main logic is computed inside call() method of ProgressTask. In my case it isn't possible.
It is possible to make a ProgressBar Component, which is activated from listener method ? If yes, what code should be in ProgressTask.call() method ?
The point of a Task is to define some functionality that should be run on a background thread. So if this functionality is already implemented elsewhere, there is no need for a task.
Assuming the service is being invoked on a background thread (i.e. not on the FX Application Thread), you should just be able to do:
#Component
#Controller
public class ProgressBarComponent {
private int max ;
#FXML
private ProgressBar progressBar;
#FXML
public void initialize(){
}
private void initProgressBar(int aMax){
this.max = aMax ;
}
#EventListener
public void handleProgressEvent(ProgressEvent event) {
if(event.getGetActStep()==0){
Platform.runLater(() -> initProgressBar(event.getMaxStep()));
}
Platform.runLater(() ->
progressBar.setProgress(1.0 * event.getGetActStep() / max));
}
}
Here's a complete SSCCE:
ProgressViewer.java:
package springevent;
import org.springframework.context.event.EventListener;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ProgressViewer {
private Stage stage = new Stage();
private Scene scene ;
private ProgressBar progressBar ;
public ProgressViewer() {
progressBar = new ProgressBar();
StackPane root = new StackPane(progressBar);
root.setPadding(new Insets(20));
scene = new Scene(root);
stage.setScene(scene);
}
#EventListener
public void progressUpdate(ProgressEvent event) {
if (event.getCurrent() < event.getMax()) {
Platform.runLater(stage::show);
} else {
Platform.runLater(stage::hide);
}
final double progress = 1.0 * event.getCurrent() / event.getMax() ;
Platform.runLater(() -> progressBar.setProgress(progress));
}
}
Service.java:
package springevent;
import javax.inject.Inject;
import org.springframework.context.ApplicationContext;
public class Service {
#Inject
private ApplicationContext context ;
public void runService() {
context.publishEvent(new ProgressEvent(0, 100));
try {
for (int i = 1 ; i <= 100; i++) {
Thread.sleep(200);
context.publishEvent(new ProgressEvent(i, 100));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
ProgressEvent.java
package springevent;
public class ProgressEvent {
private final int current ;
private final int max ;
public ProgressEvent(int current, int max) {
this.current = current ;
this.max = max ;
}
public int getCurrent() {
return current ;
}
public int getMax() {
return max ;
}
}
MainController.java
package springevent;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import org.springframework.context.event.EventListener;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class MainController {
#Inject
private Executor exec ;
#Inject
private Service service ;
#FXML
private Button startButton ;
public void startProcess() {
exec.execute(service::runService);
}
#EventListener
public void progressUpdate(ProgressEvent event) {
startButton.setDisable(event.getCurrent() < event.getMax());
}
}
Main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<StackPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="springevent.MainController">
<padding>
<Insets top="20" bottom="20" left="20" right="20"/>
</padding>
<Button fx:id="startButton" text="Start process" onAction="#startProcess" />
</StackPane>
Main.java
package springevent;
import java.io.IOException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
private AbstractApplicationContext context ;
#Override
public void start(Stage primaryStage) throws IOException {
context = new AnnotationConfigApplicationContext(AppConfig.class);
FXMLLoader loader = new FXMLLoader(getClass().getResource("Main.fxml"));
loader.setControllerFactory(context::getBean);
Scene scene = new Scene(loader.load());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
and AppConfig.java:
package springevent;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class AppConfig {
#Bean
public Service service() {
return new Service();
}
#Bean
public MainController mainController() {
return new MainController();
}
#Bean
public ProgressViewer progressViewer() {
return new ProgressViewer();
}
#Bean
public Executor exec() {
return Executors.newCachedThreadPool(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});
}
}

Slide in/out JavaFX stage from/to right side of the screen

I have a task to make JavaFX application. When I start it the window should slides smoothly from right side of screen and when I click on "x" button it's should slides out to right side and then finishes.
I found possible to use simple Timeline animation for this. I made the window to slide in, when I run app, but can't figure out how to slide out window.
I tried to handle this defining handler via setOnCloseRequest() method of stage, but stuck with two problems:
can't implement animation
after click on "x" application closed immediately even if I use consume() method of Window event
Code:
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) {
stage.setTitle("Main");
Group root = new Group();
Scene scene = new Scene(root, 300, 250);
stage.setScene(scene);
Rectangle2D primScreenBounds = Screen.getPrimary().getVisualBounds();
stage.setX(primScreenBounds.getMinX() + primScreenBounds.getWidth());
System.out.println(primScreenBounds.getWidth());
stage.setY(primScreenBounds.getMinY());
stage.setWidth(0);
stage.setHeight(primScreenBounds.getHeight());
Timeline timeline = new Timeline();
timeline.setAutoReverse(true);
WritableValue<Double> writableWidth = new WritableValue<Double>() {
#Override
public Double getValue() {
return stage.getWidth();
}
#Override
public void setValue(Double value) {
stage.setWidth(value);
}
};
KeyValue kv = new KeyValue(writableWidth, 600d);
KeyFrame kf = new KeyFrame(Duration.millis(3000), kv);
timeline.getKeyFrames().addAll(kf);
timeline.play();
stage.show();
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
event.consume();
}
});
}
}
This works (kind of, it is not very smooth) for me:
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.WritableValue;
import javafx.event.EventHandler;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.Duration;
public class SlidingWindow extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) {
stage.setTitle("Main");
Group root = new Group();
Scene scene = new Scene(root);
stage.setScene(scene);
Rectangle2D primScreenBounds = Screen.getPrimary().getVisualBounds();
double screenRightEdge = primScreenBounds.getMaxX() ;
stage.setX(screenRightEdge);
System.out.println(primScreenBounds.getWidth());
stage.setY(primScreenBounds.getMinY());
stage.setWidth(0);
stage.setHeight(primScreenBounds.getHeight());
Timeline timeline = new Timeline();
WritableValue<Double> writableWidth = new WritableValue<Double>() {
#Override
public Double getValue() {
return stage.getWidth();
}
#Override
public void setValue(Double value) {
stage.setX(screenRightEdge - value);
stage.setWidth(value);
}
};
KeyValue kv = new KeyValue(writableWidth, 600d);
KeyFrame kf = new KeyFrame(Duration.millis(3000), kv);
timeline.getKeyFrames().addAll(kf);
timeline.play();
stage.show();
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
Timeline timeline = new Timeline();
KeyFrame endFrame = new KeyFrame(Duration.millis(3000), new KeyValue(writableWidth, 0.0));
timeline.getKeyFrames().add(endFrame);
timeline.setOnFinished(e -> Platform.runLater(() -> stage.hide()));
timeline.play();
event.consume();
}
});
}
}
The Platform.runLater(...) seems necessary to prevent a slew of NullPointerExceptions when the window is hidden, probably because the animation is causing some system to try to access a stage that no longer exists.

Add image to a button at a specific position JavaFX

When I add image and text to a button, by default elements are set horizontally. How can I change this behavior to get text under image ?
Set the contentDisplayProperty on the button.
button.setContentDisplay(ContentDisplay.TOP);
Here is an executable example:
import javafx.application.Application;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ButtonGraphicTest extends Application {
#Override public void start(final Stage stage) throws Exception {
final Label response = new Label();
final ImageView imageView = new ImageView(
new Image("http://icons.iconarchive.com/icons/eponas-deeway/colobrush/128/heart-2-icon.png")
);
final Button button = new Button("I love you", imageView);
button.setStyle("-fx-base: coral;");
button.setContentDisplay(ContentDisplay.TOP);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
response.setText("I love you too!");
}
});
final VBox layout = new VBox(10);
layout.setAlignment(Pos.CENTER);
layout.getChildren().addAll(button, response);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10; -fx-font-size: 20;");
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) { launch(args); }
}
// icon license: (creative commons with attribution) http://creativecommons.org/licenses/by-nc-nd/3.0/
// icon artist attribution page: (eponas-deeway) http://eponas-deeway.deviantart.com/gallery/#/d1s7uih

Resources