2

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

考虑以下非常小的场景。使用该课程Super非常有效。但是当实例化子类Sub(在 FXML 文件中)时,FXMLLoader不再注入该@FXML字段label。因此,调用initialize会导致 aNullPointerException访问具有 value 的字段null。我想FXMLLoader不知何故需要这些信息来初始化使用Super.fxmlSuper的子对象。Sub

请注意,该方法会在注入后initialize自动调用。FXMLLoader

我知道将超级组件嵌套在子组件中应该可以正常工作,但我仍然想知道这是否可以使用继承。

扩大labelto的知名度protected显然并没有解决这个问题。fx:root结合(此处@DefaultProperty已提出此解决方案)定义扩展点均无效。

我很感激任何帮助。

fxml/超级.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>

超级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>

子.java

public class Sub extends Super {
    public Sub() {
        super();
    }
}

更新

就像在这个问题中一样,要走的路似乎是FXMLLoader为每个继承级别调用(附加了一个 FXML 文件)。问题归结为注入@FXML-annotated 字段,然后连接到调用initialize。意思是,如果我们希望这些字段被注入,initialize之后会为每个 single 调用load。但是当initialize被每个子类覆盖时,最具体的实现会被调用n次数(n继承级别的数量在哪里)。

就像是

public void initialize() {
    if (getClass() == THISCLASS) {
        realInitialize();
    }
}

[Update]不会[/Update]解决这个问题,但对我来说似乎是一个 hack。

考虑@mrak 的这个演示代码,它显示了每个继承级别的加载。当我们initialize在两个级别中实现方法时,就会出现上述问题。


这是一个基于mraks 代码的更完整的最小工作示例。

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

}

子.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");
    }

}

超级.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>

子.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在每次加载后NullPointerException发生,因此 s 在每次非叶初始化时发生。

initialize当根本不使用并自己调用一些init函数时,可以解决该问题。但同样,这对我来说似乎很老套。

4

4 回答 4

0

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

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 中设置的属性也是如此。

于 2017-03-22T15:00:01.377 回答
0

我想我看到了问题。如果不调用setController()Super()就没有注入的地方label,所以字段仍然存在null。如果你确实调用setController()了 super ,那么Sub的实现会initialize()被调用两次——一次是调用load()in Super(),另一次是调用load()in Sub

从理论上讲,只要您防范Sub. 如果Sub#initialize()被调用并且button仍然是null,你知道你正在被初始化Super并且你应该委托给super.initialize(). 什么时候button是非null,你不会打电话super

于 2015-12-12T12:47:23.640 回答
0

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

<?import Super?>

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

那样有用吗?

问题是在实例化 a 时对getClass()in的调用会Super返回。所以它加载了 Sub.xml,我猜这不是你想要的(看起来你正在尝试同时加载Super.xmlSub.xml)。您可以通过在构造函数中显式加载 Super.xml 并在构造函数显式加载Sub.xml来做到这一点。Sub.classSubSuperSub

于 2015-12-11T16:42:00.893 回答
0

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

这就是你所需要的

于 2018-11-12T07:40:11.400 回答