16

I have a JavaFX / Java 8 application written with NetBeans 8 (no SceneBuilder).

My application has a main window that has its own FXML file (primary.fxml) and its own controller class (FXMLPrimaryController.java). One of the items in the FXML is a TextArea. Some of the methods in FXMLPrimaryController.java are about appending to that TextArea.

This application now spawns a second window (another "stage") with its own FXML (second.fxml) and its own controller class (FXMLsecondController.java).

Within the second controller class, how can I access the TextArea in the primary?

Here's a sample of the relevant code:

primary.fxml:

<Button text="press me!" onAction="#openSecondWindow" />
<TextArea fx:id="myArea" />

FXMLPrimaryController.java:

public class FXMLPrimaryController implements Initializable {

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

    @FXML private TextArea myArea;

    final public void writeToTextArea() {
        myArea.appendText("hi!");
    }

    @FXML
    private void openSecondWindow(ActionEvent event) throws Exception {

        Group root = new Group();
        Stage stage = new Stage();

        AnchorPane frame = FXMLLoader.load(getClass().getResource("second.fxml"));
        root.getChildren().add(frame);
        Scene scene = new Scene(root);

        stage.setScene(scene);
        stage.show();
    }

}

There is nothing fancy about second.fxml. Assume there is a button with onAction="#writeSomething".

In FXMLsecondController.java, I would like a function that references the above TextArea.

Damien
  • 1,161
  • 2
  • 19
  • 31
adeena
  • 4,027
  • 15
  • 40
  • 52

2 Answers2

11

When you load the FXML for the secondary controller, do this:

FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("second.fxml"));
AnchorPane frame = fxmlLoader.load();
FXMLSecondaryController c = (FXMLSecondaryController) fxmlLoader.getController();

Then you can pass references to the second controller. This could just be the TextArea.

EDIT:

Replaced the load() call in the snippet above and added the setLocation() call. The old line AnchorPane frame = fxmlLoader.load(getClass().getResource("second.fxml")); was wrong as it called the static load function, which is useless here.

EDIT:

(Changed the code snippet above to better match your variable names.) The code snippet above replaces this part of your code:

AnchorPane frame = FXMLLoader.load(getClass().getResource("second.fxml"));

That line uses the FXMLLoader to load the view and also creates the instance of the controller - in this case FXMLSecondaryController. However, you cannot use the static FXMLLoader.load method for that, you need an instance of FXMLLoader. That instance holds a reference to the controller after load and you can retrieve that with getController(). And you need to cast the returned value to your controller class (with (FXMLSecondaryController)).

Your primary controller has a field:

@FXML private TextArea myArea;

This holds a reference to your TextArea and is initialized by the fxml loader. (If you remove the @FXML annotation, the loader won't touch it.)

In your secondary controller, add this field:

public TextArea primaryTextArea;

Note that @FXML is gone (the fxml loader shall not touch the field) as well as private (you want to set the field, hence others must see it). Now you can set this field after the controller has been loaded. So back to the loading code:

FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("second.fxml"));
AnchorPane frame = fxmlLoader.load();
FXMLSecondaryController c = (FXMLSecondaryController) fxmlLoader.getController();
// Add this too:
c.primaryTextArea = myArea;

EDIT: Replaced the load() call in the snippet above and added the setLocation() call. The old line AnchorPane frame = fxmlLoader.load(getClass().getResource("second.fxml")); was wrong as it called the static load function, which is useless here.

The FXMLSecondaryController now has a reference to the TextArea of the primary controller. In one of its methods you should be able to access its content:

public class FXMLSecondaryController implements Initializable {
    // ...

    public TextArea primaryTextArea;

    // ...

    @FXML
    private void doSomething(ActionEvent event) throws Exception {
        primaryTextArea.appendText("Hi ho!");
    }

}

Note that this solution is not the best approach, but it is simple and can serve as a start. Using binding is certainly recommended. I would try to create a class which holds the data and on which the other controllers bind. But if you take the simple approach for now, it should be sufficient.

