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

如何正确使用JFXML自定义子类?

洪浩
2023-03-14

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

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

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

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

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

谢谢你的帮助。

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>

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

使现代化

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

差不多

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

[更新]不会[/更新]解决这个问题,但对我来说就像一个黑客。

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

这里是一个基于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();
        }
    }

}

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

}

超级的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会在每次非叶初始化时发生。

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


共有3个答案

子车轶
2023-03-14

我知道这篇文章有点旧,但我遇到了同样的问题,最终找到了一个解决方案,在继承和在子代和父代中具有注入和属性时正确初始化父代/子代。下面是我使用的简单架构

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

全弘深
2023-03-14

看起来不像是在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。

邢冷勋
2023-03-14

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

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

 类似资料:
  • 如何通过带有自定义字段的wp rest api v2正确创建自定义post类型记录?无法创建自定义字段或更新它们。 我在一个自托管的wordpress安装中安装了超级列表和超级列表子主题。 此主题使用自定义分类法来定义类别、位置、称为业务的自定义帖子类型以及自定义帖子类型的帖子元中的自定义字段 我正在尝试使用rest api v2将数据导入站点,并成功创建了自定义类别和位置。我使用REST API

  • 2-我在FlatList中实现分页,所以当用户到达数据列表的末尾时,我调用一个函数来增加当前页,并根据当前页更新的情况,再次提取,因为我将to useEffect传递给依赖项数组 所以这里的问题是应该将以前的数据与新数据联系起来,所以我使用方法, 它工作得很好,但有时我收到一个警告,告诉我有一个重复的数据,当我使用扩展时不工作,我得到一个很大的错误,因为Flatlist keyExtractor问

  • 问题内容: 我正在尝试实施延迟的博客帖子删除方案。因此, 您确定要 烦恼 吗? ,您有2分钟的时间取消删除。 我想跟踪在使用db.Model类( DeleteQueueItem )时将要删除的内容,因为我发现无法从队列中删除任务,并且怀疑我可以查询其中的内容。 创建 DeleteQueueItem 实体应自动设置 delete_when 属性并将任务添加到队列中。我将博客文章的相对路径用作其 ke

  • 我正在尝试使用DWM为我的窗体制作一个自定义的窗口框架。平台是C# WinForms,Pinvoking DWM。 按照MSDN关于用DWM制作定制窗框的文章,主要步骤如下: 移除标准帧(非客户端区域),回复WM_NCCALCSIZE消息返回0 使用DwmExtenFrameIntoClientArea函数将框架扩展到客户端区域 我以下一种方式处理WM_NCCALCSIZE消息: 根据WM_ NC

  • 我试图熟悉JavaScript中的函数式编程。我刚读到指针函子是: 具有of函数的对象,可将任何单个值放入其中。 ES2015添加了一个rray.of使数组成为一个指向的仿函数。 我的问题是“单一价值”到底是什么意思? 我想做一个函数/容器(像https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch8.html),它持有给定维度(

  • 问题内容: 实施自己的最佳方法是哪一种? 其实我得到一个问题登记。如果我在中使用自定义类,则管理页面上不会显示任何应用程序。 我用一个小技巧解决了这个问题。我写了这个课: 并像这样实现我的自定义AdminSite: 所以,我可以用这个为。 有人知道更好的方法吗?由于我以下划线开头访问var,所以它不过是hack。我不喜欢黑客。 编辑:另一种方法是重写函数,但是在这种情况下,我将有冗余代码。 问题答