Inheritance 如何使用FXML正确地对自定义JFX组件进行子类化?

Inheritance 如何使用FXML正确地对自定义JFX组件进行子类化?,inheritance,javafx,java-8,fxml,fxmlloader,Inheritance,Javafx,Java 8,Fxml,Fxmlloader,我想对自定义JFX组件进行子类化,以更改/扩展它们的行为。作为一个真实的例子,我想扩展一个具有编辑功能的data viewer组件 考虑以下非常小的场景。 使用类Super非常有效。 但是当实例化子类Sub(在FXML文件中)时,FXMLLoader不再注入@FXML字段标签。 因此,当使用值null访问字段时,调用initialize会导致NullPointerException。我想fxmloader需要一些信息,以便使用Super.fxml初始化sub的Super子对象 请注意,方法ini

我想对自定义JFX组件进行子类化,以更改/扩展它们的行为。作为一个真实的例子,我想扩展一个具有编辑功能的data viewer组件

考虑以下非常小的场景。 使用类
Super
非常有效。 但是当实例化子类
Sub
(在FXML文件中)时,
FXMLLoader
不再注入
@FXML
字段
标签。
因此,当使用值
null
访问字段时,调用
initialize
会导致
NullPointerException
。我想
fxmloader
需要一些信息,以便使用Super.fxml初始化
sub
Super
子对象

请注意,方法
initialize
在注入后由
fxmloader
自动调用

我知道将超级组件嵌套在子组件中应该可以很好地工作,但是我仍然想知道使用继承是否可以做到这一点

标签
的可见性扩大到
受保护
显然无法解决此问题。在
fx:root
中结合
@DefaultProperty
定义扩展点(已提出此解决方案)都不起作用

谢谢你的帮助

fxml/Super.fxml

fxml/Sub.fxml


更新

像这样,方法似乎是为每一级继承调用
fxmloader
(它附带了一个FXML文件)。问题归结为注入
@FXML
-连接到调用
初始化
的带注释字段。也就是说,如果我们希望字段被注入,
initialize
会在每次加载之后被调用。但是当
initialize
被每个子类覆盖时,最具体的实现会被调用
n
次(其中
n
是继承级别的数量)

差不多

public void initialize() {
    if (getClass() == THISCLASS) {
        realInitialize();
    }
}
[Update]不会解决这个问题,但对我来说似乎是一个黑客

以@mrak为例,它显示了每个继承级别上的负载。当我们在两个级别上实现
initialize
方法时,就会出现上述问题


这里有一个基于的更完整的最小工作示例

Super.java

Super.fxml


Sub.fxml


请参阅
Super.loadFxml
中的注释行。使用此条件将导致在叶中只注入
@FXML
项。但是,
initialize
只被调用一次。不使用此条件将导致(理论上)注入所有
@FXML
条目。但是
initialize
在每次加载之后都会发生,因此
NullPointerException
s会在每次非叶初始化时发生


当根本不使用
初始化
并自己调用一些init函数时,这个问题可以得到解决。但是,这对我来说又是一个非常棘手的问题。

看起来您没有在Sub.xml中定义标签,这可能就是为什么没有向
标签
字段中注入任何内容的原因。尝试更新Sub.xml以包含以下内容:

<?import Super?>

<fx:root xmlns:fx="http://javafx.com/fxml/1" type="Super">
    <Label fx:id="label"/>
</fx:root>

这样行吗


问题在于,在实例化
子类时,在
Super
中调用
getClass()
会返回
Sub.class
。所以它加载Sub.xml,我想这不是您想要的(看起来您试图同时加载Super.xml和Sub.xml)。您可以通过在
Super
构造函数中显式加载Super.xml并在
Sub
构造函数中显式加载Sub.xml来实现这一点。

我想我看到了问题所在。如果未在
Super()
中调用
setController()
,则没有插入
label
的位置,因此该字段保持为
null
。如果确实在super中调用了
setController()
,那么
Sub
initialize()
实现将被调用两次,一次是在
super()
中调用
load()
,另一次是在
Sub
中调用
load()


从理论上讲,只要您在
Sub
中防范NPE,这应该是可行的。如果调用了
Sub#initialize()
,并且
按钮仍为
null
,则您知道正在为
Super
进行初始化,您应该委托给
Super.initialize()
。当
按钮
为非
时,您不会调用
超级

我知道这篇文章有点老了,但我遇到了同样的问题,最终找到了一个解决方案,可以在继承和在子级和父级中都有注入和属性时正确初始化父级/子级。以下是我正在使用的简单体系结构:

