Progress bar not updating in javaFX - javafx

I'm facing the following problem: My progressBar is not updating in JavaFx.
I have written the following code:
public class FXMLDocumentController implements Initializable {
static final int max = 1000000;
Task task = new Task<Void>() {
#Override public Void call() {
for (int i = 1; i <= max; i++) {
updateProgress(i, max);
}
return null;
}
};
#FXML
public AnchorPane root;
#FXML
public ProgressBar progressWater;
#FXML
public ProgressBar levelGas;
#Override
public void initialize(URL url, ResourceBundle rb) {
if (!Main.isSplashLoaded) {
loadSplashScreen();
}
}
private void loadSplashScreen() {
try {
Main.isSplashLoaded = true;
StackPane pane = FXMLLoader.load(getClass().getResource(("SplashFXML.fxml")));
root.getChildren().setAll(pane);
FadeTransition fadeIn = new FadeTransition(Duration.seconds(3), pane);
fadeIn.setFromValue(0);
fadeIn.setToValue(1);
fadeIn.setCycleCount(1);
FadeTransition fadeOut = new FadeTransition(Duration.seconds(3), pane);
fadeOut.setFromValue(1);
fadeOut.setToValue(0);
fadeOut.setCycleCount(1);
fadeIn.play();
fadeIn.setOnFinished((e) -> {
fadeOut.play();
});
fadeOut.setOnFinished((e) -> {
AnchorPane paneMain;
try {
paneMain = FXMLLoader.load(getClass().getResource("/sample/FXMLDocument.fxml"));
root.getChildren().setAll(paneMain);
paneMain.setOpacity(0);
FadeTransition fadeInmain = new FadeTransition(Duration.seconds(3), paneMain);
fadeInmain.setFromValue(0);
fadeInmain.setToValue(1);
fadeInmain.play();
fadeInmain.setOnFinished((ex)-> {
// progressWater.setProgress(0.5);
double maxlev = progressWater.getPrefWidth();
progressWater.progressProperty().bind(fadeInmain.fromValueProperty());
/**
* Action goes here
*/
new Thread(){
public void run() {
for (double i = 0.0; i < maxlev; i++){
final double step = i;
Platform.runLater(() ->
progressWater.setProgress( step / 10 ));
System.out.printf("Complete: %02.2f%n", step /10);
try {
Thread.sleep(1000);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}.start();
progressWater.progressProperty().unbind();
/**
* Action ends here
*/
});
} catch (IOException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}
});
} catch (IOException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
When executing the code, the thread updates, and it prints in the console the completion, however, the progressWater bar does not update in the UI.
Below is my FXML document:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.effect.GaussianBlur?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="MainPane" fx:id="root" prefHeight="322.0" prefWidth="232.0" style="-fx-background-color: #000000;" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.FXMLDocumentController">
<children>
<ImageView fitHeight="322.0" fitWidth="232.0" pickOnBounds="true">
<image>
<Image url="#background.jpg" />
</image>
</ImageView>
<ProgressBar fx:id="progressWater" layoutX="-80.0" layoutY="170.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="5.0" prefWidth="200.0" progress="1.0" rotate="90.0">
<effect>
<GaussianBlur radius="2.0" />
</effect>
</ProgressBar>
<ProgressBar fx:id="levelGas" layoutX="113.0" layoutY="170.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="5.0" prefWidth="200.0" progress="0.0" rotate="90.0">
<effect>
<GaussianBlur radius="2.0" />
</effect>
</ProgressBar>
</children>
</AnchorPane>
Any ideas about what am I doing wrong?

It occurs to me immediately that you are before running the thread that spins and updates the progress..
progressWater.progressProperty().bind(fadeInmain.fromValueProperty());
You're binding the progress property to something else entirely that you aren't updating seemingly, have you tried removing this binding to allow the property to float on its own and not be tied to the propagating events from the fadein property?

Related

JavaFX (Scene builder) arrow keys OnKeyPressed

I'm in the middle of a school project, but I'm stuck...
In Scene Builder there is an OnKeyPressed and an OnKeyReleased 'method', and when I assign my own method (See code further down) I want to check if I'm pressing the left or right arrow key.
The problem is, OnKeyReleased works fine, but the OnKeyPressed only works when I hold down shift!
Does anyone know the cause of this issue? and if so, how it can be fixed/worked around?
(I'm on a MacBook, could this have anything to do with it?)
Thanks :)
Method for OnKeyPressed:
#FXML
private void Input(KeyEvent e)
{
if(e.getCode().equals(KeyCode.LEFT))
{
System.out.println("Pressed left arrow key");
}
}
FullCode:
package seasweeper.seasweeper;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class SecondaryController implements Initializable {
#FXML
private Label Label_Character;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
private void switchToPrimary() throws IOException
{
App.setRoot("primary");
}
#FXML
private void Input(KeyEvent e)
{
if(e.getCode().equals(KeyCode.LEFT))
{
System.out.println("Pressed left");
}
System.out.println("KeyCode: " + e.getCode());
}
}
FXML code:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" onKeyPressed="#moveLeft" onKeyReleased="#moveLeft" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seasweeper.seasweeper.PacificController">
<children>
<Button layoutX="422.0" layoutY="64.0" mnemonicParsing="false" onAction="#switchToPrimary" prefHeight="27.0" prefWidth="164.0" text="Switch to Primary" />
<Label layoutX="489.0" layoutY="31.0" text="Pacific" />
<Label fx:id="Label_Character" layoutX="273.0" layoutY="192.0" text="Character" />
</children>
</AnchorPane>
Other Scene Controller:
package seasweeper.seasweeper;
import java.io.IOException;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class PrimaryController {
#FXML
private Button Button_Arctic;
#FXML
private Button Button_Southern;
#FXML
private Button Button_Pacific;
#FXML
private Button Button_Atlantic;
#FXML
private Button Button_Indian;
#FXML
private Button Button_Pacific1;
#FXML
private void switchToPacific() throws IOException {
App.setRoot("pacific");
}
#FXML
private void switchToSouthern() throws IOException {
App.setRoot("southern");
}
#FXML
private void switchToAtlantic() throws IOException {
App.setRoot("atlantic");
}
#FXML
private void switchToIndian() throws IOException {
App.setRoot("indian");
}
#FXML
private void switchToArctic() throws IOException {
App.setRoot("arctic");
}
}
Other FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?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/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seasweeper.seasweeper.PrimaryController">
<children>
<Button fx:id="Button_Arctic" layoutX="226.0" layoutY="14.0" mnemonicParsing="false" onAction="#switchToArctic" prefHeight="77.0" prefWidth="148.0" text="Arctic" />
<Button fx:id="Button_Southern" layoutX="226.0" layoutY="309.0" mnemonicParsing="false" onAction="#switchToSouthern" prefHeight="77.0" prefWidth="148.0" text="Southern" />
<Button fx:id="Button_Pacific" layoutX="14.0" layoutY="162.0" mnemonicParsing="false" onAction="#switchToPacific" prefHeight="77.0" prefWidth="148.0" text="Pacific" />
<Button fx:id="Button_Atlantic" layoutX="226.0" layoutY="162.0" mnemonicParsing="false" onAction="#switchToAtlantic" prefHeight="77.0" prefWidth="148.0" text="Atlantic" />
<Button fx:id="Button_Indian" layoutX="438.0" layoutY="162.0" mnemonicParsing="false" onAction="#switchToIndian" prefHeight="77.0" prefWidth="148.0" text="Indian" />
<Button fx:id="Button_Shop" layoutX="556.0" layoutY="14.0" mnemonicParsing="false" prefHeight="30.0" prefWidth="30.0" text="\$" textFill="#009712" />
</children>
</AnchorPane>
On the same issue I changed OnKeyPressed from AnchorPane to some Button on that AnchorPane and OnKeyPressed works fine with arrows :)
#Override
public void initialize(URL location, ResourceBundle resources) {
someButton.addEventHandler(KeyEvent.KEY_PRESSED,
new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
switch (event.getCode().toString()) {
// your logic here
}
event.consume();
}
});
}
This one is a little tricky, but still relatively simple:
Node node = null;
node.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.KP_UP) { // KeyPressed_UP
// Do something
}
if (event.getCode() == KeyCode.KP_DOWN) { // KeyPressed_DOWN
// Do something
}
if (event.getCode() == KeyCode.KP_LEFT) { // KeyPressed_LEFT
// Do something
}
if (event.getCode() == KeyCode.KP_RIGHT) { // KeyPressed_RIGHT
// Do something
}
}
});
Remember to change "node" with your object (it could be a Scene, AnchorPane -or any kind of pane-, buttons, etc).

