I'm making an application with JavaFX and Scene Builder. I have a method that make animation of Anchor Pane. Translate transition and rotate transition.
When I press "1" on keyboard that method is called.
The problem is when I press on key 1 so fast animation works
incorrectly. It seems when I press too fast on a key animation of
Anchor Pane is shifted.
How can I prohibit key press during animation or how can I set a delay between key presses?
Controller:
#FXML private AnchorPane randomCard;
#FXML public void initialize(URL location, ResourceBundle resources) {
//key commands
mainAnchor.setOnKeyPressed(event -> {
switch (event.getCode()) {
case DIGIT1:
showRandomCard();
break;
}
});
//show random card in main window
private void showRandomCard(){
TranslateTransition tt = new TranslateTransition();
tt.setDuration(Duration.millis(400));
tt.setNode(randomCard);
tt.setFromY(950);
tt.setFromX(-600);
tt.setToY(0);
tt.setToX(0);
RotateTransition rt = new RotateTransition(Duration.millis(500), randomCard);
rt.setByAngle(360);
rt.setRate(1);
rt.setCycleCount(1);
tt.play();
rt.play();
}
Just use a boolean field and use the onFinished handler of the animation that finishes last to reset it to a state that allows execution of the logic for creating/starting the animations:
private boolean animationRunning = false;
private void showRandomCard(){
if (!animationRunning) {
animationRunning = true;
TranslateTransition tt = new TranslateTransition();
tt.setDuration(Duration.millis(400));
tt.setNode(randomCard);
tt.setFromY(950);
tt.setFromX(-600);
tt.setToY(0);
tt.setToX(0);
RotateTransition rt = new RotateTransition(Duration.millis(500), randomCard);
rt.setByAngle(360);
rt.setRate(1);
rt.setCycleCount(1);
rt.setOnFinished(evt -> animationRunning = false);
tt.play();
rt.play();
}
}
Related
my problem is the following:
I have a JavaFX application with a Button called "bindB";
Button bindB = new Button("None");
bindB.setOnAction(event -> {
bindB.setText("...");
BindKey.bindKey(scene, bindB);
});
with the text "None". Is this button pressed, his text first changes to "..."
and by then calling the method "BindKey.bindKey();", the text will change to the
name of the key, the user is pressing on his keyboard.
This is the code of the method "BindKey.bindKey();":
public static void bindKey(Scene scene, Button bindB){
scene.setOnKeyPressed(event -> {
bindB.setText(String.valueOf(event.getCode()));
});
}
As you can see, in the args of the method we give the button "bindB", so that the method knows
what button to change the name of, aswell as the current scene.
This code does work, but the problem is, that even after the button was
pressed, and its text has already be changed, the text still changes to the name of different
keys if you press them afterwards WITHOUT having to press the button a second time.
I thought that you had to end the "setOnAction" event by calling
event.consume();
but that didnt work...
So how do I make the buttons text only change, if the button has actually been pressed a second or third time?
Otherwise, the task which the button performs is toggled by EVERY key because technically
every key is the toggle key as the task reads the name of the button to know what key is for toggling.
Full code example:
Main class:
public class Main {
// Initialize GUI
public static void main(String[] args) {
GUI gui = new GUI();
gui.run();
}
}
GUI class:
public class GUI extends Application {
public void run() {
launch();
}
#Override
public void start(Stage window) throws Exception {
// When closing Window
window.setOnCloseRequest(event -> {
exitApplication(window);
});
// GridPane
GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 10, 10,10));
grid.setVgap(15);
grid.setHgap(30);
// Scene
Scene scene = new Scene(grid, 200, 200);
window.setScene(scene);
// Bind Button
Button bindB = new Button("None");
GridPane.setConstraints(bindB, 1, 1);
bindB.setOnAction(event -> {
bindB.setText("...");
BindKey.bindKey(scene, bindB);
});
// Add to Grid
grid.getChildren().addAll(bindB);
// Show Window
window.show();
}
// Provide a clean exit
private void exitApplication(Stage window){
window.close();
Platform.exit();
System.exit(0);
}
}
BindKey class:
public class BindKey {
// Changes Buttons text
public static void bindKey(Scene scene, Button bindB){
scene.setOnKeyPressed(event -> {
bindB.setText(String.valueOf(event.getCode()));
});
}
}
I am not to 100% sure if this is the problem, but I think the only thing you have to do is the following:
scene.setOnKeyPressed(event -> {
bindB.setText(String.valueOf(event.getCode()));
scene.setOnKeyPressed(null);
});
}
You just have to remove the Key-Listener from the scene after you set the text. Because otherwise it will listen for keys the entire time.
I have added a custom toolbar (where my window actions are placed) to my application. So far everything works well. Related to the window handling I'm searching for a possibility to handle the "fullscreen got closed" event. Scenario:
App starts in windowed mode -> user clicks on (custom) toolbar button to get into fullscreen. The Toolbar will now be set its visibility to false. The users now exits fullscreen mode via button (native macOS Button to exit fullscreen) --> I need now to react for this (to set the toolbar to visible again) but cannot find a way how to do it.
main.java
MainController mc = new MainController();
Parent root = FXMLLoader.load(getClass().getResource("welcome-view.fxml"));
stage.initStyle(StageStyle.TRANSPARENT);
mc.doSwitchScenes(stage, root);
stage.show();
MainController.java
...
private String title = "Project Apollo";
private Color fillColor = TRANSPARENT;
private int minWidth = 800;
private int minHeight = 600;
...
public void btnMinimize(MouseEvent mouseEvent) {
Stage stage = (Stage)((Circle)mouseEvent.getSource()).getScene().getWindow();
// is stage minimizable into task bar. (true | false)
stage.setIconified(true);
};
public void btnCloseApp(MouseEvent mouseEvent) {
Platform.exit();
System.exit(0);
}
public void btnFullscreen(MouseEvent mouseEvent) {
Stage stage = (Stage)((Circle)mouseEvent.getSource()).getScene().getWindow();
stage.setFullScreen(true);
Scene actualScene = ((Node)mouseEvent.getSource()).getScene();
Parent hbc = (Parent) actualScene.lookup("#headerBarContainer");
if(hbc != null){
hbc.setVisible(false);
}
System.out.println("clicked FS");
}
...
The point is that at least on MacOS the window has its native os control to exit fullscreen - is it possible to target this event or at least the change of the stage size maybe?
Listen to stage.fullScreenProperty() and respond to changes:
stage.fullScreenProperty().addListener((ChangeListener) (obs,oldValue,newValue) ->
{/*TODO respond to changes in full screen */;});
I'm trying to do a maze resolver with a cellular automaton but I have a display problem
for each new generation of a grid of an automaton we try to display the cells in the form of rectangle.The initialization works well and the grid is displayed,the last generation of the simulation is displayed too but not the intermediate steps.
pic of first generation
last generation
//Initialise la liste des rectangles
public void initRectList() {
for(int height = 0; height < this.mazeA.grid.getHeight(); height++) {
for(int width = 0; width < this.mazeA.grid.getWidth(); width++) {
this.rectList[height][width] = new Rectangle(REC_HEIGHT,REC_WIDTH, getColorCell(this.mazeA.grid, height, width));
}
}
}
//Dessine le labyrinthe
public void drawGrid() {
for (int height = 0; height < this.mazeA.grid.getHeight(); height++) {
for(int width = 0; width < this.mazeA.grid.getWidth(); width++) {
tilePane.getChildren().add(this.rectList[height][width]);
}
}
}
public class MazeFX {
private HBox root = new HBox();
private Scene scene = new Scene(root,1100,800);
private TilePane tilePane = new TilePane();
private Grid grid = new Grid(30,30);
private Rectangle[][] rectList = new Rectangle[30][30];
private VBox buttons = new VBox();
private Button reset = new Button("Reset");
private Button pas = new Button("Play");
private Button load = new Button("Load");
private MazeAutomaton mazeA;
private final double REC_HEIGHT = 20.;
private final double REC_WIDTH = 20.;
public MazeFX(Stage stage) throws InterruptedException {
scene.getStylesheets().add(getClass().getResource("/src/application.css").toExternalForm());
initButton();
initLayout();
initGridByTopologie();
mazeA = new MazeAutomaton(this.grid);
initRectList();
drawGrid();
pressedButtons(stage);
setWindow(stage);
displayWindow(stage);
}
to start the next generation you press a button.
//Action de l'utilisateur sur l'un des bouttons
public void pressedButtons(Stage stage) {
pressReset();
pressPAS();
pressLoad(stage);
}
//Boutton Play/Stop préssé
public void pressPAS() {
this.pas.setOnMouseClicked(e -> {
for (int i = 0; i < 30; i++) {
mazeA.nextStep();
try {
Thread.sleep(100);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
updateRectColor();
}
}
);
}
the problem seems to be that we are stuck in the method setOnMouseClicked() and that the update of the rectangles is not done, with a textual display I see the evolution of the automaton which indicates us that the simulation works and that the problem seems to come from JavaFX
The JavaFX Application Thread runs as a loop. Effectively (the actual implementation details are far more complex) that loop does the following (in pseudocode):
while (applicationIsRunning()) {
if (thereAreEventsToHandle()) {
handleEvents();
}
if (thereAreAnimationsToUpdate()) {
updateAnimations();
}
if (itsTimeToRenderTheScene()) {
renderScene();
}
}
By default, JavaFX renders the scene at most 60 times per second.
The code in your event handler is executed on the FX Application Thread (it's invoked by the first block in the pseudocode loop above). Since it sleeps on that thread, the FX Application Thread never gets to the third part of the loop (rendering the Scene) until the entire event handler completes. Consequently, you never see the intermediate updates, because the scene is never rendered.
Assuming mazeA.nextStep() doesn't block (or take a long time to run), it's best to refactor this as an Animation, e.g. a Timeline:
public void pressPAS() {
this.pas.setOnMouseClicked(e -> {
KeyFrame updateMaze = new KeyFrame(Duration.ZERO, evt -> mazeA.nextStep());
KeyFrame updateRect = new KeyFrame(Duration.millis(100), evt -> updateRectColor());
Timeline timeline = new Timeline(updateMaze, updateRect);
timeline.setCycleCount(30);
timeline.play();
});
}
The timeline.play() method simply starts the animation and returns immediately, allowing the FX Application Thread to proceed. When the FX Application Thread checks for running animations, it will check if it's time to execute either of the handlers in the key frames, and if so will execute them. Then it will render the scene as usual.
I'm trying to do a maze resolver with a cellular automaton but I have a display problem
for each new generation of a grid of an automaton we try to display the cells in the form of rectangle.The initialization works well and the grid is displayed,the last generation of the simulation is displayed too but not the intermediate steps.
pic of first generation
last generation
//Initialise la liste des rectangles
public void initRectList() {
for(int height = 0; height < this.mazeA.grid.getHeight(); height++) {
for(int width = 0; width < this.mazeA.grid.getWidth(); width++) {
this.rectList[height][width] = new Rectangle(REC_HEIGHT,REC_WIDTH, getColorCell(this.mazeA.grid, height, width));
}
}
}
//Dessine le labyrinthe
public void drawGrid() {
for (int height = 0; height < this.mazeA.grid.getHeight(); height++) {
for(int width = 0; width < this.mazeA.grid.getWidth(); width++) {
tilePane.getChildren().add(this.rectList[height][width]);
}
}
}
public class MazeFX {
private HBox root = new HBox();
private Scene scene = new Scene(root,1100,800);
private TilePane tilePane = new TilePane();
private Grid grid = new Grid(30,30);
private Rectangle[][] rectList = new Rectangle[30][30];
private VBox buttons = new VBox();
private Button reset = new Button("Reset");
private Button pas = new Button("Play");
private Button load = new Button("Load");
private MazeAutomaton mazeA;
private final double REC_HEIGHT = 20.;
private final double REC_WIDTH = 20.;
public MazeFX(Stage stage) throws InterruptedException {
scene.getStylesheets().add(getClass().getResource("/src/application.css").toExternalForm());
initButton();
initLayout();
initGridByTopologie();
mazeA = new MazeAutomaton(this.grid);
initRectList();
drawGrid();
pressedButtons(stage);
setWindow(stage);
displayWindow(stage);
}
to start the next generation you press a button.
//Action de l'utilisateur sur l'un des bouttons
public void pressedButtons(Stage stage) {
pressReset();
pressPAS();
pressLoad(stage);
}
//Boutton Play/Stop préssé
public void pressPAS() {
this.pas.setOnMouseClicked(e -> {
for (int i = 0; i < 30; i++) {
mazeA.nextStep();
try {
Thread.sleep(100);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
updateRectColor();
}
}
);
}
the problem seems to be that we are stuck in the method setOnMouseClicked() and that the update of the rectangles is not done, with a textual display I see the evolution of the automaton which indicates us that the simulation works and that the problem seems to come from JavaFX
The JavaFX Application Thread runs as a loop. Effectively (the actual implementation details are far more complex) that loop does the following (in pseudocode):
while (applicationIsRunning()) {
if (thereAreEventsToHandle()) {
handleEvents();
}
if (thereAreAnimationsToUpdate()) {
updateAnimations();
}
if (itsTimeToRenderTheScene()) {
renderScene();
}
}
By default, JavaFX renders the scene at most 60 times per second.
The code in your event handler is executed on the FX Application Thread (it's invoked by the first block in the pseudocode loop above). Since it sleeps on that thread, the FX Application Thread never gets to the third part of the loop (rendering the Scene) until the entire event handler completes. Consequently, you never see the intermediate updates, because the scene is never rendered.
Assuming mazeA.nextStep() doesn't block (or take a long time to run), it's best to refactor this as an Animation, e.g. a Timeline:
public void pressPAS() {
this.pas.setOnMouseClicked(e -> {
KeyFrame updateMaze = new KeyFrame(Duration.ZERO, evt -> mazeA.nextStep());
KeyFrame updateRect = new KeyFrame(Duration.millis(100), evt -> updateRectColor());
Timeline timeline = new Timeline(updateMaze, updateRect);
timeline.setCycleCount(30);
timeline.play();
});
}
The timeline.play() method simply starts the animation and returns immediately, allowing the FX Application Thread to proceed. When the FX Application Thread checks for running animations, it will check if it's time to execute either of the handlers in the key frames, and if so will execute them. Then it will render the scene as usual.
I’m trying to implement a caps lock alert on password field. If caps lock is ON then the bubble will appear below the password field. I’ve searched a lot but didn’t get any solution that how can I implement such bubble on input fields in JavaFX. I’ve found some source code to get the caps lock state.
boolean isOn=Toolkit.getDefaultToolkit().getLockingKeyState(KeyEvent.VK_CAPS_LOCK);
scene.setOnKeyReleased( event -> {
if ( event.getCode() == KeyCode.CAPS ) {
System.out.println("Capslock pressed");
System.out.println("Capslock state: " + isOn);
}
});
But my problem is how to implement the bubble alert on text field.
Here you can see what I have to do.
It would be helpful if you suggest me some possible ways as I’m new in JavaFX. Is there any JavaFX library to do such bubble alert on input fields?
It sounds like you have figured out how to get the input state you could try something like this for the listener
public class Main extends Application {
private Label capsLabel = new Label("Caps is ON");
private boolean capsIsOn;
#Override
public void start(Stage stage) {
System.out.println(Toolkit.getDefaultToolkit().getLockingKeyState(20));
//Try adding this line to get state on startup
capsLabel.setVisible(Toolkit.getDefaultToolkit().getLockingKeyState(20));
TextField textField = new TextField();
//Also try adding this line and to check again so when the field
//is selected it will check again
textField.setOnMouseClicked(event -> capsLabel.setVisible(Toolkit.getDefaultToolkit().getLockingKeyState(20)));
textField.setOnKeyReleased(keyEvent -> {
if(keyEvent.getCode().toString().equals("CAPS")){
capsIsOn = !capsIsOn;
capsLabel.setVisible(capsIsOn);
}
});
VBox vBox = new VBox();
vBox.getChildren().addAll(textField, capsLabel);
stage = new Stage();
stage.setScene(new Scene(vBox));
stage.show();
}
public static void main(String[] args) { launch(args); }
}
Alternatively you could set this on a timer and have it constantly checking personally I don't like the idea of constant use of computer resources but its not my project.
JavaFX doesn’t have any way to detect CapsLock. In theory, you could install a Scene-wide listener, but that wouldn’t catch when the state changes while other applications have focus.
Mixing AWT/Swing and JavaFX is perilous, because each has its own thread on which nearly all of its methods must be executed. Since CapsLock needs to be polled anyway, it makes sense to use javax.swing.Timer, which both executes an action regularly and ensures that action is run in the proper thread (the AWT event dispatch thread):
BooleanProperty capsLockOn = new SimpleBooleanProperty();
EventQueue.invokeLater(() -> {
Timer timer = new Timer(500, e -> {
boolean state = Toolkit.getDefaultToolkit().getLockingKeyState(
KeyEvent.VK_CAPS_LOCK);
Platform.runLater(() -> capsLockOn.set(state));
});
timer.start();
Platform.runLater(() -> {
Window window = passwordField.getScene().getWindow();
window.setOnShown(e -> EventQueue.invokeLater(timer::restart));
window.setOnHidden(e -> EventQueue.invokeLater(timer::stop));
});
});
Region message = new BorderPane(new Label("Caps Lock is on"));
message.setStyle(
"-fx-background-color: #f4f4f4;" +
"-fx-border-color: black;" +
"-fx-border-width: 1px;" +
"-fx-padding: 1em 1em 0.75em 1em;" +
"-fx-shape: 'M 0 10 h 20 l 10 -10 l 10 10 h 150 v 90 h -190 z';"
);
Popup capsLockWarning = new Popup();
capsLockWarning.getContent().add(message);
capsLockOn.addListener((o, wasOn, on) -> {
if (on) {
Point2D location =
passwordField.localToScreen(-15, passwordField.getHeight());
capsLockWarning.show(passwordField,
location.getX(), location.getY());
} else {
capsLockWarning.hide();
}
});