如何在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
相关产品推荐
相关产品推荐