public class Parent extends HBox {

    @FXML
    private Label labelThatIsInBothFXMLs;

    public Parent() {
        this(true);
    }

    protected Parent(boolean doLoadFxml) {
        if (doLoadFxml) {
            loadFxml(Parent.class.getResource(...));
        }
    } 

    protected void loadFxml(URL fxmlFile) {
        FXMLLoader loader .... //Load the file
    }

    @Initialize
    protected void initialize() {
        // Do parent initialization.
        labelThatIsInBothFXMLs.setText("Works!");
    }

}

public class Child extends Parent {

    @FXML
    private Label labelOnlyInChildFXML;

    public Child() {
        super(false);
        loadFxml(Child.class.getResource(...));
    }

    @Override
    protected void initialize() {
        super.initialize();
        // Do child initialization.
        labelOnlyInChildFXML.setText("Works here too!");
    }
}

需要注意的重要部分是,最低级别的子级是调用fxml加载的子级。这是为了在fxml加载开始使用反射注入数据之前运行所有级别的构造函数。如果父级加载fxml,则子级尚未创建类属性,从而导致反射注入失败。FXML中设置的属性也是如此。

有一种简单的方法可以解答Flipbed的问题

public class Super extends HBox {

@FXML
private Label label;

public Super() {
    super();
    if(getClass() == Super.class)
        loadFxml(Super.class.getResource("/fxml/Super.fxml"), this, Super.class);
}

这就是您所需要的

是的,将标签也添加到子类中也可以。但这正是我想要避免的。想象一下
Super
是一个巨大的组件。然后我必须复制整个FXML规范。是的,我想加载两个fxml文件。首先是
Super
子对象的Super.fxml,然后是
sub
子对象的sub.fxml。但是
fxmloader
在每次加载后运行
initialize
。因此,不完全注入的对象会运行。如果只在子类中调用setController(),会怎么样
public void initialize() {
    if (getClass() == THISCLASS) {
        realInitialize();
    }
}
package test;

import java.io.IOException;
import java.net.URL;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;

public class Super extends HBox {

    @FXML
    private Label label;

    public Super() {
        super();
        loadFxml(Super.class.getResource("/fxml/Super.fxml"), this, Super.class);
    }

    public void initialize() {
        label.setText("initialized");
    }

    protected static void loadFxml(URL fxmlFile, Object rootController, Class<?> clazz) {
        FXMLLoader loader = new FXMLLoader(fxmlFile);
        if (clazz == rootController.getClass()) { // PROBLEM
            loader.setController(rootController);
        }
        loader.setRoot(rootController);
        try {
            loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
package test;

import javafx.fxml.FXML;
import javafx.scene.control.Button;

public class Sub extends Super {

    @FXML
    private Button button;

    public Sub() {
        super();
        loadFxml(Sub.class.getResource("/fxml/Sub.fxml"), this, Sub.class);
    }

    @Override
    public void initialize() {
        super.initialize();
        button.setText("initialized");
    }

}
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.*?>

<fx:root xmlns:fx="http://javafx.com/fxml/1" type="HBox">
    <Label fx:id="label" text="not initialized"/>
</fx:root>
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import test.Super?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>

<fx:root xmlns:fx="http://javafx.com/fxml/1" type="Super">
    <Button fx:id="button" text="not initialized"/>
</fx:root>
<?import Super?>

<fx:root xmlns:fx="http://javafx.com/fxml/1" type="Super">
    <Label fx:id="label"/>
</fx:root>
public class Parent extends HBox {

    @FXML
    private Label labelThatIsInBothFXMLs;

    public Parent() {
        this(true);
    }

    protected Parent(boolean doLoadFxml) {
        if (doLoadFxml) {
            loadFxml(Parent.class.getResource(...));
        }
    } 

    protected void loadFxml(URL fxmlFile) {
        FXMLLoader loader .... //Load the file
    }

    @Initialize
    protected void initialize() {
        // Do parent initialization.
        labelThatIsInBothFXMLs.setText("Works!");
    }

}

public class Child extends Parent {

    @FXML
    private Label labelOnlyInChildFXML;

    public Child() {
        super(false);
        loadFxml(Child.class.getResource(...));
    }

    @Override
    protected void initialize() {
        super.initialize();
        // Do child initialization.
        labelOnlyInChildFXML.setText("Works here too!");
    }
}
public class Super extends HBox {

@FXML
private Label label;

public Super() {
    super();
    if(getClass() == Super.class)
        loadFxml(Super.class.getResource("/fxml/Super.fxml"), this, Super.class);
}