0

我有一个主窗体 (MainForm.fxml),它的控制器在 fxml 文件中定义。在同一个 fxml 文件中,我有 2 个包含在 fx:include 中的子表单(Subform1.fxml 和 Subform2.fxml)。Subform1 有一个具体的控制器。Subform2 是一个通用的“选择和编辑”表单,后面带有抽象代码。我想根据上下文显示具有抽象代码的不同具体实现的 Subform2。如果我在 fxml 中定义控制器,那么它将不再是通用的。

我只使用 FXMLLoader 来加载 MainForm,我找不到任何地方可以更改子窗体的控制器。我在房子周围走遍了尝试不同的东西。任何帮助将非常感激。

更新我的问题 感谢 James_D 迄今为止的帮助。我的 Subform1 在 fxml 文件中的定义:

    <children>
         <!--<fx:include source="Subform1.fxml" />-->
         <!-- <Subform1 controller="${ISubform}" /> -->
         <Subform1 controller="${Subform1Controller}" />
         <!-- <Subform1 /> -->
    </children>

我创建了一个如下界面:

package testsubforms;

public interface ISubform {
}

这是我的控制器:

package testsubforms;

public class Subform1Controller implements ISubform {
    public Subform1Controller() {
        System.out.println("Inside Subform1Controller");
    }
}

以下是我的 Subform1 类:

package testsubforms;

import java.io.IOException;
import javafx.beans.NamedArg;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.GridPane;

public class Subform1 extends GridPane {
    private ObjectProperty controller;

    public ObjectProperty controllerProperty() {
        return this.controller;
    }

    public void setController(Subform1Controller controller) {
        this.controllerProperty().set(controller);
    }


    public Subform1(@NamedArg("controller") Subform1Controller controller) throws IOException {
        this.controller = new SimpleObjectProperty(this, "controller", controller);
        FXMLLoader loader = new FXMLLoader(getClass().getResource("Subform1.fxml"));
        loader.setRoot(this);
        loader.setController(controller);
        loader.load();
    }

    public Subform1() throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("Subform1.fxml"));
        loader.setRoot(this);
        loader.setController(this);
        loader.load();
    }
}

我当前的问题是运行时错误“javafx.fxml.LoadException:无法绑定到非类型化对象”,我在 fxml 文件中指定了 Subform1。任何帮助使拼图中的最后一块工作都将不胜感激。一旦我完成了最后一个工作,我将发布完整的示例供其他人使用。

4

1 回答 1

1

一种方法是将接口指定为Subform2.fxml. 例如,定义

public interface Subform2Controller {

}

那么您可以将该接口指定为控制器“类”:

<GridPane xmlns="..." fx:controller="my.package.Subform2Controller">
    <!-- -->
</GridPane>

现在指定一个专门处理这种情况的控制器工厂:

Object subform2Controller = /* any controller implementation you like... */ ;
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/main.fxml"));
loader.setControllerFactory(type -> {
    try {
        if (type == Subform2Controller.class) {
            return subform2Controller ;
        }
        // default implementation:
        return type.newInstance();
    } catch (Exception exc) {
        // this is pretty much fatal...
        throw new RuntimeException(exc);
    }
});
Parent root = loader.load();

这里的想法是控制器工厂是一个函数,FXMLLoader用于将 FXML 文件中声明的类映射到特定对象。(默认情况下,它只调用指定newInstance()Class。)当您使用 FXML 包含时,控制器工厂会向下传播以加载包含的文件。此实现只是截取定义接口的特定情况并返回您在代码中动态指定的任何对象。

据我所知,实际上并没有要求返回的对象是指定类的实例(尽管我想我从未对此进行过测试)。无论如何,如果您确实确保控制器是实现fx:controller属性中声明的接口的类的实例,它可能会帮助您保持理智(这也使您有机会指定您希望控制器提供的任何功能)。


另一种方法是使用FXML“自定义组件”模式。这从本质上颠倒了 FXML 和控制器的创建角色,这意味着不是加载 FXML 文件,它或多或少静默地创建控制器实例,而是创建一个用作控制器的 Java 对象,它负责加载FXML。

使用这种方法,您可以创建多个“自定义组件”,它们都加载相同的 FXML 文件。

因此,如果您之前有一个Subform2.fxml看起来像:

<!-- headers, etc -->

<GridPane xmlns="..." fx:controller="...">
    <!-- -->
</GridPane>

您可以将根元素替换为:

<!-- headers, etc -->

<fx:root type="GridPane" xmlns="..." >
    <!-- -->
</fx:root>

请注意,此处不再指定控制器。

现在您可以创建一个类似控制器的类,它只需要扩展(或更一般地说,它扩展了元素GridPane的“type”属性中指定的类)。fx:root在构造函数中,FXMLLoader为 FXML 文件创建一个,并将根和控制器都设置为当前对象:

public class Subform2 extends GridPane {

    @FXML
    private TextField someTextField ;

    // etc

    public Subform2() throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/Subform2.fxml"));
        loader.setRoot(this);
        loader.setController(this);
        loader.load();
    }

    @FXML
    public void handleSomeEvent(ActionEvent event) {
        // ...
    }

    // ...

}

要使用它,您可以在 Java 中简单地使用

GridPane subform2 = new Subform2();

如果您想在 FXML 中使用它,而不是使用<fx:include>,只需使用常规实例元素即可。当然,您可以像往常一样指定任何属性,无论它们是继承自GridPane还是在类本身中定义它们:

<Subform2 alignment="center">
    <padding>
        <Insets top="5" right="5" bottom="5" left="5"/>
    </padding>
</Subform2>

这可能满足您的需求,因为您可以只定义一个 FXML 文件,但也可以任意定义许多像这样的不同类,它们都加载该单个 FXML 文件。

作为一个轻微的变体,您可以使GridPane子类简单地成为 FXML 的根,并将另一个对象传递给它的构造函数以充当控制器。因此,例如,如果您为 FXML 文件定义了一个表示控制器的接口(或抽象类):

public interface Subform2Controller {

    /* methods */

}

你可以做

public class Subform2 extends GridPane {

    public Subform2(@NamedArg("controller") Subform2Controller controller) throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/Subform2.fxml"));
            loader.setRoot(this);
            loader.setController(controller);
            loader.load();
        }
    }
}

这使您可以执行以下操作

<Subform2 controller="${subform2Controller}" />

同样,它允许您加载 FXML 文件并动态指定控制器。

于 2018-02-07T04:31:07.647 回答