1

I have an interface GraphicNodeProvider:

public interface GraphicNodeProvider {
    Node getNode(Graphic graphic);
}

The Graphic type is an enum:

public enum Graphic {
    HIDE,
    SHOW,
    REFRESH,
    OPEN,
    CREATE,
    EDIT,
    DELETE,
    SAVE,
    BACK,
    ...
}

The Graphic enum represents the graphic for a JavaFX node like a button, for example, Graphic.REFRESH is the graphic for a refresh button.

I'll implement GraphicNodeProvider as follows:

import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.Glyph;
...

public class DefaultGraphicNodeProvider implements GraphicNodeProvider {
    public static final String FONT_FAMILY = "FontAwesome";

    @Override
    public Node getNode(Graphic graphic) {
        return switch (graphic) {
            case REFRESH -> new Glyph(FONT_FAMILY, FontAwesome.Glyph.REFRESH);
            case SHOW    -> new Glyph(FONT_FAMILY, FontAwesome.Glyph.EYE);
            case HIDE    -> new Glyph(FONT_FAMILY, FontAwesome.Glyph.EYE_SLASH);
            case OPEN    -> new Glyph(FONT_FAMILY, FontAwesome.Glyph.FOLDER_OPEN);
            case CREATE  -> new Glyph(FONT_FAMILY, FontAwesome.Glyph.PLUS);
            case EDIT    -> new Glyph(FONT_FAMILY, FontAwesome.Glyph.PENCIL); 
            case DELETE  -> new Glyph(FONT_FAMILY, FontAwesome.Glyph.TRASH);
            case SAVE    -> new Glyph(FONT_FAMILY, FontAwesome.Glyph.SAVE);
            case BACK    -> new Glyph(FONT_FAMILY, FontAwesome.Glyph.ARROW_LEFT);
            ...
        };
    }
}

This way I can easily swap out the app icon nodes if required and decouple graphic implementation details from my controls.

In my JavaFX controller, I'm injecting GraphicNodeProvider via the constructor:

public class AuthController {
    @FXML
    private Button loginButton;

    @FXML
    private Button signupButton;

    private final GraphicNodeProvider graphicProvider;

    public AuthController(GraphicNodeProvider graphicProvider) {
        this.graphicProvider = graphicProvider;
    }
}

Currently, I'm setting the graphics of all the buttons and other nodes in the controller's initialize() method using the injected GraphicNodeProvider instance:

public class AuthController {
    ...
    public void initialize() {
         loginButton.setGraphic(graphicProvider.getNode(Graphic.LOGIN));
         signupButton.setGraphic(graphicProvider.getNode(Graphic.SIGNUP));
         ...
    }

However, I would like to set the graphic in the FXML file instead of the controller. Is there a way to access the injected field graphicProvider in AuthControllerand call its getNode() method from the FXML file using FXML syntax:

...
<HBox alignment="CENTER">
    <Button fx:id="loginButton" graphic="#graphicProvider.getNode(Graphic.LOGIN)" text="%auth.login.button.login"/>
<Button fx:id="signUpButton" graphic="#graphicProvider.getNode(Graphic.SIGNUP)"  text="%auth.signup.button.signup"/>
</HBox>

Is there a way I can set the graphic of the buttons in the FXML above by calling graphicProvider.getNode()?

Project Link for Reference: https://github.com/amal-stack/notebooksfx

Amal K
  • 4,359
  • 2
  • 22
  • 44
  • 2
    I don't see anything in the documentation. Have you tried exposing a map view of your `GraphicProvider` (i.e. `public Map getNodes() { ... }`) and then ``? FXML doesn't really allow you to invoke methods in general, just to access properties, and that's the only way I could see to set this up. – James_D Apr 11 '23 at 13:45
  • 1
    I would recommend https://kordamp.org/ikonli/ over `FontAwesome` – SedJ601 Apr 11 '23 at 14:43
  • 1
    Also, I don't understand why you would go through all of that using `FXML`. Just use it as it is designed to be used in `FXML`. https://stackoverflow.com/questions/24430121/how-to-use-font-awesome-in-a-fxml-project-javafx – SedJ601 Apr 11 '23 at 14:45
  • @James_D The map view solution works fine when I define a getter for `graphicProvider` in the controller. However, I have to convert the map key from my `Graphic` enum to `String`. Is there a way to refer to the enum directly in the expression, maybe with `fx:value`? – Amal K Apr 11 '23 at 16:07
  • 1
    @SedJ601 I could have defined the nodes directly in FXML as your linked answer, but if I ever switch my graphic/icon library, this pattern would help to avoid making changes in every FXML file, all you have to do is create a new implementation of `GraphicNodeProvider`. – Amal K Apr 11 '23 at 16:10
  • 2
    @AmalK I don't think there's a way to do that which is convenient. You could try some combination such as `` and then just reference `login` in the expression binding, but it looks like more trouble that it's worth. – James_D Apr 11 '23 at 16:44
  • There may also be a solution to this using a custom [`builderFactory`](https://openjfx.io/javadoc/20/javafx.fxml/javafx/fxml/FXMLLoader.html#setBuilderFactory(javafx.util.BuilderFactory)) on the `FXMLLoader`, which I'd need to think about a little. – James_D Apr 11 '23 at 16:47
  • @James_D Also, is there a reason to use an expression binding: `${controller.graphicProvider.nodes.LOGIN}` instead of variable interpolation: `$controller.graphicProvider.nodes.LOGIN`? I suppose the former creates a binding making it unable to set the value later if required. Either way, IntelliJ gives an error, but it works when I run it. – Amal K Apr 12 '23 at 07:57
  • 1
    @AmalK No: I agree in your use case the variable interpolation may be better. – James_D Apr 12 '23 at 12:46

0 Answers0