0

I have tried following the solution from here but without success: JavaFX Change label text from another class with controller

I am not sure if they want the same as I do.

So basically what I have is: FXMLDocumentController.java, FXMLDocument.xml, Operations.java, and Main.java. (I have some other classes that make the Arduino connection)

This is the start method that I have in my Main.java:

@Override
public void start(Stage primaryStage) throws IOException {

    Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));

    Scene scene = new Scene(root);

    primaryStage.setTitle("This is the title");
    primaryStage.setScene(scene);
    primaryStage.show();

}

EDIT: Here's my Operations.java:

public class Operations {

private String mensagem, hora, dados;
private String [] msgSeparada, dadosSeparados;
private int origem, destino, tipoMensagem, comprimento;
private int [] estadoDosSensores;

public FiltrarMensagem(String mensagem) {
    //remove o primeiro e ultimo carater
    mensagem = mensagem.substring(1, mensagem.length()-2);
    this.mensagem = mensagem;
    System.out.printf("Mensagem Recebida: %s\n", mensagem);
    msgSeparada = this.mensagem.split(";");
    destino = Integer.valueOf(msgSeparada[0]);
    origem = Integer.valueOf(msgSeparada[1]);
    hora = msgSeparada[2];
    tipoMensagem = Integer.valueOf(msgSeparada[3]);
    comprimento = Integer.valueOf(msgSeparada[4]);
    dados = msgSeparada[5];
    dadosSeparados = dados.split(",");
}

public void imprimir() {
    System.out.printf("Origem: %d\n", origem);
    System.out.printf("Destino: %d\n", destino);
    System.out.printf("Hora: %s\n", hora);
    System.out.printf("Tipo de Mensagem: %d\n", tipoMensagem);
    System.out.printf("Comprimento: %d\n", comprimento);
    System.out.printf("Dados: %s\n\n", dados);
    if(Integer.valueOf(dadosSeparados[0]) == 1) {
                //change label value here
    }

}

}

To simplify, here's what my program does:

I have my controller class with 2 simple buttons that receive data from the serial port coming from an Arduino, and with the data received from the Arduino, I create an object of the class Operations so I can make the necessary changes depending on the data received from the Arduino, and what I would like to do is to change labels and all the objects available at the FXML file, but I am not able to do that. What is the simplest way to do it?

I've tried everything and with no success... So would really appreciate if someone could help me on this.

jks
  • 13
  • 6
  • You haven't shown us your attempt to change something in the controller from the `Operations` class. It's impossible to know what you did wrong without seeing that, and so it's not really possible to answer your question (you have already posted a link to a possible solution - what is wrong with that solution?). – James_D Feb 05 '18 at 19:53
  • I did not try that solution because I wasn't able to understand their question, and I don't think it's the same as mine. That is why I am asking how do I do it... I've added the code though. – jks Feb 05 '18 at 20:38
  • Perhaps I should have been a bit more careful in the previous comment. The update doesn't really help much; really we need more information and context but with perhaps not as much code that isn't pertinent to the issue. Is it possible to make a [MCVE] that shows the issue? You have an FXML with a label, a controller for it; and then you have another *object* from which you want to do something that results in the label updating. We don't know where you are instantiating that object, or where it is created in relationship to where the FXML is loaded. That's what your example code needs to show – James_D Feb 05 '18 at 23:17
  • I'm sorry, you are right. Yes, basically we can assume that I only have the 4 files/classes: the main class, which basically only loads the FXML file, the FXML file itself, the Controller class and the other class (Operations) that has no connection with these classes, it just receives messages from the arduino and filters the data, so I can show it in my visual interface. That's why I want to change the labels from this Operations class. – jks Feb 05 '18 at 23:30
  • *Probably* what you need to do is create a model class, and share a single instance of it with your controller and with the other object which is going to do things which you want to cause the label to change its text. Your model can have a `StringProperty` and the controller can bind the label's text to it; then your other object can update that `StringProperty`. See my answer to https://stackoverflow.com/questions/32342864/applying-mvc-with-javafx (which has multiple controllers interacting with a single model instance, instead of a controller and another object). – James_D Feb 05 '18 at 23:33
  • *"the other class (`Operations`) that has no connection with these classes"*. It's not really possible for it to have *no connection*. You must be instantiating it, and presumably calling methods on the resulting object, *somewhere*. Since your "main class" contains the entry point of the application, that must happen in that class or in the controller... So there must be a connection between these. – James_D Feb 05 '18 at 23:43
  • Yes, but I didn't think that would matter. I am really new to this GUI and JavaFX world... Basically, I have a button that initializes the connection with the arduino and then when a message is received, I instantiate this class Operation to filter the message. Is there a way to create a method in my Main class where I load the controller, to return the controller like a getter method? So I can call it in my Operations class and use the "controller.set" method to change the value of the label? – jks Feb 06 '18 at 00:08
  • If you're instantiating the `Operations` class from the controller, why not just give it a reference to the controller? It's not really clear where the problem lies if you already have a connection between the two objects like that. – James_D Feb 06 '18 at 00:10

