1

Very new to JavaFX and lacking a bit of knowledge in the way controllers work but here it goes.

My problem is easy. I need to update a Label on the screen during runtime.

This problem has been addressed on this site before:

Java FX change Label text

Java FX change Label text 2

Passing Parameters

Also, are these links describing the same thing but done differently?

But my program is a little different.

The flow of the program is as follows:

The Main Stage has several Objects that extends Pane with a Label inside. These Objects can be right clicked which opens a context menu. An option in the context menu opens a new window with RadioButtons.

The idea is to select one of the RadioButtons and use that string to rewrite the Label back on the Main Stage.

However my code only works once, the first time. All subsequent changes are not shown on the screen. I can even output the Label that was changed to the Console and it shows the correct value, but never updates the Label on the Stage.

Class that has the Label on the screen:

import javafx.scene.control.Label;
import javafx.scene.layout.Pane;

public class CoursePane extends Pane {

    private Label courseID;

    public CoursePane(Label courseID) {

        this.courseID = courseID;
    }

    public String getCourseID() {

        return courseID.getText();
    }

    public Label getCourseLabel() {

        return courseID;
    }

    public void setCourseID(String ID) {

        courseID.setText(ID);   
    }
}

The Context Menu Class that invokes the menu:

public class CourseContext {


    static String fxmlfile;
    private static Object paneSrc; //the CoursePane that was clicked on

    public static void start(CoursePane pane, String courseSrc) {

        //Context Menu
        ContextMenu contextMenu = new ContextMenu();

        //MenuItems
        MenuItem item4 = new MenuItem("option");

        //add items to context menu
        contextMenu.getItems().addAll(item4);

        pane.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                if (event.isSecondaryButtonDown()) {

                    //the coursePane that was right clicked on
                    paneSrc = event.getSource().toString();

                    contextMenu.show(pane, event.getScreenX(), event.getScreenY());

                    item4.setOnAction(new EventHandler<ActionEvent>() {

                        @Override
                        public void handle(ActionEvent event) {

                            try {

                                FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("my fxml file for the radio Buttons"));

                                Parent root= loader.load();

                                ElectiveController electiveController = loader.getController();

                                electiveController.start( "pass the coursePane that was right clicked on" );

                                Scene scene = new Scene(root);

                                Stage stage = new Stage();

                                stage.setScene(scene);

                                stage.setTitle("Set Elective");

                                stage.show();

                            }
                            catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
        });
    }
}

And finally, the class that has the value that Label is supposed to be set to:

public class ElectiveController {

    @FXML
    private Button setButton;

    private RadioButton chk;

    //the pane that was right clicked on
    private static String courseSource;

    public void start(Course courseSrc) { //courseSrc: the Pane you right clicked on

        courseSource = courseSrc.getCoursenamenumber().getValue();

    }//end start

    //sets the course pane with the selected elective radio button
    @FXML
    private void setElective() {

        chk = (RadioButton)humElectiveGroup.getSelectedToggle();

        //This is supposed to set the value for the coursePane Object to show on the screen!
        MainStage.getCoursePanes().get(courseSource).setCourseID(chk.getText());

        Stage stage = (Stage) setButton.getScene().getWindow();

        stage.close();
    }
}

I have looked into dependency injection, tried binding and passing parameters but getting the same results. I know this is straight forward, any help is appreciated! Thanks.

c0der
  • 18,467
  • 6
  • 33
  • 65
comfychair
  • 43
  • 6
  • 1
    What you need is a [model-view-controller](https://www.tutorialspoint.com/design_pattern/mvc_pattern.htm) concept, where the "data" is stored in the model, other classes can share the model and the model generates (via an [observer pattern](https://www.tutorialspoint.com/design_pattern/observer_pattern.htm)) notifications when it changes – MadProgrammer Aug 18 '18 at 06:30
  • Welcome to SO. Please see [How do I ask a good question?](https://stackoverflow.com/help/how-to-ask). Note the information about [mcve] – c0der Aug 18 '18 at 08:09

1 Answers1

0

Here is an mcve of how you could wire up the different parts.
- It can be copy pasted into a single file and invoked.
- Note that it is not meant to represent or mock your application. It is meant to demonstrate a (very basic and simplistic) solution for the issue

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

//main class
public class UpdateViewByMenu extends Application {

    private Controller controller;

    @Override
    public void start(Stage stage) throws Exception {

        BorderPane root = new BorderPane();
        controller = new Controller();
        root.setTop(controller.getMenu());
        root.setBottom(controller.getView());
        Scene scene = new Scene(root, 350,200);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) { launch(args);}
}

//controller which "wires" view to model
class Controller {

    private Model model;
    private View view;
    private TopMenu menu;

    public Controller() {
        model = new Model();
        view = new View();
        menu = new TopMenu();
        //wire up menu to model : menu changes update model
        menu.getMenuTextProperty().addListener(
                e-> model.setCourseID(menu.getMenuTextProperty().get()));
        //wire model to view: change in model update view
        view. geLabelTextProerty().bind(model.getCourseIDProperty());
        //set initial value to show
        menu.getMenuTextProperty().set("Not set");
    }

    Model     getModel() {return model;}
    Pane      getView()  { return view;}
    MenuBar  getMenu()  { return menu; }
}

//model which represent the data, in this case label info
class Model{

    SimpleStringProperty courseIdProperty;
    Model(){
        courseIdProperty = new SimpleStringProperty();
    }

    StringProperty getCourseIDProperty() {

        return courseIdProperty;
    }

    void setCourseID(String id) {

        courseIdProperty.set(id);
    }
}

//represents main view, in this case a container for a label
class View extends HBox {

    private Label courseID;

    View() {
        courseID = new Label();
        getChildren().add(courseID);
    }

    StringProperty geLabelTextProerty() {

        return courseID.textProperty();
    }
}

//menu
class TopMenu extends MenuBar{

    SimpleStringProperty menuTextProperty;

    TopMenu() {
        menuTextProperty = new SimpleStringProperty();
        Menu menu = new Menu("Select id");
        MenuItem item1 =  getMenuItem("10021");
        MenuItem item2 =  getMenuItem("10022");
        MenuItem item3 =  getMenuItem("10023");
        MenuItem item4 =  getMenuItem("10024");
        menu.getItems().addAll(item1, item2, item3, item4);
        getMenus().add(menu);
    }

    MenuItem getMenuItem(String text) {

        MenuItem item =  new MenuItem(text);
        item.setOnAction(e -> menuTextProperty.set(item.textProperty().get()));
        return item;
    }

    StringProperty getMenuTextProperty() {

        return menuTextProperty;
    }
}

Do not hesitate to ask for clarifications as needed.

c0der
  • 18,467
  • 6
  • 33
  • 65
  • Thanks so much, looking into it now, I'll be back tomorrow with the results! – comfychair Aug 19 '18 at 01:51
  • really like the example; a few things, it seems like most of the work is being done by instantiated objects and getters right? In the controller constructor, there is a listener and a bind. Is this two ways of doing the same thing? Also, it seems like the Controller class serves as kind of the center where everything connects, is this right? – comfychair Aug 21 '18 at 02:10
  • Also, does it complicate things if the other classes are also separate windows / stages? Or is this irrelevant? – comfychair Aug 21 '18 at 02:12
  • The controller as its name suggests, does the control, and "connects" the model and the view. There are many flavors to MVC model. Binding is a mechanism of changing one property based on changes in other property (ies). As you wrote you can it also by registering listeners. Stage can certainly be constructed by a different class than the Application class. – c0der Aug 21 '18 at 05:31