0

From all the searching and reading it’s clear that I need to call Platform.runLater() to change the GUI. It also appears I need to use the Runnable interface. Perhaps I should also use Tasks?

But I can’t figure out how exactly I should use them. Plus, I’m not sure which class I should put them in. I’m super new to JavaFX.

My trial JavaFX project has only a Label and a TextField. Label contains a question and the TextField is for answering. Simple enough.

I ran into the problem here:

The answer checking method is in a separate class. I can’t figure out how I can access the components of the GUI/FXML and change them. The methods in the other classes are static while the components of the GUI/FXML are non-static.

Since my actual project would have many quizzes, I'm keen on using separate classes for checking answers.

Only 3 small classes are relevant here:

  1. The “Launcher” class which contains the main method.
  2. The “ViewController” class for the FXML file as well as some methods.
  3. The “Ans” class which has a method to check the answer input.

In which class should I put the Platform.runLater()? And how would the code be?

I’ll just share the code of the “Ans” and the “ViewController” classes.

Ans (The background works are supposed to happen in this file. In the comments, I've mentioned what I want to do but unable to do. For example, I want to set the Label text from there but I can't. Since I have no idea how to do it I've just put a System.out.Println there. In the comments next to it, I've mentioned what I actually want to do.)

package com.dan.ans;

import com.dan.qn.Qn;
import com.dan.view.ViewController;
public class Ans {
public static void checkAns() {

    // Checks if the ans is correct.
    if (ViewController.getTextFieldInput().equalsIgnoreCase(Qn.getAns())) {

        System.out.println("Correct!");     // Here I want the label to say 'Correct!' rather than it be print out in the console.

        Qn.setQuestion();                   // This gets the next question from the database. But again, I don't know how to make the changes show on the screen. (In the actual code I'd have a separate Label for each of these things)

    } else { // Runs if it's not correct.

        System.out.println("Incorrect!");    // Here I want the label to say 'Incorrect' rather than it be print out in the console.
    }
  }
}

ViewController

package com.dan.view;

import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;

import com.dan.ans.Ans;
import com.dan.qn.Qn;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

public class ViewController implements Initializable {

private static String textFieldInput;      // I don't know how to access the typed info in the textField from another class. So I store it here and get it from it.

// This is the getter I use for it. (See above)
public static String getTextFieldInput() {
    return textFieldInput;
}

@FXML
private Label label;

@FXML
private TextField textField;

@Override
public void initialize(URL location, ResourceBundle resources) {

    Qn.setQuestion();                       // This method is in the Qn class. It retrieves data from the db file and keeps them in variables.

    label.setText(Qn.getQn());              // This sets the label's text using the retrieved data. So you see the first question when the program opens.
}

// Event Listener on TextField[#textField].onAction
public void enter(ActionEvent event) throws IOException {

    textFieldInput = textField.getText();    // Stores the typed info in the variable to be accessed from elsewhere.

    Ans.checkAns();                         // Runs the checkAns to check if the typed answer is correct or not.

  }

}

The “Launcher” method just looks like any method with a main class. So I haven’t shared its code here.

Could someone please show me how I can update the components in the GUI from other classes such as “Ans”? I’m pretty sure I should use Platform.runLater() and Runnable. Also may be Tasks. I’ve seen several examples but it’s not clear how I can use it this context.

Thanks a lot in advance! :)

