0

I have a little quiz game I'm making, however when I try to pass a value from one of my controllers to another, the answer is always the opposite of what I expect. Basically, a radio button is selected, and if the correct one is selected, the returnValue() method should return a 1, else 0. This tells me if the user selected the proper answer or not.

Code for controller 1:

public class Question1
{
    @FXML
    private Label question1Lbl;
    @FXML
    private RadioButton rb1;
    @FXML
    private RadioButton rb2;
    @FXML
    private RadioButton rb3;

    private static int score;

    public void nextQuestion(ActionEvent actionEvent)
    {
        try
        {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("../gui/Question2.fxml"));
            Parent rootPane = loader.load();
            Stage primaryStage = new Stage();
            primaryStage.setScene(new Scene(rootPane));
            primaryStage.setResizable(false);
            primaryStage.setTitle("Frage 2");
            primaryStage.show();
            rb1.getScene().getWindow().hide();
        }
        catch (Exception e)
        {
            System.out.println("Error loading question 1" + e.toString());
            e.printStackTrace();
        }
    }
    //returns the points for this question. 1 for correct, 0 for incorrect
    public int returnValue()
    {

        if (!rb2.isSelected())
        {
            score = 0;
            return score;
        }
        score = 1;
        return score;
    }
}

The FinishScreen class is supposed to tally up the points gathered from all the returnValue() methods and display a score, but no matter what I try, the Question1 class always returns 0, whether the radio button was selected or not! I've written code like this before and it's worked fine, so I'm confused. I have 4 more classes like this, too.

Anyway, here is the FinishScreen class:

public class FinishScreen implements Initializable
{
    @FXML
    private Label resultLbl;

    private int totalScore;

    public void calculateScore() throws Exception
    {
        FXMLLoader loader1 = new FXMLLoader(getClass().getResource("../gui/Question1.fxml"));
        Parent root1 = loader1.load();
        Question1 q1 = loader1.getController();

        totalScore = q1.returnValue();
        System.out.println(totalScore);
        resultLbl.setText("Score: " + totalScore);
    }

    @Override
    public void initialize(URL location, ResourceBundle resources)
    {
        try
        {
            calculateScore();
        }
        catch (Exception e)
        {
            System.out.println("Error in results " + e.toString());
        }

    }
}

I used info from this post as a reference for how to pass values from one controller to another: FXMLLoader getController returns NULL?

If there is an error in the way I am trying to get the value from my Question1 class that would make sense, but most other posts I've read seem to follow this design pattern as well. It is a silly problem to have, yet I cannot figure it out.

My returnValue() should return a 1 when rb2.isSelected() == true but that isn't the case. I tried making the score member variable static as well and that hasn't helped. What could be the issue here?

halfer
  • 19,824
  • 17
  • 99
  • 186
FuegoJohnson
  • 372
  • 6
  • 18
  • don't use static fields, ever! They have class scope - that is in your case, every newly loaded Question modifies the exact same field which is bound to lead to unpredicable results. For passing parameters around controllers, see https://stackoverflow.com/questions/14187963/passing-parameters-javafx-fxml – kleopatra Apr 10 '19 at 10:05

1 Answers1

1

Even if you use a static field to store the results, those results are are retrieved from the controls in the scene when you call the returnValue method.

Loading the fxml a second time the way you do it in the calculateScore methods creates a new version of the scene though. In this new version of the scene the controls are in their initial states regardless of the user input done in the other version of the scene.

I recommend redesigning the app a bit:

  • Seperate the logic for the question from the logic for "navigating" the questions.
  • Keep a reference of the controller for later evaluation.
  • You can make your life easier by implementing an interface with your controllers.

Example

private int questionIndex = -1;

@Override
public void start(Stage primaryStage) throws Exception {
    StackPane questionContainer = new StackPane();
    questionContainer.setAlignment(Pos.TOP_LEFT);
    questionContainer.setPrefSize(300, 300);

    // create question list repeating our only question
    List<URL> questions = new ArrayList<>();
    URL url = getClass().getResource("/my/package/question1.fxml");
    for (int i = 0; i < 5; i++) {
        questions.add(url);
    }

    List<QuestionController> questionControllers = new ArrayList<>(questions.size());

    Button next = new Button("next");
    EventHandler<ActionEvent> handler = evt -> {
        if (questionIndex + 1 < questions.size()) {
            questionIndex++;

            // load next question & store controller for later evaluation
            FXMLLoader loader = new FXMLLoader(questions.get(questionIndex));
            try {
                questionContainer.getChildren().setAll(loader.<Node>load());
                questionControllers.add(loader.getController());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else {
            // display results
            Alert alert = new Alert(AlertType.INFORMATION);
            alert.setContentText("You've answered "
                + questionControllers.stream().mapToInt(QuestionController::getScore).sum()
                + " / " + questionControllers.size()
                + " questions correctly.");
            alert.showAndWait();
            primaryStage.close();
        }
    };
    next.setOnAction(handler);

    // activate first question
    handler.handle(null);

    primaryStage.setScene(new Scene(new VBox(questionContainer, next)));
    primaryStage.show();
}
public interface QuestionController {
    int getScore();
}
public class Question1Controller implements QuestionController {

    private static final Random random = new Random();

    private int correctAnswer;

    @FXML
    private List<RadioButton> buttons;

    @FXML
    private void initialize() {

        // randomly assign correct answer
        correctAnswer = random.nextInt(buttons.size());

        for (RadioButton btn : buttons) {
            btn.setText("incorrect");
        }

        buttons.get(correctAnswer).setText("correct");
    }

    @Override
    public int getScore() {
        return buttons.get(correctAnswer).isSelected() ? 1 : 0;
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<?import java.util.ArrayList?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.RadioButton?>

<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="my.package.Question1Controller">
    <children>
        <RadioButton fx:id="b1" />
        <RadioButton fx:id="b2" />
        <RadioButton fx:id="b3" />
        <fx:define>
            <ArrayList fx:id="buttons">
                <fx:reference source="b1"/>
                <fx:reference source="b2"/>
                <fx:reference source="b3"/>
            </ArrayList>
        </fx:define>
    </children>
</VBox>
fabian
  • 80,457
  • 12
  • 86
  • 114
  • Thank you, your input makes a lot of sense. I'm going to try this implementation later today and see how it works. If needed may I ask you additional questions about this? I will accept your answer once I try it regardless. – FuegoJohnson Apr 10 '19 at 13:56
  • @FuegoJohnson As long as you simply need clarification regarding the answer or something is unclear, you can comment here. I may answer you in a comment or improve the answer... – fabian Apr 10 '19 at 20:52