0

I've seen bunch of posts connected with my problem but it seems like nothing works for me. I'm writing simple window app in Java FX and FXML. I have a Main class which has field with days names. I've got also two controller classes - Controller (which is like more important controller) and ShowDays. Each controller has their own FXML and they are responsible for switching scenes and working with user (and with Model = Main). In Controller class user can add a new day to our days names or remove last day. In ShowDays class user can print on window names of days. It's all connected with clicking buttons.

I know that I can organize my program easier and I can print names of days in Controller class and remove all ShowDays class but it's only a part of project I want to create and I just want to understand how real programmers would have done this kind of things.

This is my code:

public class Main extends Application {

private ArrayList<String> daysNames;

public Main() {
    daysNames = new ArrayList<>();
}

public void addDay() {
    String[] days = {"Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
    System.out.println("I'm adding day!" + daysNames.size());

    if (getDaysNames().size() < 7)
        getDaysNames().add(days[getDaysNames().size()]);
    else
        System.out.println("Can't add more days!");
}

public void removeDay() {
    if (!getDaysNames().isEmpty())
        getDaysNames().remove(getDaysNames().get(getDaysNames().size()-1));
}

@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, 300, 275));
    primaryStage.show();
}
//setters,getters...
}

Controller class:

public class Controller implements Initializable {
private Main myMain;
@FXML
private ShowDays showDays;

public Controller() {
    myMain=new Main();
    showDays = new ShowDays(myMain);
}

public Controller(Main main) {
    this.myMain = main;
    showDays = new ShowDays(myMain);
}

@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
}

public void addDay() {
    myMain.addDay();
}

public void removeDay() {
    myMain.removeDay();
}

public void GOTOshowDays(ActionEvent event) throws IOException {
    Parent showDaysParent = FXMLLoader.load(getClass().getResource("ShowDays.fxml"));
    Scene showDaysScene = new Scene(showDaysParent);
    Stage window = (Stage) ((Node)event.getSource()).getScene().getWindow();

    window.setScene(showDaysScene);
    window.show();
}
}

ShowDays class:

public class ShowDays implements Initializable {
@FXML
private Text text;
private Main myMain;

public ShowDays() {
    myMain = new Main();
}

public ShowDays(Main main) {
    myMain = main;
}

@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
}

public void BackToController(ActionEvent event) throws IOException {
    Parent controllerParent = FXMLLoader.load(getClass().getResource("sample.fxml"));
    Scene controllerScene = new Scene(controllerParent);
    Stage window = (Stage) ((Node)event.getSource()).getScene().getWindow();

    window.setScene(controllerScene);
    window.show();
}

public void showDaysInWindow(ActionEvent event) throws IOException {
    String allText = "";
    for (String day : myMain.getDaysNames())
        allText += day + " ";
    text.setText(allText);
}
}  

and the FXML files: sample.fxml

<HBox prefHeight="100.0" prefWidth="483.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
    <children>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
     <children>
        <Button layoutX="-8.0" layoutY="48.0" mnemonicParsing="false" onAction="#GOTOshowDays" text="GOTO show days" />
     </children>
  </AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0" />
  <Button mnemonicParsing="false" onAction="#addDay" text="add day" />
  <Button mnemonicParsing="false" onAction="#removeDay" text="remove day" />

ShowDays.fxml

<BorderPane 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="sample.ShowDays">
   <center>
      <Text fx:id="text" strokeType="OUTSIDE" strokeWidth="0.0" BorderPane.alignment="CENTER" />
   </center>
   <top>
      <Button mnemonicParsing="false" onAction="#showDaysInWindow" text="Show days" BorderPane.alignment="CENTER" />
   </top>
   <bottom>
      <Button mnemonicParsing="false" onAction="#BackToController" text="Go back" BorderPane.alignment="CENTER" />
   </bottom>
</BorderPane>

