12

I am new to FXML and I am trying to create a handler for all of the button clicks using a switch. However, in order to do so, I need to get the elements using and id. I have tried the following but for some reason (maybe because I am doing it in the controller class and not on the main) I get a stack overflow exception.

public class ViewController {
    public Button exitBtn;

    public ViewController() throws IOException {
         Parent root = FXMLLoader.load(getClass().getResource("mainWindow.fxml"));
         Scene scene = new Scene(root);

         exitBtn = (Button) scene.lookup("#exitBtn");
    }
}

So how will I get an element (for example a button) using it's id as a reference?

The fxml block for the button is:

<Button fx:id="exitBtn" contentDisplay="CENTER" mnemonicParsing="false"
        onAction="#handleButtonClick" text="Exit" HBox.hgrow="NEVER" HBox.margin="$x1"/>
RubioRic
  • 2,442
  • 4
  • 28
  • 35
Rakim
  • 1,087
  • 9
  • 21
  • 40
  • Use a [controller class](http://docs.oracle.com/javase/8/javafx/api/javafx/fxml/doc-files/introduction_to_fxml.html#controllers) – James_D Jan 28 '16 at 17:23
  • 1
    You are mixing the `id` and `fx:id` tags. `lookup` is based on `id`. `fx:id` is for injection into controller class. – Itai Jan 28 '16 at 17:24
  • @James_D this is a controller class – Rakim Jan 28 '16 at 17:26
  • @sillyfly ok, but still. how do I do it? The way I tried doesn't work as I cannot get the scene from the fxml file – Rakim Jan 28 '16 at 17:28
  • So why are you loading the fxml in the controller? That makes no sense: you create a controller instance that loads the FXML, during which another controller instance is created... – James_D Jan 28 '16 at 17:29
  • @James_D I am just trying to figure out how to get an element by id. And the only solution I found online was taht. But that would not work in the controller class – Rakim Jan 28 '16 at 17:31
  • But the whole point of using a controller is that you don't have to use a lookup. The fields are injected for you. – James_D Jan 28 '16 at 17:32
  • So the answer to my question is that what I am trying to do is not possible (i.e. getting an element by id) and I should use different handler methods for each of the actions?! – Rakim Jan 28 '16 at 17:34
  • It has nothing to do with which handler methods you choose, it's just about getting a reference to the button(s). Lookups only work after the scene has been rendered. (Though, actually, I do strongly recommend using a different handler for each action.) – James_D Jan 28 '16 at 17:36

1 Answers1

13

Use a controller class, so that you don't need to use a lookup. The FXMLLoader will inject the fields into the controller for you. The injection is guaranteed to happen before the initialize() method (if you have one) is called

public class ViewController {

    @FXML
    private Button exitBtn ;

    @FXML
    private Button openBtn ;

    public void initialize() {
        // initialization here, if needed...
    }

    @FXML
    private void handleButtonClick(ActionEvent event) {
        // I really don't recommend using a single handler like this,
        // but it will work
        if (event.getSource() == exitBtn) {
            exitBtn.getScene().getWindow().hide();
        } else if (event.getSource() == openBtn) {
            // do open action...
        }
        // etc...
    }
}

Specify the controller class in the root element of your FXML:

<!-- imports etc... -->
<SomePane xmlns="..." fx:controller="my.package.ViewController">
<!-- ... -->
    <Button fx:id="exitBtn" contentDisplay="CENTER" mnemonicParsing="false" onAction="#handleButtonClick" text="Exit" HBox.hgrow="NEVER" HBox.margin="$x1" />
    <Button fx:id="openBtn" contentDisplay="CENTER" mnemonicParsing="false" onAction="#handleButtonClick" text="Open" HBox.hgrow="NEVER" HBox.margin="$x1" />
</SomePane>

Finally, load the FXML from a class other than your controller class (maybe, but not necessarily, your Application class) with

Parent root = FXMLLoader.load(getClass().getResource("path/to/fxml"));
Scene scene = new Scene(root);   
// etc...     
James_D
  • 201,275
  • 16
  • 291
  • 322
  • What I am trying to say is that for example the button you have defined as `private Button exitBtn ;` is not the actual exitBtn. It is just a button reference. So if another button handler (i.e. `openBtn`) points to the same method then that button is going to `hide()` and not the exit button – Rakim Jan 28 '16 at 17:45
  • No, because in that case the condition in the `if` would evaluate to false. (All objects in Java are accessed by reference anyway: if you used a lookup, which would be really hard in the controller, you would still have a reference to the button.) – James_D Jan 28 '16 at 17:45
  • Alright, I see. I haven't realised that using as a variable name the same as defined in `fx:id="exitBtn"` works as a reference – Rakim Jan 28 '16 at 17:53
  • 1
    The `fx:id` specifies the name of the field into which the FXML Loader will inject the object created by the element. – James_D Jan 28 '16 at 17:54
  • If you are familiar with iOS development, `fx:id` effectively creates what Apple calls an "outlet" for the object. If you define a field with the same name in your controller class, `FXMLLoader` will automatically map the object instance to the field. – Greg Brown Jan 28 '16 at 18:08