简体   繁体   English

为什么SceneBuilder对自定义控件如此特别?

[英]Why is SceneBuilder so particular about Custom Controls?

I was hoping someone would be able to explain to me exactly why SceneBuilder is so tempermental when it comes to importing custom controls. 我希望有人能够向我确切地解释为什么SceneBuilder在导入自定义控件时如此脾气暴躁。

Take for example a relatively simple custom control (As only an example): 以一个相对简单的自定义控件为例(仅作为示例):

public class HybridControl extends VBox{
    final private Controller ctrlr;
    public CustomComboBox(){
        this.ctrlr = this.Load();
    }

    private Controller Load(){
        final FXMLLoader loader = new FXMLLoader();
        loader.setRoot(this);
        loader.setClassLoader(this.getClass().getClassLoader());
        loader.setLocation(this.getClass().getResource("Hybrid.fxml"));
        try{
            final Object root = loader.load();
            assert root == this;
        } catch (IOException ex){
            throw new IllegalStateException(ex);
        }

        final Controller ctrlr = loader.getController();
        assert ctrlr != null;
        return ctrlr;
    }
    /*Custom Stuff Here*/
}

And then you have the Controller class here: 然后在这里有Controller类:

public class Controller implements Initializable{
    /*FXML Variables Here*/
    @Override public void initialize(URL location, ResourceBundle resources){
        /*Initialization Stuff Here*/
    }
}

This works just fine. 这样很好。 The .jar compiles fine, SceneBuilder reads the .jar just fine, it imports the control just fine, which is great. .jar可以很好地编译,SceneBuilder可以很好地读取.jar,它可以很好地导入控件。

The thing that irks me is that it requires two separate classes to accomplish, which is not THAT big of a deal except that I feel like this should be doable with just a single class. 让我感到烦恼的是,它需要两个单独的类来完成,这没什么大不了的,只是我觉得这应该只对一个类就可以完成。

I have it as above now, but I've tried two other ways that both fail (SceneBuilder won't find and let me import the controls) and I was hoping someone would tell me why so I can get on with my life. 我现在如上所述,但是我尝试了两种其他方法都失败了(SceneBuilder找不到并允许我导入控件),我希望有人能告诉我为什么,这样我就可以继续我的生活。

In the second case I attempted a single class which extended a VBox and implemented Initializable: 在第二种情况下,我尝试了一个扩展VBox并实现Initializable的类:

public class Hybrid extends VBox implements Initializable{ /*In this case the FXML file Controller would be set to this class.*/
    /*FXML Variables Here*/
    public Hybrid(){
        this.Load();
    }
    private void Load(){
        final FXMLLoader loader = new FXMLLoader();
        loader.setRoot(this);
        loader.setClassLoader(this.getClass().getClassLoader());
        loader.setLocation(this.getClass().getResource("Hybrid.fxml"));
        try{
            final Object root = loader.load();
            assert root == this;
        } catch (IOException ex){
            throw new IllegalStateException(ex);
        }
        assert this == loader.getController();
    }
    @Override public void initialize(URL location, ResourceBundle resources){
        /*Initialization Stuff Here*/
    }

}

This makes PERFECT sense to me. 这对我来说很完美。 It SHOULD work, at least in my head, but it doesn't. 它应该至少在我的脑海中起作用,但事实并非如此。 The jar compiles fine, and I'd even wager it would work perfectly fine in a program, but when I try to import the .jar into Scene Builder, it doesn't work. 该jar可以很好地编译,我什至下注它在程序中可以正常运行,但是当我尝试将.jar导入Scene Builder时,它不起作用。 It's not present in the list of importable controls. 它不在可导入控件列表中。

So... I tried something different. 所以...我尝试了不同的东西。 I tried nesting the Controller class within the Control class: 我尝试将Controller类嵌套在Control类中:

public class Hybrid extends VBox{ /*In this case the FXML Controller I had set to Package.Hybrid.Controller*/
    final private Controller ctrlr
    public Hybrid(){
        this.ctrlr = this.Load();
    }
    private Controller Load(){
        /*Load Code*/
    }
    public class Controller implements Initializable{
        /*Controller Code*/
    }
}

