【发布时间】:2015-12-11 16:27:45
【问题描述】:
我想继承自定义 JFX 组件以更改/扩展它们的行为。作为一个真实的例子,我想扩展一个具有编辑功能的数据查看器组件。
考虑以下非常小的场景。
使用 Super 类可以完美运行。
但是在实例化子类Sub(在FXML 文件中)时,FXMLLoader 不再注入@FXML 字段label。
因此,在访问值为null 的字段时,调用initialize 会导致NullPointerException。我想FXMLLoader 不知何故需要信息来初始化Sub 的Super 子对象,使用Super.fxml。
请注意initialize 方法在注入后会被FXMLLoader 自动调用。
我知道将超级组件嵌套在子组件中应该可以正常工作,但我仍然想知道这是否可以使用继承。
将label 的可见性扩大到protected 显然并没有解决这个问题。在fx:root 中定义扩展点并结合@DefaultProperty(已提出此解决方案here)均无效。
感谢您的帮助。
fxml/Super.fxml
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.*?>
<fx:root xmlns:fx="http://javafx.com/fxml/1" type="HBox">
<Label fx:id="label"/>
</fx:root>
Super.java
import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
public class Super extends HBox {
@FXML
protected Label label;
public Super() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/" + getClass().getSimpleName() + ".fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public void initialize() {
label.setText("Super");
}
}
fxml/Sub.fxml
<?import test.Super?>
<fx:root xmlns:fx="http://javafx.com/fxml/1" type="Super"></fx:root>
Sub.java
public class Sub extends Super {
public Sub() {
super();
}
}
更新
就像在这个question 中一样,要走的路似乎是为每个继承级别调用FXMLLoader(附加了一个FXML 文件)。问题归结为注入@FXML-annotated 字段,然后连接到调用initialize。意思是,如果我们希望这些字段被注入,initialize 会在之后为每个 load 调用。但是当initialize 被每个子类覆盖时,最具体的实现会被调用n 次(其中n 是继承级别的数量)。
类似
public void initialize() {
if (getClass() == THISCLASS) {
realInitialize();
}
}
[Update]不会[/Update]解决这个问题,但对我来说似乎是个黑客。
考虑@mrak 的demo code,它显示了每个继承级别的负载。当我们在两个级别中实现initialize 方法时,就会出现上述问题。
这里是基于mraks code 的更完整的最小工作示例。
Super.java
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();
}
}
}
Sub.java
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");
}
}
Super.fxml
<?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>
Sub.fxml
<?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>
请参阅Super.loadFxml 中的注释行。使用此条件会导致叶中仅注入 @FXML 条目。但是initialize 只被调用一次。不使用此条件会导致(理论上)注入所有 @FXML 条目。但是initialize 发生在每次加载之后,因此NullPointerExceptions 发生在每次非叶初始化时。
完全不使用initialize 并自己调用一些init 函数时可以解决此问题。但是,这对我来说似乎很老套。
【问题讨论】:
标签: inheritance javafx java-8 fxml fxmlloader