JavaFXUI使用FXML控制体系结构(控制和皮肤)

JavaFXUI使用FXML控制体系结构(控制和皮肤),java,model-view-controller,custom-controls,javafx-8,fxml,Java,Model View Controller,Custom Controls,Javafx 8,Fxml,在JavaFX8中,有一个用于生成自定义控件的。基本上基于: 控制 皮肤 CSS 此外,还有一个用于制作GUI的。基本上: 控制 FXML文件 CSS 我想将FXML与一起使用,所以我的问题是: 谁是FXML文件的控制器?皮肤 我必须执行以下类似代码的操作: public class MySkin extends SkinBase<MyControl> { public GaugeSkin(MyControl control) { super(control);

在JavaFX8中,有一个用于生成自定义控件的。基本上基于:

  • 控制
  • 皮肤
  • CSS
此外,还有一个用于制作GUI的。基本上:

  • 控制
  • FXML文件
  • CSS
我想将FXML与一起使用,所以我的问题是:

谁是FXML文件的控制器?皮肤

我必须执行以下类似代码的操作:

public class MySkin extends SkinBase<MyControl> {
public GaugeSkin(MyControl control) {
    super(control);
    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("MyView.fxml"));
    fxmlLoader.setRoot(control);
    fxmlLoader.setController(control);

    try {
        fxmlLoader.load();
    } catch (IOException exception) {
        throw new RuntimeException(exception);
    }
}
公共类MySkin扩展了SkinBase{
公共计量皮肤(MyControl控制){
超级(控制);
FXMLLoader FXMLLoader=新的FXMLLoader(getClass().getResource(“MyView.fxml”);
fxmlLoader.setRoot(控制);
fxmlLoader.setController(控制);
试一试{
fxmloader.load();
}捕获(IOException异常){
抛出新的RuntimeException(异常);
}
}

我认为您使用
Skin
类作为加载的FXML文件的控制器是正确的,因为正是
Skin
负责定义构成特定控件“外观”的节点

控件
类本身实际上应该只定义保存控件状态的属性,而不应该关心
外观
如何实际创建视图层次结构(也就是说,它应该只关心它的状态,而不关心它的外观)

我要做的一个区别是将
fxmloader.setController(control);
更改为
fxmloader.setController(this);
,这样
Skin
类就变成了控制器而不是控件本身

您可以做的另一件事是将
fxmloader
逻辑移动到基类中,这样您就不必在每次创建
皮肤时都复制它,类似这样:

public abstract class FXMLSkin<C extends Control> extends SkinBase<C>{

    public FXMLSkin(C control) {
        super(control);
        this.load();
    }

    private void load() {
        FXMLLoader loader = new FXMLLoader(getFXML());
        loader.setController(this);

        try {
            Node root = loader.load();
            this.getChildren().add(root);
        } catch (IOException ex) {
            Logger.getLogger(FXMLSkin.class.getName()).log(Level.SEVERE, null, ex);
        }   
    }

    protected abstract URL getFXML();
}
公共抽象类FXMLSkin扩展了SkinBase{
公共FXMLSkin(C控制){
超级(控制);
这个.load();
}
专用空心荷载(){
FXMLLoader=新的FXMLLoader(getFXML());
loader.setController(此);
试一试{
节点根=loader.load();
this.getChildren().add(根目录);
}捕获(IOEX异常){
Logger.getLogger(FXMLSkin.class.getName()).log(Level.SEVERE,null,ex);
}   
}
受保护的抽象URL getFXML();
}
我有一个页面,它的功能与上面的
FXMLSkinBase
类非常相似。它使用约定加载与派生类同名的FXML文件,这样就不需要每次都指定FXML文件名。例如,如果您的派生皮肤名为
FooControlSkin
,则控件将自动加载一个名为
FooControlSkin.FXML
的FXML文件

该类非常简单,代码可以很容易地重构为功能齐全的
FXMLSkinBase
类,以满足您的需求。

我的更好方法:

  • 不再需要将
    作为.fxml文件的根元素
  • 正在删除调用
    fxmloader.setRoot(控制);
    的需要
  • 删除调用
    fxmlLoader.setController(控制);
    的需要
  • 允许皮肤通过FXML根元素中的
    fx:controller=“{controller class name}”
    自动成为控制器
  • 允许IDE突出显示ControlNameSkin类中对FXML的伪@FXML引用
一个适当的控制类

public static class ControlName extends Control {
    @Override
    protected Skin<?> createDefaultSkin() {
        ControlNameSkin.Factory factory = new ControlNameSkin.Factory(this);
        FXMLLoader fxmlLoader = new FXMLLoader(getClass()
                 .getResource("ControlName.fxml"));
        fxmlLoader.setControllerFactory(factory);
        try {
            Node root = fxmlLoader.load();
            ControlNameSkin skin = fxmlLoader.getController();
            skin.construct(root);
            return skin;
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }
}
公共静态类ControlName扩展控件{
@凌驾
受保护的皮肤createDefaultSkin(){
ControlNameSkin.Factory工厂=新的ControlNameSkin.Factory(此);
FXMLLoader FXMLLoader=新的FXMLLoader(getClass()
.getResource(“ControlName.fxml”);
fxmlLoader.setControllerFactory(工厂);
试一试{
节点根=fxmloader.load();
ControlNameSkin skin=fxmlLoader.getController();
皮肤构造(根);
返回皮肤;
}捕获(IOEX异常){
抛出新的运行时异常(ex);
}
}
}
一个组合的皮肤和控制器类

public static class ControlNameSkin extends SkinBase<ControlName> {
    public ControlNameSkin(Factory factory) {
        super(factory.control);
        // any setup NOT using FXML references
    }

    public void construct(Node root) {
        ControlName control = getSkinnable();
        // any setup using FXML references
        getChildren().add(root);
    }

    public static class Factory implements Callback<Class<?>, Object> {
        public final ControlName control;

        public Factory(ControlName control) {
            this.control = control;
        }

        @Override
        public Object call(Class<?> cls) {
            try {
                return cls.getConstructor(Factory.class).newInstance(this);
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        }
    }
}
public静态类ControlNameSkin扩展SkinBase{
公共控制(工厂){
超级(工厂控制);
//任何不使用FXML引用的设置
}
公共void构造(节点根){
ControlName控件=getSkinnable();
//使用FXML引用的任何设置
getChildren().add(根目录);
}

公共静态类工厂实现回调这个答案的问题是,您必须使用setController(.),因此扔掉在FXML根元素中使用fx:controller属性所获得的IDE验证优势;这可能会导致意外的运行时绑定错误或NullPointerException,因为我发现了困难的方法!