Rainer Schwarze
  • 4,725
  • 1
  • 27
  • 49
  • Please take a look at the sample code I provided. What I'm not understanding with your answer is that my primary window is initialized when the application is initialized. But the second window is by user request (they press a button). Once that second window is open, assume that window has a button, I would like the code in the button to access the textArea in the first window. – adeena Feb 24 '14 at 18:54
  • When you load the secondary controller with FXMLLoader.load, you can change that line and add the code from my answer. You also create a field in your secondary controller which holds a TextArea: "TextArea primaryTextArea;". So when you create the secondary controller, set the primaryTextArea field to myArea. You don't need a function to query for the TextArea; you can pass it along when you create the secondary controller. – Rainer Schwarze Feb 24 '14 at 19:16
  • Sorry - still a little confused. If I use your code above to replace some of the code where I'm loaded my second window/second FXML stuff... then I don't understand what this line: FXMLSecondaryController c = (FXMLSecondaryController) fxmlLoader.getController(); does or how when I'm over in my second Controller.java how I'm accessing the TextArea from the primary window? "c" isn't useable...? is it? – adeena Feb 24 '14 at 19:26
  • If you give me a little time, I will add better information later. Currently I am on my iPhone and editing is rather uneasy here :-) For now: that line gives you access to the second controller in which you want to access the TextArea. Inside that secondary controller you can access the TextArea just with the field primaryTextArea. – Rainer Schwarze Feb 24 '14 at 19:40
  • Yes, take you time. I appreciate your help! I still haven't been able to figure this one out... – adeena Feb 24 '14 at 20:44
  • Added more details - let me know, if you have more questions :-) – Rainer Schwarze Feb 25 '14 at 08:45
  • I understand what you've posted, but I'm getting a NullPointerException at the line "c.primaryTextArea = myArea;". Not sure why? I am going to examine the next answer here, too, with the binding... I do get why that would be better... – adeena Feb 25 '14 at 15:12
  • If you like - and the project size is not too large - you can send me your project by email and I can take a quick look at it. – Rainer Schwarze Feb 25 '14 at 16:46
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/48395/discussion-between-adeena-and-rainer-schwarze) – adeena Feb 25 '14 at 17:02
  • @RainerSchwarze +1 For the warning ! I used the same approach when I was new to Javafx, but then in the due course of time, I understood that this approach will hurt when your application becomes huge ! – ItachiUchiha Feb 26 '14 at 07:37
  • @ItachiUchiha Yes I agree. Especially with projects which evolve over many years, the quick shortcuts become a real burden. On the other hand I also noticed, that sometimes it pays off to keep it very simple as long as it does not hurt :-) – Rainer Schwarze Feb 26 '14 at 10:05
  • @RainerSchwarze **keep it very simple as long as it does not hurt** :) :) – ItachiUchiha Feb 26 '14 at 10:06
  • @RainerSchwarze What if my second class is not a controller but a "normal" class? I'm having issues accessing the TextArea from that now... (and hoping you can still help!) – adeena Mar 22 '14 at 19:36
  • @adeena When (or how) do you create an instance of that second class? – Rainer Schwarze Mar 23 '14 at 11:04
  • @RainerSchwarze Let's say it happens when I press a button (but can happen other times) and let's say that the other class is doing something with XML data. within that class, I want text to appear on my GUI TextArea. – adeena Mar 23 '14 at 11:52
  • @adeena I created a chat room http://chat.stackoverflow.com/rooms/50219/discussion-between-adeena-and-rainer-schwarze - lets discuss it there (the old room seems to be closed) – Rainer Schwarze Mar 23 '14 at 15:30
  • I would like to know what you mean by using "bindings" instead? (the best approach) – miniHessel Sep 17 '14 at 11:25
  • @miniHeessel I think the answer of ItachiUchiha deals with bindings. – Rainer Schwarze Sep 17 '14 at 14:14
3

I hope you could expose the TextField from the FXMLPrimaryController, and have a TextField property in the FXMLsecondController; then set that property when you assemble the pieces together. This would not be a good design, though, because you would be exposing details of the view through the controllers.

I am sure you don't want the TextArea but the data of the TextArea in second.fxml. So what I would suggest is to expose a String property in each of the controllers and bind them together.

public class FXMLPrimaryController {  
     private ReadOnlyStringWrapper text ;  

     @FXML 
     private TextArea myArea;

     public void initialize() {  
          text= new ReadOnlyStringWrapper(this, "text");  
          text.bind(myArea.textProperty());  
     }  
     public ReadOnlyStringProperty textProperty() {  
          return text.getReadOnlyProperty();  
     }  
     public String getText() {  
          return text.get();  
     }  

    @FXML
    private void openSecondWindow(ActionEvent event) throws Exception {

    Group root = new Group();
    Stage stage = new Stage();

    AnchorPane frame = FXMLLoader.load(getClass().getResource("second.fxml"));
    root.getChildren().add(frame);
    Scene scene = new Scene(root);

    stage.setScene(scene);
    stage.show();
}
}

and then in your FXMLsecondController, you can have

public class FXMLsecondController {  
     private StringProperty text ;  
     @FXML  
     private void handleButtonAction(ActionEvent event) {  
          System.out.println(text.getValue());  
     }  
     public void initialize() {  
          text = new SimpleStringProperty(this, "text");  
     }  
     public StringProperty textProperty() {  
          return text ;  
     }  
     public String getText() {  
          return text.get();  
     }  
}

Then you can bind these two, using something like this

FXMLLoader primaryLoader = new FXMLLoader(getClass().getResource("primary.fxml"));  
Parent textAreaHolder = (Parent) primaryLoader.load();  
FXMLLoader secondaryLoader = new FXMLLoader(getClass().getResource("second.fxml"));  
Parent textAreaUser = (Parent) secondaryLoader.load();  
FXMLPrimaryController primaryController = (FXMLPrimaryController) textAreaHolder.getController();  
FXMLsecondController secondController = (FXMLsecondController) textAreaUser.getController();  
secondController.textProperty().bind(primaryController.textProperty());

This now binds the variable text of both the controllers and you can easily use the value that is entered in the textarea of the primary controller in your secondary controller !

ItachiUchiha
  • 36,135
  • 10
  • 122
  • 176
  • Thanks! I think I understand it all except where in my code does the last block of stuff (the block with the FXMLLoader stuff) go? In my application, the primary.fxml is loaded when the application starts. The second.fxml isn't loaded until the user pushes a button to open that second window. – adeena Feb 25 '14 at 15:18
  • You can use the binding property in the openSecondWindow() of yours ! – ItachiUchiha Feb 25 '14 at 18:00
  • @ItachiUchiha Could you please invite to a chat? I have some more questions about this answer. – miniHessel Sep 18 '14 at 08:07