Dan
  • 95
  • 11
  • 1
    Where is the background work in this example? The example code doesn't seem to require a separate thread at all. – James_D Apr 03 '18 at 19:25
  • Sorry for not making it clear. It's the codes in "Ans" class (The first code sample). In the comments, I've mentioned what I want to do but unable to do. Like I want to set the Label text from there but I can't. Since I have no idea how to do it I've just put a System.out.Println there. In the comments next to it, I've mentioned what I actually want to do. – Dan Apr 03 '18 at 19:29
  • 1
    But what does any of this have to do with threading? You haven't really explained that anywhere. – James_D Apr 03 '18 at 19:30
  • https://stackoverflow.com/questions/49635282/is-it-possible-to-use-objects-in-an-arraylist-to-set-labels-and-radio-buttons-in – SedJ601 Apr 03 '18 at 19:31
  • In terms of providing feedback from the `Ans` class to the UI, surely the natural thing to do in this case is just make `checkAns()` return a value, e.g. just return a `boolean` with `true` meaning the answer is correct and `false` meaning it's incorrect. Then you can just do `if (Ans.checkAns()) { /* ... */ } else {/* ... */}` in the controller, and update the UI from there (again, this seems much more natural). – James_D Apr 03 '18 at 19:32
  • @James_D Oh boy. I actually, spent over an hour on editing it for clarity. Really sorry that it still isn't clear. Basically, I want to put a string on the GUI Label from another class. That's what I want to do. I can't simply make a setter method for the Labels because they are non-static. That's the issue. I didn't have this problem in Swing. JavaFX seems to be different. – Dan Apr 03 '18 at 19:34
  • 1
    More generally, though, you should use a model class, and observe and update it from the controller (or from multiple controllers). See e.g. https://stackoverflow.com/q/32342864 – James_D Apr 03 '18 at 19:35
  • 1
    Still: "I want to put a string on the GUI label from another class". **What does this have to do with threading?** Why are you asking about `Platform.runLater()`? And what does static or non-static have to do with anything? If you want to call a non-static method, you would need to pass a reference to the controller instance to the `Ans` class (though I would not make the method in that class static either) – James_D Apr 03 '18 at 19:37
  • If you are familiar with Swing, `Platform.runLater()` is basically the equivalent of `SwingUtilities.invokeLater()`; they work pretty much exactly the same way. – James_D Apr 03 '18 at 19:39
  • @James_D Well, when I searched for solutions the to updating the UI, Platform.runLater() had often been mentioned in the accepted comments or comments with many upvotes. So I thought that was key. Thanks for the other tip and the link. I'll read it now. – Dan Apr 03 '18 at 19:41
  • @Sedrick, thanks for the link. I'll read it now. – Dan Apr 03 '18 at 19:42

1 Answers1

1

It's not really particularly clear what the issue is here. The natural (to me, anyway) approach would simply be to make the checkAnswer(...) method a method that simply "does what it says on the box", i.e. that takes an answer as a parameter, checks it, and returns a value to the caller indicating if it is correct.

That way you can also avoid all the ugly static hacks.

public class Ans {

    public boolean checkAns(String answer) {
        // not really sure what Qn is here, but you can also clean this up and
        // get rid of the static methods
        if (answer.equalsIgnoreCase(Qn.getAns()) {
            // not sure if this really belongs here?
            Qn.setQuestion(); // really takes no parameters? Sets it to what, then?
            return true ;
        } else {
            return false ;
        }
    }
}

And then in your controller, you can just do

public class ViewController implements Initializable {

    private Ans ans ;

    @FXML
    private Label label;

    @FXML
    private TextField textField;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        ans = new Ans();
        // ...
    }

    // ...

    public void enter(ActionEvent event) {
        if (ans.checkAns(textField.getText())) {
            // update UI to show answer was correct, etc
        } else {
            // update UI to show answer was incorrect...
        }
    }

    // ...
}

Note how this allows you to maintain proper separation of concerns: the Ans class doesn't need to know anything at all about the UI (which it should not know about at all), and all the UI-specific code is encapsulated in the controller class where it belongs.

It's not really clear why you are asking about Platform.runLater(...) and using Task, since none of the code you posted appears to involve any background threads (i.e. none of this code seems to take an appreciable amount of time to run). If, for example, the checkAns(...) method was doing some remote lookup and did take time to run, you would execute it in a Task and update the UI from the task's onSucceeded handler. See, e.g. Using threads to make database requests. Your question really seems to be more about basic OO design and how to define the relationships between different objects, though; I don't think you are actually asking about threading at all.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thank you so much. Your solutions make a lot of sense. I've decided to just stick in all the code from "Ans" into the controller. It just takes a few extra lines. Also, thanks for explaining what Platform.runLater() is used for. Now it makes sense. I understand my question wasn't clear enough and I really appreciate you taking the time to reply. :) – Dan Apr 04 '18 at 08:24