如何在运行时定义fx:include'd表单的控制器 [英] How to define the controller of an fx:include'd form at runtime

查看:87
本文介绍了如何在运行时定义fx:include'd表单的控制器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a main form (MainForm.fxml) that has its controller defined in the fxml file. In this same fxml file I have 2 subforms (Subform1.fxml and Subform2.fxml) that I have included with fx:include. Subform1 has a concrete controller. Subform2 is a general purpose 'select and edit' form with abstract code behind it. I want to display Subform2 with different concrete implementations of the abstract code depending on the context. If I define the controller in the fxml then it will not be general purpose anymore.

我仅使用FXMLLoader加载MainForm,而在任何地方都找不到改变子窗体控制器的方法.我到处走动,尝试各种不同的事情.任何帮助将不胜感激.

I am only using FXMLLoader to load the MainForm, and I can't find anywhere a way of changing the controller for the subforms. I have gone all around the houses trying different things. Any help would be much appreciated.

更新至我的问题 到目前为止,感谢James_D的帮助.我的Subform1在fxml文件中的定义:

Updates to my question Thanks to James_D for the help so far. The definition of my Subform1 in the fxml file:

    <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类:

The following is my Subform1 class:

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. 任何帮助将最后的拼图工作起来的帮助将不胜感激.一旦我完成了最后一篇文章,我将发布完整的示例供其他人使用.

My current problem is the runtime error "javafx.fxml.LoadException: Cannot bind to untyped object" where I specify Subform1 in the fxml file. Any help to get this final piece in the jigsaw to work would be much appreciated. Once I get this last piece to work I will post the complete example for others to use later.

推荐答案

一种方法是在Subform2.fxml中将接口指定为控制器类.例如,定义

One approach would be to specify an interface as the controller class in Subform2.fxml. For example, define

public interface Subform2Controller {

}

然后,您只需将该接口指定为控制器的类"即可:

then you can just specify that interface as the controller "class":

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

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

Now specify a controller factory that specifically handles that case:

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文件中声明的类映射到特定对象的函数. (默认情况下,它仅在指定的Class上调用newInstance().)使用FXML包含时,控制器工厂将向下传播以加载包含的文件.该实现只是截取了定义接口的特定情况,并返回您在代码中动态指定的任何对象.

The idea here is that the controller factory is a function that the FXMLLoader uses to map the declared class in the FXML file to a specific object. (By default it just calls newInstance() on the Class that is specified.) When you use FXML includes, the controller factory is propagated down to loading the included files. This implementation just intercepts the specific case where the interface is defined and returns whatever object you dynamically specified in the code.

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

As far as I know, there is no actual requirement for the object returned to be an instance of the class that is specified (though I guess I have never tested this out). At any rate, it might help your sanity if you do make sure the controller is an instance of a class implementing the interface declared in the fx:controller attribute (and this also gives you the chance to specify any functionality you expect that controller to provide).

另一种方法是使用.这从本质上颠倒了FXML和控制器的创建角色,这意味着您将创建一个充当控制器的Java对象,并负责加载该XML对象,而不是加载FXML文件(后者或多或少地静默创建了控制器实例). FXML.

Another approach is to use the FXML "custom component" pattern. This essentially reverses the creational roles of the FXML and the controller, meaning that instead of loading an FXML file, which more or less silently creates the controller instance, you create a Java object that serves as the controller, and it takes responsibility for loading the FXML.

使用这种方法,您可以创建多个均加载相同FXML文件的自定义组件".

Using this approach you could create multiple "custom components" that all load the same FXML file.

因此,如果您之前有一个类似的Subform2.fxml:

So if before you had a Subform2.fxml that looked like:

<!-- headers, etc -->

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

您可以将根元素替换为:

you would replace the root element with:

<!-- headers, etc -->

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

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

Note the controller is no longer specified here.

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

Now you can create a controller-like class, which just has to extend GridPane (or more generally, it extends the class specified in the "type" attribute of the fx:root element). In the constructor, create an FXMLLoader for the FXML file, and set both the root and the controller to the current object:

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中使用

To use this, you can do so in Java simply with

GridPane subform2 = new Subform2();

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

If you want to use it in FXML, instead of using an <fx:include>, just use a regular instance element. You can, of course, specify any properties as usual, whether they are inherited from GridPane or whether you define them in the class itself:

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

这可以满足您的需求,因为您可以只定义一个FXML文件,但是可以任意选择许多类似的类来加载该单个FXML文件.

This may meet your needs, as you can define just a single FXML file, but arbitrarily many different classes like this that all load that single FXML file.

作为此方法的一个微小变体,您可以使GridPane子类成为FXML的根,然后将另一个对象传递给其构造函数以充当控制器.因此,例如,如果您定义了表示FXML文件控制器的接口(或抽象类):

As a slight variant of this, you can make the GridPane subclass simply the root of the FXML, and pass another object to its constructor to act as the controller. So, for example, if you have defined an interface (or abstract class) representing the controller for the FXML file:

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

这可以让您做类似的事情

This allows you to do things like

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

再次,它使您可以加载FXML文件并动态指定控制器.

which, again, lets you load an FXML file and specify the controller dynamically.

这篇关于如何在运行时定义fx:include'd表单的控制器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