You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在FXML中调用JavaFX控制器的GraphicNodeProvider方法?

在FXML中直接调用控制器注入的GraphicNodeProvider设置图标

我定义了GraphicNodeProvider接口来封装图标节点的创建逻辑:

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

Graphic是枚举类型,对应不同的图标类型:

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

通过DefaultGraphicNodeProvider实现该接口,使用FontAwesome生成图标:

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);
            case LOGIN   -> new Glyph(FONT_FAMILY, FontAwesome.Glyph.SIGN_IN);
            case SIGNUP  -> new Glyph(FONT_FAMILY, FontAwesome.Glyph.USER_PLUS);
            ...
        };
    }
}

在AuthController中通过构造函数注入GraphicNodeProvider,目前在initialize()方法中设置按钮图标:

public class AuthController {
    @FXML
    private Button loginButton;

    @FXML
    private Button signupButton;

    private final GraphicNodeProvider graphicProvider;

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

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

我希望直接在FXML文件中设置图标,而非在控制器中操作,是否可以通过FXML语法访问控制器中的graphicProvider字段并调用其getNode()方法,比如这样:

...
<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>

可行方案

1. 使用FXML表达式绑定(推荐)

FXML支持${}表达式绑定语法,只需将graphicProvider通过公共getter暴露,即可在FXML中调用其方法:

步骤1:为控制器添加getter方法

public class AuthController {
    // 现有代码不变...

    public GraphicNodeProvider getGraphicProvider() {
        return graphicProvider;
    }
}

步骤2:在FXML中编写绑定表达式

先导入Graphic枚举的全限定类,再通过controller(FXML对当前控制器的默认引用)调用方法:

<?import com.yourpackage.Graphic?>
...
<HBox alignment="CENTER">
    <Button fx:id="loginButton" 
            graphic="${controller.graphicProvider.getNode(Graphic.LOGIN)}" 
            text="%auth.login.button.login"/>
    <Button fx:id="signUpButton" 
            graphic="${controller.graphicProvider.getNode(Graphic.SIGNUP)}"  
            text="%auth.signup.button.signup"/>
</HBox>

2. 自定义FXML Builder简化语法

如果希望更简洁的声明式写法,可以自定义Button的Builder类:

步骤1:创建Builder类

import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.util.Builder;

public class IconButtonBuilder implements Builder<Button> {
    private final GraphicNodeProvider provider;
    private Graphic graphic;
    private String text;

    public IconButtonBuilder(GraphicNodeProvider provider) {
        this.provider = provider;
    }

    public IconButtonBuilder graphic(Graphic graphic) {
        this.graphic = graphic;
        return this;
    }

    public IconButtonBuilder text(String text) {
        this.text = text;
        return this;
    }

    @Override
    public Button build() {
        Button button = new Button(text);
        if (graphic != null) {
            button.setGraphic(provider.getNode(graphic));
        }
        return button;
    }
}

步骤2:在FXML中使用Builder

导入Builder和枚举类后,直接通过Builder创建带图标的按钮:

<?import com.yourpackage.IconButtonBuilder?>
<?import com.yourpackage.Graphic?>
...
<HBox alignment="CENTER">
    <IconButtonBuilder fx:factory="iconButton" 
                       graphic="LOGIN" 
                       text="%auth.login.button.login"/>
    <IconButtonBuilder fx:factory="iconButton" 
                       graphic="SIGNUP"  
                       text="%auth.signup.button.signup"/>
</HBox>

注意需通过DI框架将IconButtonBuilder实例注入FXML上下文。

3. 自定义带图标支持的Button控件

创建继承自Button的自定义控件,直接支持绑定Graphic枚举:

public class IconButton extends Button {
    private final GraphicNodeProvider provider;

    public IconButton(GraphicNodeProvider provider) {
        this.provider = provider;
        graphicEnumProperty().addListener((obs, oldG, newG) -> {
            if (newG != null) {
                setGraphic(provider.getNode(newG));
            }
        });
    }

    private final ObjectProperty<Graphic> graphicEnum = new SimpleObjectProperty<>();
    public ObjectProperty<Graphic> graphicEnumProperty() { return graphicEnum; }
    public void setGraphicEnum(Graphic graphic) { graphicEnum.set(graphic); }
    public Graphic getGraphicEnum() { return graphicEnum.get(); }
}

在FXML中使用自定义控件

<?import com.yourpackage.IconButton?>
<?import com.yourpackage.Graphic?>
...
<HBox alignment="CENTER">
    <IconButton graphicEnum="LOGIN" text="%auth.login.button.login"/>
    <IconButton graphicEnum="SIGNUP" text="%auth.signup.button.signup"/>
</HBox>

这种方式最贴合FXML的声明式风格,但需要DI框架支持自定义控件的构造函数注入。


内容的提问来源于stack exchange,提问作者Amal K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 04:54:54