I'm not sure if ShowDays user actions works as expected (it's all about that...) but Controller do. The problem is when I add a few days in Controller scene and then switch scene to ShowDays scene, it seems like I lose my Main instance... When I have one controller class, my code cope with working all time on the same instance of Main and adding/removing days works as expected but I can't cope with connecting multiple controllers to model and them each other... I've spend so much time trying to fix this and I don't really understand all of the tips which I found on the internet, so I ask you.

  • Looking at your `Main`.class, I don't think you understand the basic JavaFx structure. What is the purpose of `private ArrayList daysNames;` in your `Main` class? – SedJ601 Jan 07 '19 at 17:10
  • I wanted my *Main* class to be the Model class. Explain what exactly is wrong in that, please –  Jan 07 '19 at 17:12
  • 1
    You need to follow @James_D answer from [here](https://stackoverflow.com/questions/32342864/applying-mvc-with-javafx). – SedJ601 Jan 07 '19 at 17:13
  • 1
    As suggested [here](https://stackoverflow.com/a/25556585/230513), similar design issues arise elsewhere; see also [*How should a model be structured in MVC?](https://stackoverflow.com/q/5863870/230513) – trashgod Jan 07 '19 at 17:43
  • Thanks for your time and feedback. I made separate Main class and rename model class to MainModel. When I load my FXML files and getControllers as shown [here](https://github.com/james-d/SimpleMVP/blob/master/src/examples/mvp/application/ContactApp.java) and check it in debugger, the values of MainModel in these two controllers are different... –  Jan 07 '19 at 17:52
  • Possible duplicate of [Applying MVC With JavaFx](https://stackoverflow.com/questions/32342864/applying-mvc-with-javafx) – SedJ601 Jan 07 '19 at 20:56

1 Answers1

0

You should use Ideas from @James_D answer here. Your problem is how to pass a model from one Controller to the next. In this example, the model is a List<String>. The model is passed to the initial Controller named FXMLDocumentController. From that point, the model is passed between FXMLDocumentController and ShowDayscontroller. The showDaysInWindow method, shows the current model info in a Text node. addDay adds a day from the array days if the model is missing a day. removeDay removes a day from the model.

Main

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

/**
 *
 * @author blj0011
 */
public class JavaFXApplication320 extends Application
{

    @Override
    public void start(Stage stage)
    {
        try {
            String[] days = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
            List<String> daysNames = new ArrayList();
            for (int i = 0; i < days.length; i++) {
                daysNames.add(days[i]);
            }

            //Load the FXML. Pass the model to initModel
            FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
            loader.load();
            FXMLDocumentController fXMLDocumentController = loader.getController();
            fXMLDocumentController.initModel(daysNames);
            Scene scene = new Scene(loader.getRoot());

            stage.setScene(scene);
            stage.show();
        }
        catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args)
    {
        launch(args);
    }

}

FXMLDocumentController

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;

/**
 *
 * @author blj0011
 */
public class FXMLDocumentController implements Initializable
{

    List<String> model;

    @FXML
    private void goToShowDays(ActionEvent event)
    {
        try {
            FXMLLoader showDaysLoader = new FXMLLoader(getClass().getResource("ShowDays.fxml"));
            showDaysLoader.load();
            ShowDaysController showDaysController = showDaysLoader.getController();
            showDaysController.initModel(this.model);

            Stage stage = (Stage) ((Button) event.getSource()).getScene().getWindow();
            stage.setScene(new Scene(showDaysLoader.getRoot()));
        }
        catch (IOException ex) {
            ex.printStackTrace();
        }

    }

    @FXML
    private void addDay(ActionEvent event)
    {
        String[] days = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
        List<String> tempDays = new ArrayList(Arrays.asList(days));

        if (!this.model.containsAll(tempDays)) {
            tempDays.removeAll(this.model);
            if (tempDays.size() > 0) {
                this.model.add(tempDays.get(0));
            }
        }
    }

    @FXML
    private void removeDay(ActionEvent event)
    {
        if (this.model.size() > 0) {
             Random random = new Random();
             this.model.remove(random.nextInt(this.model.size()));
        }
    }

    @Override
    public void initialize(URL url, ResourceBundle rb)
    {
        // TODO
    }

    public void initModel(List<String> model)
    {
        // ensure model is only set once:
        if (this.model != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }

        this.model = model;
        this.model.forEach(System.out::println);
    }

}

FXMLDocument

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>



<HBox prefHeight="100.0" prefWidth="483.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication320.FXMLDocumentController">
    <children>
        <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
            <children>
                <Button layoutX="-8.0" layoutY="48.0" mnemonicParsing="false" onAction="#goToShowDays" text="GOTO show days" />
            </children>
        </AnchorPane>
        <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0" />
        <Button mnemonicParsing="false" onAction="#addDay" text="add day" />
        <Button mnemonicParsing="false" onAction="#removeDay" text="remove day" />
    </children>
</HBox>

ShowDaysController

import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.text.Text;
import javafx.stage.Stage;

/**
 * FXML Controller class
 *
 * @author blj0011
 */
public class ShowDaysController implements Initializable
{

    @FXML
    Text text;

    List<String> model;

    @FXML
    private void showDaysInWindow(ActionEvent event)
    {
        text.setText(String.join(" ", this.model));
    }

    @FXML
    private void backToController(ActionEvent event)
    {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
            loader.load();
            FXMLDocumentController fXMLDocumentController = loader.getController();
            fXMLDocumentController.initModel(this.model);

            Stage stage = (Stage) ((Button) event.getSource()).getScene().getWindow();
            stage.setScene(new Scene(loader.getRoot()));
        }
        catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Initializes the controller class.
     *
     * @param url
     * @param rb
     */
    @Override
    public void initialize(URL url, ResourceBundle rb)
    {
        // TODO
    }

    public void initModel(List<String> model)
    {
        // ensure model is only set once:
        if (this.model != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }

        this.model = model;
        this.model.forEach(System.out::println);
    }

}

ShowDays 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.*?>
<?import javafx.scene.text.*?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication320.ShowDaysController">
    <center>
        <Text fx:id="text" strokeType="OUTSIDE" strokeWidth="0.0" BorderPane.alignment="CENTER" />
    </center>
    <top>
        <Button mnemonicParsing="false" onAction="#showDaysInWindow" text="Show days" BorderPane.alignment="CENTER" />
    </top>
    <bottom>
        <Button mnemonicParsing="false" onAction="#backToController" text="Go back" BorderPane.alignment="CENTER" />
    </bottom>
</BorderPane>
SedJ601
  • 12,173
  • 3
  • 41
  • 59
  • 1
    Thank you so much! But it seems like it was fault in loading the scenes more than one time - and then *Model* class constructor were called and the data was lost. I wanted the user to be able to add one day by one clicking and then in the second scene we could have something written like "Monday" or "Monday Tuesday" or "Monday Tuesday Wednesday" (all based on number of days how much user added/removed to/from *Model* instance) etc... Maybe my question wasn't so clear and sorry for my English but you all don't know how happy I am that stack overflow exitsts and there are people like you. –  Jan 07 '19 at 21:27
  • 1
    And there is a post that helped me: [link](https://stackoverflow.com/a/44191727/9717641) –  Jan 07 '19 at 21:28