This didn't work either. 这也不起作用。 I tried it public, private, public static, private static, none of them worked. 我尝试了public,private,public static,private static,但没有一个起作用。

So why is this the case? 那么为什么会这样呢? Why does SceneBuilder fail to recognize a custom control unless the Control class and the Controller class are two separate entities? 为什么除非Control类和Controller类是两个单独的实体,否则SceneBuilder无法识别自定义控件?

EDIT: 编辑:

Thanks to James_D below I was able to get an answer AND make custom controls work the way I would like. 感谢下面的James_D,我能够得到一个答案,并使自定义控件以我想要的方式工作。 I was also able to create a generic Load method that works for all custom classes if the name of the Class is the same as the name of the FXML file: 如果Class的名称与FXML文件的名称相同,我还能够创建一个适用于所有自定义类的通用Load方法:

private void Load(){
    final FXMLLoader loader = new FXMLLoader();
    String[] classes = this.getClass().getTypeName().split("\\.");
    String loc = classes[classes.length - 1] + ".fxml";
    loader.setRoot(this);
    loader.setController(this);
    loader.setClassLoader(this.getClass().getClassLoader());
    loader.setLocation(this.getClass().getResource(loc));
    try{
        final Object root = loader.load();
        assert root == this;
    } catch (IOException ex){
        throw new IllegalStateException(ex);
    }
    assert this == loader.getController();
}

Just thought I would share that. 只是想我会分享。 Note, again, that it only works if, for example, your class was named Hybrid, and your FXML file was named Hybrid.fxml. 再次注意,它仅在例如您的类名为Hybrid且您的FXML文件名为Hybrid.fxml时才有效。

Your second (and third) version won't work at all (SceneBuilder or no SceneBuilder), because the assertion 您的第二个(和第三个)版本根本不起作用(SceneBuilder或没有SceneBuilder),因为断言

this == loader.getController()

will fail. 将失败。 When you call loader.load() the FXMLLoader sees the fx:controller="some.package.Hybrid" and creates a new instance of it . 当您调用loader.load()FXMLLoader看到fx:controller="some.package.Hybrid"fx:controller="some.package.Hybrid" 创建一个新实例 So now you have two instances of the Hybrid class: the one which invoked load on the FXMLLoader and the one which is set as the controller of the loaded FXML. 因此,现在您有了Hybrid类的两个实例:一个在FXMLLoader上调用load ,另一个被设置为已加载的FXML的控制器。

You need to remove the fx:controller attribute from the FXML file, and set the controller directly in your code, as in the documentation : 您需要从FXML文件中删除fx:controller属性,并直接在代码中设置控制器,如文档所示

private void Load(){
    final FXMLLoader loader = new FXMLLoader();
    loader.setRoot(this);
    loader.setClassLoader(this.getClass().getClassLoader());
    loader.setLocation(this.getClass().getResource("Hybrid.fxml"));

    // add this line, and remove the fx:controller attribute from the fxml file:
    loader.setController(this);

    try{
        final Object root = loader.load();
        assert root == this;
    } catch (IOException ex){
        throw new IllegalStateException(ex);
    }
    assert this == loader.getController();
}

Experimenting with SceneBuilder, it seems it will attempt to create custom controls by calling their no-arg constructor, and expects that to complete without creating an exception. 使用SceneBuilder进行试验,似乎它将尝试通过调用其no-arg构造函数来创建自定义控件,并期望该过程能够完成而不会产生异常。 It does seem like it's not able to handle injecting @FXML annotated values correctly in this particular scenario. 在这种特定情况下,似乎无法正确处理注入@FXML注释的值。 I would recommend filing a bug at jira for this. 我建议为此在吉拉提交一个错误。

As a workaround, you will probably have to write your code so that executing the no-arg constructor completes without throwing an exception even if the @FXML -annotated fields are not injected. 解决方法是,您可能必须编写代码,以便即使没有注入@FXML字段,也可以完成no-arg构造函数的执行而不会引发异常。 (Yes, this is a pain.) (是的,这很痛苦。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM