Inheritance 如何使用FXML正确地对自定义JFX组件进行子类化?
我想对自定义JFX组件进行子类化,以更改/扩展它们的行为。作为一个真实的例子,我想扩展一个具有编辑功能的data viewer组件 考虑以下非常小的场景。 使用类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
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);
}