Search code examples
javajavafxpropertiesgluon

How to create an attribute for a custom component in JavaFx?


After a few hours work I have managed to understand how to create a custom component using javafx and Gluons Scene Builder. How does one add a new property for that component which will appear in the scene builder properties menu so that the property value can be entered at design time.


Solution

  • You need to subclass something, most likely an implementation of Parent, Region or Pane. From there you can load the FXML which was generated by whatever you have done in Scene Builder.

    Example:

    public class MyComponent extends VBox {
        private final StringProperty foo = new SimpleStringProperty();
    
        public MyComponent {
            final Parent root = (Parent) FXMLLoader.load(getClass().getResource(fxmlPath));
    
            getChildren().add(root);
        }
    
        public final StringProperty fooProperty() { return foo; }
        public final String getFoo() { return foo.get(); }
        public final void setFoo(String foo) { this.foo.set(foo); }
    }
    

    This may produce an extra container, one being MyComponent, the other being the root node of the FXML. To prevent this, use FXMLLoader.setRoot().

    Update

    It seems like you have an extra obstacle - the controller which has the main logic is decoupled with MyComponent.

    There are many ways to do this:

    By Reference Passing

    You can pass in the reference of MyComponent into the controller.

    public class MyController {
        /* Your controller stuff */
    
        private MyComponent component;
    
        public final void setComponent(MyComponent component) {
            // You can bind your controller properties to fooProperty here
        }
    }
    

    Then when loading the FXML in MyComponent, you get the reference of the controller and manually call setComponent().

    By exposing controller property

    This method directly exposes the properties of the controller in MyComponent

    public class MyComponent extends VBox {
        // Other stuff
    
        private MyController controller;
    
        public MyComponent {
            // Original stuff
    
            this.controller = loader.getController();
        }
    
        public final StringProperty fooProperty() {
            assert controller != null; // If you loaded correctly this assertion should be valid
    
            return controller.fooProperty();
        }
    
        public final String getFoo() { return fooProperty().get(); }
        public final void setFoo(String foo) { fooProperty().set(foo); }
    }
    

    Integrating controller into the component

    This method simply combines MyComponent and MyController into a single class. You would need FXMLLoader.setController(this).

    The advantage is you have direct access to everything. The bad thing is, some people would say this violates MVC.