当前位置: 首页 > 知识库问答 >
问题:

为什么SceneBuilder对自定义控件如此挑剔?

湛文乐
2023-03-14

我希望有人能向我解释一下为什么SceneBuilder在导入自定义控件时如此温和。

以一个相对简单的自定义控件为例(仅作为示例):

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*/
}

然后你有控制器类在这里:

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

这工作得很好。.jar编译得很好,SceneBuilder读取的.jar很好,它导入的控件也很好,这很好。

让我恼火的是,它需要两个独立的类来完成,这没什么大不了的,除了我觉得只用一个类就可以做到这一点。

我现在有了上面的方法,但是我尝试了另外两种方法都失败了(SceneBuilder不会找到并让我导入控件),我希望有人能告诉我为什么,这样我才能继续我的生活。

在第二种情况下,我尝试了一个扩展VBox并实现初始化的类:

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*/
    }

}

这对我来说非常有意义。它应该工作,至少在我的脑海中是这样,但它没有。jar可以很好地编译,我甚至敢打赌它在程序中可以很好地工作,但是当我尝试将. jar导入Scene Builder时,它不起作用。它不存在于可导入控件的列表中。

所以…我尝试了不同的方法。我尝试在Control类中嵌套Controller类:

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*/
    }
}

这个也不行,我试了一下公的,私的,公静态,私静态,都不行

那么为什么会这样呢?为什么SceneBuilder无法识别自定义控件,除非Control类和Controller类是两个独立的实体?

感谢下面的James_D,我能够得到一个答案,并使自定义控件以我想要的方式工作。我还能够创建一个通用的Load方法,如果类的名称与FXML文件的名称相同,该方法适用于所有自定义类:

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();
}

我只是想分享一下。请再次注意,它只在以下情况下有效,例如,您的类名为混合,而您的FXML文件名为Hybrid.fxml.

共有1个答案

乔凯康
2023-03-14

您的第二个(和第三个)版本根本无法工作(SceneBuilder或没有SceneBuildr),因为断言

this == loader.getController()

将失败。当您调用加载器.load() 时,FXMLLoader 会看到 fx:控制器=“一些.包.混合”,并创建它的新实例。因此,现在您有两个混合类的实例:一个在 FXMLLoader 上调用 load,另一个被设置为已加载 FXML 的控制器。

您需要从 FXML 文件中删除 fx:控制器属性,并直接在代码中设置控制器,如文档所示:

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();
}

尝试使用 SceneBuilder,它似乎将尝试通过调用其无参数构造函数来创建自定义控件,并期望在不创建异常的情况下完成此操作。在此特定方案中,它似乎无法正确处理注入@FXML带注释的值。我建议为此在jira上提交一个错误。

作为一种解决方法,您可能必须编写代码,以便即使未注入@FXML注释字段,也可以完成执行无参数构造函数而不会引发异常。(是的,这是一种痛苦。

 类似资料:
  • > 我是使用fx:root,还是不使用它?我选择用它。所以在控件的controller+root类中,我将自己设置为root和controller,就像需要的那样。但它仍然说“root尚未设置。在加载之前使用setRoot()方法。” 在包含自定义控件的父FXML中,我应该导入什么? 正确的类路径是什么,以便我可以在SceneBuilder2.0中显示我的自定义控件?我根本不太明白“/.../..

  • 我有一个自定义JavaFx控件,它在我的应用程序中呈现。但是,我无法让SceneBuilder理解它。 我有customTextField.java/customTextField.CustomTextField继承自UserControl,如这里所定义的,但我创建的任何自定义控件都会出现场景生成器问题。 首先,我必须将import语句更改为通配符。从 sample.fxml:

  • 我跟随这个博客向Scene Builder 2.0添加了一个自定义JavaFX组件,并构建了自己的自定义组件。 FXML文件: 控制器类: 样式表: 现在我的问题是我不能改变场景生成器中标签的值。我们是否可以创建一个自定义字段,该字段将出现在场景生成器上,并有助于更改标签文本?

  • 每隔一段时间,我就会犯这样的错误 2020-02-26 14:17:31.605963:I tensorflow/stream_执行器/平台/默认/dso_加载器。cc:42]已成功打开动态库libcublas。所以10.0 2020-02-26 14:17:31.829898:I tensorflow/流执行器/平台/默认/dso装载器。cc:42]已成功打开动态库libcudnn。所以7 20

  • 我需要在窗格上有一个选择监听器和选择方法,以便在单击节点时能够监视并显示突出显示。 我做了以下操作: 这工作得很好 - 但是我无法再使用场景构建器,因为我的FXML引用了此而不是。我不确定如何将我的自定义窗格放入场景构建器。我已经看过其他问题,它们都是FXML和控制器的组合 - 这只是一个。 有没有人知道这样做的方法,或者在初始化时将< code>Pane换成< code > PaneWithSe

  • 自定义控件用JavaScript和原生平台支持的语言编写。它们使用原生Tabris.js客户端的接口,并被封装在Cordova插件中。本文将介绍JavaScript的实现部分。 用JavaScript定义自定义控件 自定义控件必须继承自Widget。它能够与自定义控件的原生部分进行通信。 自定义控件类必须覆写_nativeType属性的getter以返回与原生实现匹配的类型: class MyCu