JavaFX strange menu accelerator behaviour - java

I noticed a really strange behaviour with menu accelerators on the JavaFX (system) menu. Some key combinations don't work at all and others are interpreted as the wrong key.
For instance, when you use the CMD+CLEAR key as accelerator, it gets changed as the CMD+Page Down down key (both in the menu text and in the actual response to a keyboard event). The '-' (MINUS) key doesn't work well at all and while, CMD + - shows up well in the menu, it never gets triggered when you press the keys. When on the other hand you add the ALT key (i.e., you press CMD+ALT+-), the CMD- events does get triggered (but not the CMD+ALT+- one).
Any idea what's going on, or is this a (know) bug?
I have created below sample program to illustrate this.
Note: I'm on a Mac.
package menuaccelerator;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MenuAccelerator extends Application {
// Counter for the event number
int eventNr = 0;
#Override
public void start(Stage primaryStage) {
// Text Area for displaying the events
TextArea events = new TextArea();
events.setEditable(false);
events.setOnKeyPressed((e) -> {
System.out.println("Key Event(" + ++eventNr + "): " + e);
events.appendText((eventNr == 1 ? "" : "\n") + "KeyCode(" + eventNr + "): " + e.getCode());
e.consume();
});
// Menu Bar & Menu
MenuBar menuBar = new MenuBar();
menuBar.setUseSystemMenuBar(true);
Menu menu = new Menu("Accelerator Test");
menuBar.getMenus().add(menu);
// Build menu
for (String item : new String[]{"Shortcut+PLUS", "Shortcut+Alt+PLUS",
"Shortcut+MINUS", "Shortcut+Alt+MINUS",
"Shortcut+PAGE__UP", "Shortcut+Alt+PAGE__UP",
"Shortcut+PAGE__DOWN", "Shortcut+Alt+PAGE__DOWN",
"Shortcut+CLEAR"}) {
MenuItem menuItem = new MenuItem(item);
menuItem.setAccelerator(KeyCombination.keyCombination(item));
menuItem.setOnAction((ActionEvent e) -> {
System.out.println("Menu Event(" + ++eventNr + "): " + e);
events.appendText((eventNr == 1 ? "" : "\n") + "Menu Event(" + eventNr + "): " + ((MenuItem) e.getSource()).getText());
e.consume();
});
menu.getItems().add(menuItem);
}
// Create scene
StackPane root = new StackPane();
VBox vbox = new VBox(10);
vbox.setAlignment(Pos.CENTER);
vbox.getChildren().add(events);
root.getChildren().add(vbox);
root.getChildren().add(menuBar);
Scene scene = new Scene(root, 300, 200);
// Display scene
primaryStage.setTitle("Accelerator Test");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Related

JavaFX Slider Tick Marks Initialized via CSS Disappear

I am using CSS to configure my JavaFX Sliders, then applying the style in code with:
cssSlider.getStyleClass().add("slider-style");
When I first open my window, the tick marks are present on the CSS configured Slider(s). When I close and reopen the window, the tick marks are no longer present.
This following example demonstrates the anomaly using 2 Sliders, one configured directly, the other via CSS. Click the button to hide the window for 2 seconds. Notice that the Slider in which I directly configure the attributes works fine after hiding and re-showing, but the CSS configured Slider loses its tick marks after hiding and re-showing.
Does anyone have any ideas why showing, hiding, and re-showing the window causes the tick marks to vanish from the CSS configured Slider? Am I doing something wrong, or is this a JavaFX bug?
sample.css:
.slider-style {
-fx-show-tick-marks: true;
-fx-snap-to-ticks: true;
-fx-major-tick-unit: 5;
-fx-minor-tick-count: 5;
}
CssExample.java:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.URL;
/**
* This simple example demonstrates that JavaFX Sliders configured with CSS only show their tick marks the first time
* they are shown. If the Slider is hidden, then shown again, the tick marks are gone forever.
*/
public class CssExample extends Application {
#Override
public void start(Stage stage) throws InterruptedException, IOException {
Group root = new Group();
Scene scene = new Scene(root, 400, 200);
stage.setScene(scene);
stage.setTitle("Slider Sample");
scene.setFill(Color.BLACK);
GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 10, 10, 10));
grid.setVgap(10);
grid.setHgap(70);
scene.setRoot(grid);
int rowNumber = 1;
Label directLabel = new Label("Slider from attribute assignment");
GridPane.setConstraints(directLabel, 1, rowNumber++);
grid.getChildren().add(directLabel);
Slider directSlider = new Slider();
GridPane.setConstraints(directSlider, 1, rowNumber++);
grid.getChildren().add(directSlider);
directSlider.setShowTickMarks(true);
directSlider.setSnapToTicks(true);
directSlider.setMajorTickUnit(5);
directSlider.setMinorTickCount(5);
Label cssLabel = new Label("Slider from CSS (tick marks disappear after hidden)");
GridPane.setConstraints(cssLabel, 1, rowNumber++);
grid.getChildren().add(cssLabel);
Slider cssSlider = new Slider();
GridPane.setConstraints(cssSlider, 1, rowNumber++);
grid.getChildren().add(cssSlider);
URL url = getClass().getResource("sample.css");
String cssString = url.toExternalForm();
scene.getStylesheets().add(cssString);
cssSlider.getStyleClass().add("slider-style");
Button button = new Button("Hide for 2 Seconds");
GridPane.setConstraints(button, 1, rowNumber++);
grid.getChildren().add(button);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
stage.hide();
stage.show();
}
});
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
is this a JavaFX bug?
Yes.
See: https://github.com/openjdk/jfx/blob/fdc88341f1df8fb9c99356ada54b25124b77ea6e/modules/javafx.controls/src/main/java/javafx/scene/control/skin/SliderSkin.java#L398
It is a bug in the internal implementation of the setShowTickMarks method of SliderSkin (verified in JavaFX 18.0.1).
Test case:
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.io.IOException;
public class CssExample extends Application {
private static final String CSS = // language=CSS
"""
.slider-style {
-fx-show-tick-marks: true;
-fx-snap-to-ticks: true;
-fx-major-tick-unit: 5;
-fx-minor-tick-count: 5;
}
""";
private static final String CSS_INLINE = "data:text/css," + CSS;
#Override
public void start(Stage stage) throws InterruptedException, IOException {
Platform.setImplicitExit(false);
Slider cssSlider = new Slider();
cssSlider.showTickMarksProperty().addListener((observable, oldValue, newValue) ->
System.out.println(cssSlider.showTickMarksProperty())
);
cssSlider.getStyleClass().add("slider-style");
PauseTransition hideAnimation = new PauseTransition(Duration.seconds(2));
hideAnimation.setOnFinished(e -> stage.show());
Button hideWindow = new Button("Hide for 2 Seconds");
hideWindow.setOnAction(e -> {
stage.hide();
hideAnimation.play();
});
Button closeApp = new Button("Close app");
closeApp.setOnAction(e -> Platform.exit());
VBox layout = new VBox(
10,
cssSlider, hideWindow, closeApp
);
layout.setPadding(new Insets(10));
layout.setPrefSize(400, 120);
Scene scene = new Scene(layout);
scene.getStylesheets().add(CSS_INLINE);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Test output:
BooleanProperty [bean: Slider#132908b9[styleClass=slider slider-style], name: showTickMarks, value: true]
BooleanProperty [bean: Slider#132908b9[styleClass=slider slider-style], name: showTickMarks, value: false]
BooleanProperty [bean: Slider#132908b9[styleClass=slider slider-style], name: showTickMarks, value: true]
It switches showTicks from true to false, and back to true, which triggers the bug.
In the current implementation for the setShowTicks method:
private void setShowTickMarks(boolean ticksVisible, boolean labelsVisible) {
showTickMarks = (ticksVisible || labelsVisible);
Slider slider = getSkinnable();
if (showTickMarks) {
if (tickLine == null) {
tickLine = new NumberAxis();
tickLine.setAutoRanging(false);
tickLine.setSide(slider.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (slider.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
tickLine.setUpperBound(slider.getMax());
tickLine.setLowerBound(slider.getMin());
tickLine.setTickUnit(slider.getMajorTickUnit());
tickLine.setTickMarkVisible(ticksVisible);
tickLine.setTickLabelsVisible(labelsVisible);
tickLine.setMinorTickVisible(ticksVisible);
// add 1 to the slider minor tick count since the axis draws one
// less minor ticks than the number given.
tickLine.setMinorTickCount(Math.max(slider.getMinorTickCount(),0) + 1);
if (slider.getLabelFormatter() != null) {
tickLine.setTickLabelFormatter(stringConverterWrapper);
}
getChildren().clear();
getChildren().addAll(tickLine, track, thumb);
} else {
tickLine.setTickLabelsVisible(labelsVisible);
tickLine.setTickMarkVisible(ticksVisible);
tickLine.setMinorTickVisible(ticksVisible);
}
}
else {
getChildren().clear();
getChildren().addAll(track, thumb);
// tickLine = null;
}
getSkinnable().requestLayout();
}
The first time it shows the ticks it will do this:
getChildren().clear();
getChildren().addAll(tickLine, track, thumb);
Then, when the ticks are hidden, it will do this:
getChildren().clear();
getChildren().addAll(track, thumb);
Then, when the ticks are supposed to be shown again, the tickLine is not added back to the children, so it never shows the ticks again.

Pressed state gets stuck after drag and drop

I believe I've discovered an issue with the pressed state of a JavaFX node after a drag and drop operation. The pressed state gets "stuck" as true after the drop.
The following example demonstrates the issue. Try and drag the black circle to anywhere in the window, observe it remains red after the drop.
I've visually highlighted the issue by coloring the circle red whilst pressed. This is usually done with :pressed CSS pseudo class, but I've done it like this for sake of a self contained example.
import javafx.application.Application;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class StuckPressedState extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Circle circle = new Circle(100, 100, 20);
circle.setOnDragDetected(event -> {
Dragboard db = circle.startDragAndDrop(TransferMode.ANY);
ClipboardContent clipBoardContent = new ClipboardContent();
clipBoardContent.putString("foo");
db.setContent(clipBoardContent);
});
circle.pressedProperty().addListener((obs, oldValue, newValue) -> {
System.out.println("Pressed " + newValue);
if (Boolean.TRUE.equals(newValue)) {
circle.setFill(Color.RED);
} else {
circle.setFill(Color.BLACK);
}
});
circle.setOnMouseReleased(event -> {
System.out.println(event);
});
Pane root = new Pane(circle);
root.setOnDragOver(event -> {
event.acceptTransferModes(TransferMode.ANY);
});
root.setOnDragDropped(event -> {
System.out.println("Dropped " + event.getDragboard().getContent(DataFormat.PLAIN_TEXT));
circle.pseudoClassStateChanged(PseudoClass.getPseudoClass("pressed"), false);
event.setDropCompleted(true);
});
root.setPrefSize(200, 200);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
Things I've tried
detect mouse released on circle - this is not triggered
manually clear the psuedo class with circle.pseudoClassStateChanged(PseudoClass.getPseudoClass("pressed"), false); - this works for CSS styling but circle does not go back to pressed again.
different platforms - Redhat 7, Redhat 8, Windows 10, JavaFX 11.0.2 / 17.0.2

Java FX not recognizing KeyCode.UP or KeyCode.Down when up and down arrows pressed (MacBook Pro 18 - Mojave 10.14.5 )

I am currently learning Java, and am working on event driven programming with Java FX. This program makes a circle, and increases its size via a few different methods (Buttons, Mouse Buttons, Up and Down Arrow).
The buttons, and mouse clicks work fine, but the up and down arrows do not. It seems that when I press them, no KeyCodes are received. I have tried changing this to other keys, and it works fine.
I actually copied this program out of the book for practice, and it is identical to my code...
I am using a MacBook Pro 18, on Mojave 10.14.5. Java 10, IntelliJ Community Edition 2019.1.
Code below, any help would be appreciated.
package testing2;
import javafx.application.Application; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.input.KeyCode; import javafx.scene.input.MouseButton; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.stage.Stage;
public class ControlCircleWithMouseAndKey extends Application {
private CirclePane circlePane = new CirclePane();
#Override
public void start(Stage primaryStage) {
//hold 2 buttons in an Hbos
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setAlignment(Pos.CENTER);
Button btEnlarge = new Button("Enlarge");
Button btShrink = new Button("Shrink");
hBox.getChildren().addAll(btEnlarge, btShrink);
//Create and register button click handlers
btEnlarge.setOnAction(e -> circlePane.enlarge());
btShrink.setOnAction(e -> circlePane.shrink());
BorderPane borderPane = new BorderPane();
borderPane.setCenter(circlePane);
borderPane.setBottom(hBox);
borderPane.setAlignment(hBox, Pos.CENTER);
Scene scene = new Scene(borderPane, 200, 200);
primaryStage.setTitle("Change Circle");
primaryStage.setScene(scene);
primaryStage.show();
//Register the mouse clicks enlarge and shrink
circlePane.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
circlePane.enlarge();
} else if (e.getButton() == MouseButton.SECONDARY) {
circlePane.shrink();
}
});
//Register keys to englarge and shrink
scene.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.UP) {
circlePane.enlarge();
} else if (e.getCode() == KeyCode.DOWN) {
circlePane.shrink();
} else {
System.out.println(e.getCode());
}
});
} }
class CirclePane extends StackPane {
private Circle circle = new Circle(50);
public CirclePane() {
getChildren().add(circle);
circle.setStroke(Color.BLACK);
circle.setFill(Color.WHITE);
}
public void enlarge() {
circle.setRadius(circle.getRadius() + 2);
}
public void shrink() {
circle.setRadius(circle.getRadius() > 2 ? circle.getRadius() - 1 : circle.getRadius());
} }
You have used 'scene' to register the key presses. Shouldn't that be 'borderPane' (it all depends on focus I guess)?

JavaFX TreeView select(TreeItem<Object>) not working as expected

in the past I read lot's of hints, Tipps'n Tricks in this Forum.
Now I try to ask my first question here :-)
I'm a bit new to JavaFX, but not new to Java. I'm trying to port an application from Swing to JavaFX so it will be more fancy and customizable.
The main Task of this program is to show two trees and compare them.
To make it easier I implemented in the old application a listener and celleditor to visualize the differences and when you select one Item in the one tree, it tries to find the corresponding Item in the other one. If it is not visible, it will be visible.
One screenshot to show the current application:
After the start it Looks like this and
When selecting on the left, autoselect on the right and vice versa:
Old application
Now I try to implement the same Feature in JavaFX:
But using this code:
public void setOtherTree(TreeView<CompareNode> otherTree) {
if (log.isTraceEnabled()) {
log.trace("setOtherTree("+otherTree+")");
}
this.otherTree = otherTree;
tree.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
tree.getSelectionModel().selectedItemProperty().addListener((observable,oldValue,newValue) -> selectInOtherTree(newValue));
}
public void selectInOtherTree(TreeItem<CompareNode> newValue) {
if (log.isDebugEnabled()) {
log.debug(logPrefix + " " + String.format("selectInOtherTree:newValue".replaceAll(", ", "=%s, ") + "=%s", newValue));
}
otherTree.getSelectionModel().clearSelection();
otherTree.getSelectionModel().select(newValue);
}
It always selects the item on an index base:
Problem in JavaFX
Am I missing something or is it a bug in JDK 1.8u45 (which I'm currently using)?
The class CompareNode overwrites toString, equals and hash
As I see, I need 10 reputations to post Images :-(, so I hope my words will explain it detailed enough or you can follow the links. Sorry for that
Any help is well appreciated.
Kind regards
Andreas
2015-07-10 UPDATE:
The two screenshots showing the old application:
And the screenshot of the new application:
Thanks for reputations :-)
2015-09-22 UPDATE
Meanwhile I create a Little demo app to demonstrate it.
TreeItemString.java
package application;
import javafx.scene.control.TreeItem;
public class TreeItemString extends TreeItem<String> {
public TreeItemString(String value) {
this.setValue(value);
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
return this.getValue().equals(((TreeItemString) obj).getValu());
}
#Override
public String toString() {
return this.getValue();
}
#Override
public int hashCode() {
return this.getValue().hashCode();
}
}
MainApp.java
package application;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public void selectInOtherTree(TreeItem<String> toSelect, TreeView otherTree, TreeView thisTree) {
System.out.println("Othertree : " + toSelect);
System.out.println("Me : " + thisTree.getRow(toSelect));
System.out.println("Other: " + otherTree.getRow(toSelect));
otherTree.getSelectionModel().select(toSelect);
}
public TreeItemString newRoot(TreeView<String> tree, Label label, String labelPrefix) {
//Create TreeItems for the Hierarchy of the TreeView
TreeItemString royalRoot = new TreeItemString("Queen Elizabeth - Prince Philip");
TreeItemString Charlie = new TreeItemString("Prince Charles - Princess Diana");
TreeItemString Annie = new TreeItemString("Princess Anne - Mark Phillips");
TreeItemString Andy = new TreeItemString("Prince Andrew - Sarah Ferguson");
TreeItemString Eddie = new TreeItemString("Prince Edward - Sophie");
//Populate the TreeItem to be used as the root with the other TreeItems
royalRoot.getChildren().addAll(Charlie, Annie, Andy, Eddie);
//Populate the other TreeItems with more TreeItems
//to build the family tree
Charlie.getChildren().addAll(
new TreeItemString("Prince William"),
new TreeItemString("Prince Henry"));
Annie.getChildren().addAll(
new TreeItemString("Peter Phillips"),
new TreeItemString("Zara Phillips"));
Andy.getChildren().addAll(
new TreeItemString("Princess Beatrice"),
new TreeItemString("Princess Eugenie"));
Eddie.getChildren().addAll(
new TreeItemString("Lady Louise"),
new TreeItemString("Viscount Severn"));
return royalRoot;
}
#Override
public void start(Stage primaryStage) {
final String leftTreeLabelPrefix= "Selected Tree Item From left Tree: \n";
final String rightTreeLabelPrefix = "Selected Tree Item From right Tree: \n";
//Use HBOX and VBOX layout panes to space out the controls
//in a single row
HBox treeBox = new HBox();
VBox labelBox = new VBox(30);
HBox controlBox = new HBox(10);
//Create labels to highlight the selected items from the TreeViews
final Label leftTreeLabel = new Label(leftTreeLabelPrefix);
final Label rightTreeLabel = new Label(rightTreeLabelPrefix);
//Create and empty TreeView
TreeView<String> leftTree = new TreeView<String>();
//Use the setRoot method to set the root TreeItem
// duckTree.setRoot(duckRoot);
leftTree.setRoot(newRoot(leftTree,leftTreeLabel, "Selected Tree Item From left Tree: \n"));
//Create a TreeView using the root TreeItem
TreeView<String> rightTree = new TreeView<String>();
rightTree.setRoot(newRoot(rightTree,rightTreeLabel, "Selected Tree Item From right Tree: \n"));
rightTree.getSelectionModel().selectedItemProperty().addListener((observable,oldValue,newValue) -> selectInOtherTree(newValue, leftTree, rightTree));
leftTree.getSelectionModel().selectedItemProperty().addListener((observable,oldValue,newValue) -> selectInOtherTree(newValue, rightTree,leftTree));
//Add the TreeViews to the HBox
treeBox.getChildren().add(leftTree);
treeBox.getChildren().add(rightTree);
//Add the labels to the VBox
labelBox.getChildren().add(leftTreeLabel);
labelBox.getChildren().add(rightTreeLabel);
//Add the HBox and VBox to another HBox to
//position the layout panes
controlBox.getChildren().add(treeBox);
controlBox.getChildren().add(labelBox);
//Add the main HBOX layout pane to the scene
Scene scene = new Scene(controlBox, 800, 250);
Platform.setImplicitExit(false);
Thread backGroundThread = new Thread(() -> {
try {
Thread.sleep(5000);
System.out.println("Select left Viscount Severn");
leftTree.getRoot().setExpanded(true);
leftTree.getRoot().getChildren().get(1).setExpanded(true);
leftTree.getRoot().getChildren().get(2).setExpanded(true);
leftTree.getRoot().getChildren().get(3).setExpanded(true);
TreeItemString toSearch = new TreeItemString("Prince Edward - Sophie");
System.out.println("true:false for : " + toSearch);
System.out.println("true:false : " + leftTree.getRoot().getChildren().contains(toSearch));
System.out.println("indexOf: " + leftTree.getRoot().getChildren().indexOf(toSearch));
System.out.println("get: " + leftTree.getRoot().getChildren().get(leftTree.getRoot().getChildren().indexOf(toSearch)));
System.out.println("getRow: " + leftTree.getRow(leftTree.getRoot().getChildren().get(leftTree.getRoot().getChildren().indexOf(toSearch))));
leftTree.getSelectionModel().select(new TreeItemString("Princess Anne - Mark Phillips"));
Thread.sleep(5000);
System.out.println("Select right Princess Beatrice");
rightTree.getSelectionModel().select(new TreeItemString("Princess Beatrice"));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
backGroundThread.setDaemon(true);
backGroundThread.start();
//Show the form
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

Wait for and then receive textfield input without freezing GUI

I hope I'm not duplicating a question, but I couldn't find one specifically for my issue.
I'm developing a small math flash card application, using JavaFX to create the GUI. The program should runs as follow:
user selects settings, then presses start button.
gui displays question and textfield for user input.
user inputs answer within X amount of seconds or gui automatically move onto the next question - alternatively, user can move onto next question immediately by pressing next button.
GUI displays score and average.
The problems is getText() from user textfield is processed as soon as start button is pressed, without giving the user a chance to enter an answer. How do I make the program wait for X amount of seconds or for the next button to be clicked before processing the user's answer? Here's my code:
//start button changes view and then runs startTest()
start.setOnAction(e -> {
setLeft(null);
setRight(null);
setCenter(test_container);
running_program_title.setText(getDifficulty().name() + " Test");
buttons_container.getChildren().clear();
buttons_container.getChildren().addAll(next, quit, submit);
startTest();
});
Here is the problem code... at least how I see it.
//startTest method calls askAdd() to ask an addition question
void startTest() {
int asked = 0;
int correct = 0;
while (asked < numberOfQuestions) {
if(askAdd()){
correct++;
asked++;
}
}
boolean askAdd() {
int a = (int) (Math.random() * getMultiplier());
int b = (int) (Math.random() * getMultiplier());
//ask question
question.setText("What is " + a + " + " + b + "?");
//code needed to pause method and wait for user input for X seconds
//retrieve user answer and return if its correct
return answer.getText().equalsIgnoreCase(String.valueOf(a+b));
}
I've tried using Thread.sleep(X) but that freezes the gui for however long I specify and then goes through the addAsk() method and the loop before going to the test screen. (I know because I had the program set up to print the questions and answer input to the console). It shows the last question and that's all.
I didn't include the next button code because I can't get the gui to go to the test page anyway.
Any help on any of the code is appreciated.
This can be achieved by various methods.
PauseTransition is one of the many apt solution present. It waits for X time interval and then performs a Task. It can start, restart, stop at any moment.
Here is an example of how it can used to achieve a similar result.
Complete Code
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.stream.IntStream;
public class Main extends Application {
int questionIndex = 0;
int noOfQuestions = 10;
#Override
public void start(Stage stage) {
VBox box = new VBox(10);
box.setPadding(new Insets(10));
Scene scene = new Scene(new ScrollPane(box), 500, 200);
ObservableList<String> questions =
FXCollections.observableArrayList("1) Whats your (full) name?",
"2) How old are you?",
"3) Whats your Birthday?",
"4) What starsign does that make it?",
"5) Whats your favourite colour?",
"6) Whats your lucky number?",
"7) Do you have any pets?",
"8) Where are you from?",
"9) How tall are you?",
"10) What shoe size are you?");
ObservableList<String> answers = FXCollections.observableArrayList();
final PauseTransition pt = new PauseTransition(Duration.millis(5000));
Label questionLabel = new Label(questions.get(questionIndex));
Label timerLabel = new Label("Time Remaining : ");
Label time = new Label();
time.setStyle("-fx-text-fill: RED");
TextField answerField = new TextField();
Button nextQuestion = new Button("Next");
pt.currentTimeProperty().addListener(new ChangeListener<Duration>() {
#Override
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
time.setText(String.valueOf(5 - (int)newValue.toSeconds()));
}
});
box.getChildren().addAll(questionLabel, answerField, new HBox(timerLabel, time), nextQuestion);
nextQuestion.setOnAction( (ActionEvent event) -> {
answers.add(questionIndex, answerField.getText());
//Check if it is the last question
if(questionIndex == noOfQuestions-1) {
pt.stop();
box.getChildren().clear();
IntStream.range(0, noOfQuestions).forEach(i -> {
Label question = new Label("Question : " + questions.get(i));
question.setStyle("-fx-text-fill: RED");
Label answer = new Label("Answer : " + answers.get(i));
answer.setStyle("-fx-text-fill: GREEN");
box.getChildren().addAll(question, answer);
});
}
// All other time
else {
//Set new question
questionLabel.setText(questions.get(++questionIndex));
answerField.clear();
pt.playFromStart();
}
});
pt.setOnFinished( ( ActionEvent event ) -> {
nextQuestion.fire();
});
pt.play();
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
For the timer you should (IMO) use a Timeline. Here is an example:
public class MultiGame extends Application {
ProgressBar progressBar;
final int allowedTime = 5; //seconds
final DoubleProperty percentOfTimeUsed = new SimpleDoubleProperty(0);
final Timeline timer =
new Timeline(
new KeyFrame(
Duration.ZERO, new KeyValue(percentOfTimeUsed, 0)),
new KeyFrame(
Duration.seconds(allowedTime), new KeyValue(percentOfTimeUsed, 1))
);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
progressBar = new ProgressBar();
progressBar.progressProperty().bindBidirectional(percentOfTimeUsed);
root.setTop(progressBar);
Button answer = new Button("Answer");
answer.setOnAction(ae -> restart());// the on answer handler
Button skip = new Button("Skip");
skip.setOnAction(ae -> restart());// the skip question handler
HBox mainContent = new HBox(15,
new Label("Your Question"), new TextField("The answer"), answer, skip);
root.setCenter(mainContent);
timer.setOnFinished(ae -> restart());// the end of timer handler
primaryStage.setScene(new Scene(root));
primaryStage.show();
restart();
}
void restart() { timer.stop(); timer.playFromStart(); }
void pause() { timer.pause(); }
void resume() { timer.play(); }
}
You just need to capture the text from the input in between the starting of the timeline and the restart method.

Categories

Resources