Sefa
Sefa

Reputation: 380

JavaFx handler override

I have a fxml file build from fxml builder and I am using it by a loader in Java.

URL resource = getClass().getClassLoader().getResource("fxmlFile.fxml");

FXMLLoader loader = new FXMLLoader(resource, resourceBundle);
Pane rootPane = (Pane) loader.load(); 

this fxml file maps click event to my class;

<Group id="Group" layoutX="0.0" layoutY="0.0" onMouseReleased="#handleThis" scaleX="1.0" scaleY="1.0">
...
<Group/>

so I implement my handler in my class, lets call it MyClass;

public class MyClass {
public void createScene() throws IOException
{
    URL resource = getClass().getClassLoader().getResource("fxmlFile.fxml");

    FXMLLoader loader = new FXMLLoader(resource, resourceBundle);
    Pane rootPane = (Pane) loader.load(); 
    ...
}

@FXML
public void handleThis(ActionEvent event) {
    System.out.println("from MyClass");
}
        ...
}

Now I extend MyClass as MyExtendedClass and override handleThis method;

public class MyExtendedClass extends MyClass {
    @Override
    public void handleThis(ActionEvent event) {
           System.out.println("from MyExtendedClass");
    }
}

My question is, I cannot manage to work handle method in my extended class. It does not overrides it. How can I achieve to make it print "from MyExtendedClass" instead of "from MyClass"?

Upvotes: 1

Views: 2446

Answers (1)

Prometheus
Prometheus

Reputation: 1015

When createScene() is called on an instance of MyExtendedClass, the FXMLLoader parses the FXML file, reads the fx:controller="MyClass" attribute and instantiates a new object of type MyClass. That is why the base method is always called. The FXMLLoader doesn't know about MyExtendedClass.

There is a - hackish - way to achieve what you want (i.e. doing the loading in MyClass and still defining the controller in FXML):

public class MyClass
{
    public void createScene()
    {
        try
        {
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(getClass().getResource("FXML.fxml"));
            // set a controller factory that returns this instance as controller
            // (works in this case, but not recommended)
            loader.setControllerFactory(controllerType -> this);
            pane = (Pane) loader.load();
        }
        catch (IOException e)
        {
            throw new RuntimeException(e);
        }
    }
}

It would be cleaner to instantiate the controller and pass it to the FXMLLoader.
For this the fx:controller="" attribute must be removed from the FXML file.

public class Main extends Application
{
    @Override
    public void start(Stage primaryStage) throws Exception
    {
        MyClass controller = new MyExtendedClass();

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("FXML.fxml"));
        loader.setController(controller);
        Pane pane = (Pane) loader.load();

        primaryStage.setScene(new Scene(pane));
        primaryStage.show();
    }

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

Or use fx:controller="MyClass" to define the base type in the FXML file and let a controller factory decide the actual implementation.

public class Main extends Application
{
    @Override
    public void start(Stage primaryStage) throws Exception
    {
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("FXML.fxml"));
        loader.setControllerFactory(controllerType -> {
            if (MyClass.class.equals(controllerType))
                return new MyExtendedClass();
            else
                return null; // return some other controller
        });
        Pane pane = (Pane) loader.load();

        MyClass controller = (MyClass) loader.getController();

        primaryStage.setScene(new Scene(pane));
        primaryStage.show();
    }

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

Upvotes: 1

Related Questions