1 Answers1

0

Simple solution for easy case

If you're instantiating your "other class" in response to a button press, i.e. in the controller, all you need to do is pass the new object a reference to the controller.

I.e.

public class Controller {

    @FXML
    private Label label ;

    public void showMessage(String message) {
        label.setText(message);
    }

    @FXML
    private void handleButtonPress(ActionEvent event) {
        Operations ops = new Operations(this);
        ops.doYourThing();
    }
}

and then

public class Operations {

    private final Controller controller ;

    public Operations(Controller controller) {
        this.controller = controller ;
    }

    public void doYourThing() {
        // ...

        String someMessage = ... ;
        controller.showMessage(someMessage);

        // ...
    }
}

MVC approach

A slightly more general and robust solution is to use a MVC-approach, and create a model class. Your model class can use an observable StringProperty to keep the text to display. Share the model instance with the controller and with the service class (Operations). The controller can observe the property, so it can update the label whenever the property changes, and the service can update the property. This looks something like this:

public class Model {

    private final StringProperty message = new SimpleStringProperty(); 

    public StringProperty messageProperty() P{
        return message ;
    }

    public final String getMessage() {
        return messageProperty().get();
    }

    public final void setMessage(String message) {
        messageProperty().set(message);
    }

}
public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
        Parent root = loader.load();
        Controller controller = loader.getController();

        Model model = new Model();
        controller.setModel(model);

        Scene scene = new Scene(root);

        primaryStage.setTitle("This is the title");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}
public class Controller {

    @FXML
    private Label label ;

    private Model model ;

    public void setModel(Model model) {
        this.model = model ;
        model.messageProperty().addListener((obs, oldMessage, newMessage) ->
            label.setText(newMessage));
    }

    @FXML
    private void handleButtonPress(ActionEvent event) {
        Operations ops = new Operations(model);
        ops.doYourThing();
    }
}

And finally

public class Operations {

    private final Model model ;

    public Operations(Model model) {
        this.model = model ;
    }

    public void doYourThing() {
        // ...

        String someMessage = ... ;
        model.setMessage(message);

        // ...
    }
}

The benefits to this (slightly more complex) approach are:

  1. You remove any coupling between your "service" class and the controller, so the service is really independent of the UI
  2. This now works if the service is created elsewhere, as you have access to the model in a wider scope (it's created in the Main class, which is the entry point to the application).
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thank you so much! I finally made it using the first method. I just have to find a way to get the data "back up" from the subclasses, because the Operations class is under 3 instantiations of other classes, but now I know how to make the connection! Thank you James – jks Feb 06 '18 at 02:06
  • I am using this code: "http://rxtx.qbang.org/wiki/index.php/Event_Based_Two_Way_Communication" that makes the connection with the Arduino, and under the class SerialReader I have the instantiation of the class Operations, how do you suggest make this work keeping in mind I have more objects to edit? – jks Feb 06 '18 at 02:13
  • So I passed the controller trhough each class as a parameter but now I am getting an error because of the thread being used not being a javafx thread (Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4). This code uses a thread to read and a thread to write to arduino, and I am filtering the info (with Operations class) from "under" the class SerialReader which is running on a thread. Is there any solution for this? (I have no experience with threads in java or just threads at all) – jks Feb 06 '18 at 02:28
  • @jks That's an entirely different question to the one you posted here. Fortunately it's one that has been answered many times on this forum - just search for the error message. – James_D Feb 06 '18 at 10:22
  • @jks Also, since you seem to have quite a complex structure, "wiring" everything by hand by passing the model through multiple layers of dependencies can get to the point that it is prohibitively complex and unmaintainable. Assuming you have enough Java experience to understand dependency injection frameworks, these can simplify your code a lot: see https://stackoverflow.com/questions/40539310/dependency-injection-and-javafx – James_D Feb 06 '18 at 13:57