0

I want to have a reusable button in JavaFX and give a function to it.

I currently have this:

BackButtonController

public class BackButtonController {
@FXML
private Button btnBack;

private final Method method;

    public BackButtonController(Method method) {
        this.method = method;
    }

    @FXML
    protected void initialize() {
        this.btnBack.setOnMouseClick(() -> buttonClick());
    }

    public void btnClick() {
        // do the received function
    }

}

BackButton.fxml

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>


<AnchorPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
    <Button fx:id="btnBack" mnemonicParsing="false" text="Back" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
        AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
</AnchorPane>

Controller

protected void initialize() {
    try {
        var loader = new FXMLLoader(getClass().getResource("/views/backButton.fxml"));
        loader.setController(new BackButtonController(this.test()));
        this.vbPanel.getChildren().add(0, loader.load());
    } catch (IOException e) {
        e.printStackTrace();
    }
    // some other code
}

I want to be able to load the button with a function that I send to the controller so when the button is pressed it runs that function.

I have been looking online but I can't get this to work.

I hope someone can help me with this.

BlueDragon709
  • 196
  • 1
  • 12
  • The answer seems to work but I recommend doing a search for creating custom nodes using FXML. I would not take the approach suggested by the answer. – SedJ601 Dec 05 '19 at 15:18

2 Answers2

3

To take action for a button, you should call button.setOnAction(eventHandler), not button.setOnMouseClicked(mouseEventHandler). That way if the button action is triggered in some other way than clicking on it with a mouse (e.g. via a keyboard action or a programmatic call to button.fire()), the desired action will still be triggered.

The eventHandler is already the equivalent of a function reference, all it does is provide a single method to handle() the event, so, for the purposes of Java 8, it forms a SAM type, which allows useful shortcuts using lambdas and method references.

There are lots of different ways to define and set event handler methods. The source code below provides some examples. Not all examples are used, so it has redundant code on purpose, but it includes lots of examples to demonstrate the different ways functions can be defined and used.

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class FunctionalButton extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        Layout layout = new Layout();

        layout.setFunction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                System.out.println(
                        "Invoked function from anonymous inner class defined in " + FunctionalButton.class.getSimpleName()
                );
            }
        });
        layout.setFunction(
                (actionEvent) ->
                        System.out.println(
                                "Invoked function from lambda defined in " + FunctionalButton.class.getSimpleName()
                        )
        );

        // use a method reference.
        layout.setFunction(this::applicationInstanceHandleButtonAction);

        stage.setScene(new Scene(layout));
        stage.show();
    }

    private void applicationInstanceHandleButtonAction(ActionEvent event) {
        System.out.println("Invoked event handler for method applicationInstanceHandleButtonAction");
    }

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

class Layout extends StackPane {
    private Button button = new Button("Invoke Configurable Function");

    private EventHandler<ActionEvent> layOutEventHandlerWithoutLambda =
            new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent event) {
                    System.out.println("Invoked event handler layOutEventHandlerWithoutLambda");
                }
            };

    private EventHandler<ActionEvent> layOutEventHandlerWithLambda =
            event -> System.out.println("Invoked event handler layOutEventHandlerWithLambda");

    private static void staticHandleButtonAction(ActionEvent event) {
        System.out.println("Invoked event handler for method staticHandleButtonAction");
    }

    private void instanceHandleButtonAction(ActionEvent event) {
        System.out.println("Invoked event handler for method instanceHandleButtonAction");
    }

    public Layout() {
        getChildren().add(button);
        button.setOnAction(layOutEventHandlerWithoutLambda);
        button.setOnAction(layOutEventHandlerWithLambda);
        button.setOnAction(Layout::staticHandleButtonAction); // Java 8 static method reference.
        button.setOnAction(this::instanceHandleButtonAction); // Java 8 instance method reference.
    }

    public void setFunction(EventHandler<ActionEvent> eventHandler) {
        button.setOnAction(eventHandler);
    }
}

For background (and to help if you can't understand some of the code in the example), you could review:

As you are using FXML, if you need to know how to pass a value to a controller (to set the event handler on the button from another class outside the controller), review:

jewelsea
  • 150,031
  • 14
  • 366
  • 406
1

I would replace the Method with a Runnable object. Then, on button clicked, execute that Runnable.

Like so:

public class BackButtonController {
@FXML
private Button btnBack;

private final Runnable runnable;

public BackButtonController(Runnable r) {
    this.runnable= r;
}

@FXML
protected void initialize() {
    this.btnBack.setOnMouseClick(() -> buttonClick());
}

public void btnClick() {
    runnable.run();
//Alternatively, if you want to run it in a separate Thread, use 
    new Thread(runnable).start();
}

}

AdminOfThis
  • 191
  • 3
  • By the way, you can add 'onAction="btnClick' to the button on your fxml file, and change buttonClick on the controller to '@FXML private void buttonClick(ActionEvent e)' That way, you don't need 'this.btnBack.setOnMouseClick(() -> buttonClick());' – AdminOfThis Dec 05 '19 at 14:33