JavaFX loading a new fxml file into the same scene

I have a class named Test1Controller where a Scene is executed and in another class, Test2Controller, I want to load another FXML but in the same Scene. Can someone help me with this?
public class Test1Controller {
private static AnchorPane page;
public static Stage initialicePage() {
primaryStage = new Stage();
page = (AnchorPane) FXMLLoader.load(Principal.class.getResource("Test1.fxml"));
Scene scene = new Scene(page);
primaryStage.setScene(scene);
primaryStage.setTitle("Test1");
primaryStage.setResizable(true);
primaryStage.setMinHeight(700);
primaryStage.setMinWidth(824);
primaryStage.setMaximized(true);
primaryStage.show();
}
}
public class SwitchScene extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
public class FXMLDocumentController {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
try {
Parent root = FXMLLoader.load(getClass().getResource("SecondScreen.fxml"));
Scene dashboard=new Scene(root);
//This line gets the Stage Information
Stage window=(Stage)((Node)event.getSource()).getScene().getWindow();
window.setScene(dashboard);
window.show();
} catch (IOException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public class SecondScreenController {
public void MoveBack(ActionEvent event){
try {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene dashboard = new Scene(root);
//This line gets the Stage Information
//here we get the stage from event action and setting the root element in the scene and display scene with stage object (window) which is retrieved from action event
Stage window=(Stage)((Node)event.getSource()).getScene().getWindow();
window.setScene(dashboard);
window.show();
} catch (IOException ex) {
Logger.getLogger(SecondScreenController.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
//FXMLDocument.xml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" fx:controller="switchscene.FXMLDocumentController">
<children>
<Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
<Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" />
</children>
</AnchorPane>
////SecondScreen fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="switchscene.SecondScreenController">
<children>
<Button fx:id="btnMove" layoutX="269.0" layoutY="188.0" mnemonicParsing="false" onAction="#MoveBack" text="Move Back" />
</children>
</AnchorPane>

ListSelectionView not displaying contents of ObservableList

I have a ListSelectionView (from the ControlsFX library) in JavaFX and I need it to display a set of values when a particular button is clicked. It displays the values when I place the add the contents into it in the initialize method. But when I place the same code in the event action method of a button it doesn't display. I have another method which gets the Source items and Target items from the ListSelectionView and displays them. Even though the ListSelectionView isn't displaying anything, the getSourceItems() method returns a set of values which are then displayed.
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private Button button;
#FXML
private JFXListView<Label> listv;
#FXML
private ListSelectionView<Label> abc;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
label.setText("Hello World!");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
#FXML
private void handleToggle(MouseEvent event) {
ObservableList<Label> users = FXCollections.observableArrayList();
abc.setSourceHeader(new Label("Present"));
abc.setTargetHeader(new Label("Absent"));
for (int i=0; i<4;i++)
{
users.add(new Label("Name "+i));
}
abc.setSourceItems(users);
abc.setDisable(false);
for (int i=0; i<4;i++)
{
Label k = new Label("Hello"+i);
listv.getItems().add(k);
}
}
#FXML
private void handlenewNOde(MouseEvent event) {
ObservableList<Label> users1 =abc.getSourceItems();
System.out.println("Source items");
for(int i =0; i<users1.size(); i++)
{
Label k=users1.get(i);
System.out.println(k.getText());
}
System.out.println("Target items");
ObservableList<Label> users12 =abc.getTargetItems();
for(int i =0; i<users12.size(); i++)
{
Label k=users12.get(i);
System.out.println(k.getText());
}
}
}
.fxml :
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXListView?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import org.controlsfx.control.ListSelectionView?>
<AnchorPane id="AnchorPane" prefHeight="680.0" prefWidth="914.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.141" fx:controller="listselectionviewtryout.FXMLDocumentController">
<children>
<Button fx:id="button" layoutX="14.0" layoutY="21.0" onAction="#handleButtonAction" text="Click Me!" />
<Label fx:id="label" layoutX="17.0" layoutY="54.0" minHeight="16" minWidth="69" />
<JFXListView fx:id="listv" layoutX="131.0" layoutY="35.0" prefHeight="237.0" prefWidth="75.0" />
<JFXButton layoutX="413.0" layoutY="58.0" onMouseClicked="#handleToggle" text="Toggle Extension" />
<JFXButton layoutX="445.0" layoutY="134.0" onMouseClicked="#handlenewNOde" text="new Node" />
<ListSelectionView fx:id="abc" layoutX="226.0" layoutY="262.0" />
</children>
</AnchorPane>
Ignore the JFXListView.
Output:
Source items
Name 0
Name 1
Name 2
Name 3
Target items
Should
abc.setSourceItems(users);
be
abc.getSourceItems().addAll(users);

StringProperty and Switch Statement Javafx

My brain is about to explode. I am pretty new to java, so far self-taught but I really want to get this code to work. I am also not the best at making sense of the java docs.
I am trying to create a gameloop where the user can freely traverse the switch statement by clicking the buttons.
However, previously, when my gameloop method was called, it ran the code with the current settings without letting me use the buttons to traverse the switch statement.
I think this was because my "choice" variable did not have an action listener attached to it (because a listener will make the program sit on the item, waiting for something to happen?). Unfortunately, my choice variable was a String variable and you cannot attach an actionlistener to a String variable.
Now I tried using the StringProperty class to create a string object to be able to attach an action listener to it.
But now I don't even know how to use StringProperty with switch statement and I just feel like I am on the wrong track.
Does it make sense what I am trying to accomplish?
Can anybody help me create a gameloop where the user can freely traverse the switch statement by clicking the buttons?
♥♥♥!
Here is my code:
Controller:
package sample;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
import static java.lang.System.out;
public class Controller implements Initializable {
public String question;
public StringProperty choice = new SimpleStringProperty(this, "choice", "");
// this code starts when the XML file is loaded.
#Override
public void initialize(URL location, ResourceBundle resources) {
question = "0";
gameloop();
} public String getChoice() {
return choice.get();
}
public StringProperty choiceProperty() {
return choice;
}
public void setChoice(String choice) {
this.choice.set(choice);
}
// public String choice = "";
public Controller() {
}
// Here are the buttons trying to add an actionlistener to the choice object.
// Maybe wrong path to go
#FXML
public void button1() {
choiceProperty().addListener((v, oldValue, newValue) ->{setChoice("c1");});
}
#FXML
public void button2() {
choiceProperty().addListener((v, oldValue, newValue) ->{setChoice("c2");});
out.println(choice); //think this prints out where it is stored in the memory
}
#FXML
public void button3() {
choiceProperty().addListener((v, oldValue, newValue) ->{setChoice("c3");});
out.println(choice);
}
//this block of code does not work because of the switch (choice) statement.
//Where "c1","c2","c3" have incompatible a type error.
public void gameloop() {
switch (question) {
case "0":
switch (choice) {
case "c1":
out.println("you chose 1");
question = "1";
break;
case "c2":
out.println("you chose 2");
break;
case "c3":
out.println("you chose 3");
break;
}
case "1":
switch (choice) {
case "c1":
out.println("you chose a");
question = "0";
break;
case "c2":
out.println("you chose b");
break;
case "c3":
out.println("you chose c");
break;
}
}
}
}
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.effect.*?>
<?import javafx.scene.image.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<children>
<BorderPane layoutX="163.0" layoutY="82.0" prefHeight="200.0" prefWidth="200.0" style="-fx-background-color: black;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<bottom>
<GridPane BorderPane.alignment="CENTER">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button fx:id="button1" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#button1" onMouseClicked="#button1" style="-fx-background-color: black; -fx-border-color: grey;" text="Button" textFill="WHITE" />
<Button fx:id="button2" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#button2" style="-fx-background-color: black; -fx-border-color: grey;" text="Button" textFill="WHITE" GridPane.rowIndex="1" />
<Button fx:id="button3" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#button3" style="-fx-background-color: black; -fx-border-color: grey;" text="Button" textFill="WHITE" GridPane.rowIndex="2" />
</children>
</GridPane>
</bottom>
<center>
<TextArea fx:id="textArea" editable="false" prefHeight="200.0" prefWidth="200.0" promptText="..." style="-fx-border-color: grey;" wrapText="true" BorderPane.alignment="CENTER" />
</center>
<top>
<Label fx:id="label" text="Chapter 1" textFill="WHITE" BorderPane.alignment="CENTER" />
</top>
<left>
<ImageView fx:id="image1" fitHeight="75.0" fitWidth="75.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER" />
</left>
<right>
<ImageView fx:id="image2" fitHeight="75.0" fitWidth="75.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER" />
</right></BorderPane>
</children>
</AnchorPane>
Main:
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
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");
primaryStage.setScene(new Scene(root, 800, 500));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Registering listeners in the button event handlers is nonsense. Using listeners to modify the property they listen to is even worse. Passing a property as argument for a switch will not even compile.
In this case the smallest modification to make this work would be registering a single listener in the initialize method, calling the gameloop method from this listener and using the value stored in the property as argument for the switch.
#Override
public void initialize(URL location, ResourceBundle resources) {
question = "0";
gameloop();
choiceProperty().addListener((observable, oldValue, newValue) -> gameloop());
}
...
#FXML
private void button1() {
setChoice("c1");
}
...
public void gameloop() {
switch (question) {
case "0":
switch (choice.get()) {
...
}
...
}
}
Note however you'll get a LOT of repeated code this way. Better come up with a data structure to store the game data instead of hardcoding everything.
E.g.
public class GameData {
private final ReadOnlyObjectWrapper<Question> question;
public ReadOnlyObjectProperty<Question> questionProperty() {
return question.getReadOnlyProperty();
}
public Question getQuestion() {
return question.get();
}
private final Map<Integer, Question> questions;
public Map<Integer, Question> getQuestions() {
return questions;
}
public GameData(int initialQuestion, Map<Integer, Question> questions) {
this.questions = questions;
this.question = new ReadOnlyObjectWrapper(questions.get(initialQuestion));
}
public void activateChoice(Choice choice) {
question.set(questions.get(choice.getNextQuestion()));
}
}
public class Question {
private final String text;
public String getText() {
return text;
}
public Question(String text, Choice... choices) {
this.text = text;
this.choices = Collections.unmodifiableList(new ArrayList(Arrays.asList(choices)));
}
private final List<Choice> choices;
public List<Choice> getChoices() {
return choices;
}
}
public class Choice {
private final String text;
private final int nextQuestion;
public Choice(int nextQuestion, String text) {
this.text = text;
this.nextQuestion = nextQuestion;
}
public String getText() {
return text;
}
public int getNextQuestion() {
return nextQuestion;
}
}
public class Controller {
private GameData gameData;
#FXML
private Button button1, button2, button3;
private Button[] buttons;
#FXML
private void initialize() {
buttons = new Button[]{button1, button2, button3};
}
public void setGameData(GameData gameData) {
this.gameData = gameData;
gameData.questionProperty().addListener((observable, oldValue, newValue) -> setQuestion(newValue));
setQuestion(gameData.getQuestion());
}
private void setQuestion(Question question) {
System.out.println(question.getText());
for (Button b : buttons) {
b.setVisible(false);
}
for (int i = 0, max = Math.min(buttons.length, question.getChoices().size()); i < max; i++) {
Button b = buttons[i];
Choice c = question.getChoices().get(i);
b.setUserData(c);
b.setText(c.getText());
b.setVisible(true);
}
}
#FXML
private void button(ActionEvent evt) {
Node source = (Node) evt.getSource();
Choice choice = (Choice) source.getUserData();
out.println("You've chosen " + choice.getText());
gameData.activateChoice(choice);
}
}
...
<Button fx:id="button1" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#button" style="-fx-background-color: black; -fx-border-color: grey;" text="Button" textFill="WHITE" />
<Button fx:id="button2" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#button" style="-fx-background-color: black; -fx-border-color: grey;" text="Button" textFill="WHITE" GridPane.rowIndex="1" />
<Button fx:id="button3" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#button" style="-fx-background-color: black; -fx-border-color: grey;" text="Button" textFill="WHITE" GridPane.rowIndex="2" />
...
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root = loader.load();
Map<Integer, Question> questions = new HashMap<>();
questions.put(0, new Question("0",
new Choice(1, "1"),
new Choice(0, "2"),
new Choice(0, "3")
));
questions.put(1, new Question("1",
new Choice(0, "a"),
new Choice(1, "b"),
new Choice(1, "c")
));
GameData data = new GameData(0, questions);
loader.<Controller>getController().setGameData(data);

ChoiceBox: freeze when removing selected item from another ChoiceBox

I want to use two ChoiceBoxes offering the same select items with the exception of the one selected by the other. However, after selecting options a few times, the entire java process becomes unresponsive.
Update: the issue does not occur if I use ComboBox instead of ChoiceBox. However, an explanation for why it happens would be interesting.
I have two ChoiceBoxes
#FXML private ChoiceBox<String> firstCB;
#FXML private ChoiceBox<String> secondCB;
that initially have the same selection options
firstCB.getItems().addAll(Charset.availableCharsets().keySet());
secondCB.getItems().addAll(Charset.availableCharsets().keySet());
and event listeners to remove the new selection from the other ChoiceBox's options and make the old option available again
firstCB.getSelectionModel().selectedItemProperty()
.addListener((observable, oldVal, newVal) -> {
secondCB.getItems().remove(newVal);
secondCB.getItems().add(oldVal);
});
the equivalent Swing code with JComboBoxes and the following event handler works
firstCB.addItemListener(itemEvent -> {
if (itemEvent.getStateChange() == ItemEvent.DESELECTED) {
secondCB.addItem((String) itemEvent.getItem());
} else if (itemEvent.getStateChange() == ItemEvent.SELECTED) {
secondCB.removeItem(itemEvent.getItem());
}
});
Full code:
class test.Main
public class Main extends Application {
private Parent rootPane;
#Override
public void start(Stage arg0) throws Exception {
Scene scene = new Scene(rootPane);
arg0.setScene(scene);
arg0.show();
}
#Override
public void init() throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/main.fxml"));
rootPane = loader.load();
loader.<View>getController().init();
}
public static void main(String[] args) {
launch(args);
}
}
class test.View
public class View {
#FXML
private ChoiceBox<String> firstCB;
#FXML
private ChoiceBox<String> secondCB;
public void init() {
firstCB.getItems().addAll(Charset.availableCharsets().keySet());
secondCB.getItems().addAll(Charset.availableCharsets().keySet());
firstCB.getSelectionModel().selectedItemProperty()
.addListener((observable, oldVal, newVal) -> {
System.out.printf("[%s]firstCB selection changed%n", Thread.currentThread().getName());
secondCB.getItems().remove(newVal);
secondCB.getItems().add(oldVal);
});
// removing one of the event listeners doesn't help
secondCB.getSelectionModel().selectedItemProperty()
.addListener((observable, oldVal, newVal) -> {
System.out.printf("[%s]secondCB selection changed%n", Thread.currentThread().getName());
firstCB.getItems().remove(newVal);
firstCB.getItems().add(oldVal);
});
}
}
main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<Pane fx:id="rootPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="90.0" prefWidth="180.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test.View">
<children>
<ChoiceBox fx:id="firstCB" layoutX="14.0" layoutY="14.0" prefWidth="150.0" />
<ChoiceBox fx:id="secondCB" layoutX="14.0" layoutY="52.0" prefWidth="150.0" />
</children>
</Pane>
try this, it works fine for me
firstComboBox.valueProperty().addListener((obs, oldV, newV) -> {
secondComboBox.getItems().remove(newV);
if(oldV!= null) secondComboBox.getItems().add(oldV);
});
secondComboBox.valueProperty().addListener((obs, oldV, newV) -> {
firstComboBox.getItems().remove(newV);
if(oldV!= null) firstComboBox.getItems().add(oldV);
});
or other not best solution with observable and filtered list
public class Controller implements Initializable{
public ComboBox<String> firstComboBox;
public ComboBox<String> secondComboBox;
#Override
public void initialize(URL location, ResourceBundle resources) {
ObservableList<String> list = FXCollections.observableArrayList();
list.addAll("one", "two", "three", "four", "five");
firstComboBox.getItems().addAll(list);
secondComboBox.getItems().addAll(list);
firstComboBox.valueProperty().addListener(c -> refreshList(list, secondComboBox, firstComboBox));
secondComboBox.valueProperty().addListener(c -> refreshList(list, firstComboBox, secondComboBox));
}
private void refreshList(ObservableList<String> list, ComboBox<String> choiceBox, ComboBox<String> choiceBox2) {
String tmp = choiceBox.getValue();
FilteredList<String> filteredList = new FilteredList<>(list, string -> !string.equals(choiceBox2.getValue()));
choiceBox.setItems(filteredList);
choiceBox.setValue(tmp);
}

Resources