我有一个主窗体(MainForm.fxml),它的控制器在fxml文件中定义。在这个相同的fxml文件中,我有2个子表单(Subform1.fxml和Subform2.fxml),我包含在fx:include中。 Subform1有一个具体的控制器。 Subform2是一个通用的“选择和编辑”表单,后面有抽象代码。我想根据上下文显示具有抽象代码的不同具体实现的Subform2。如果我在fxml中定义控制器,那么它将不再是通用的。
我只使用FXMLLoader来加载MainForm,我找不到任何方法来更改子窗体的控制器。我四处走动,尝试不同的事情。任何帮助将不胜感激。
更新我的问题 感谢James_D到目前为止的帮助。 fxml文件中我的Subform1的定义:
<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。 任何有助于在拼图中获得最后一块作品的帮助将非常感激。一旦我得到最后一篇文章,我将发布完整的例子供其他人使用。
答案 0 :(得分:0)
一种方法是将接口指定为Subform2.fxml
中的控制器类。例如,定义
public interface Subform2Controller {
}
然后您可以将该接口指定为控制器“class”:
<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 "custom component" pattern。这实际上颠倒了FXML和控制器的创建角色,这意味着您不是加载一个或多或少静默创建控制器实例的FXML文件,而是创建一个充当控制器的Java对象,并负责加载FXML。
使用这种方法,您可以创建多个“自定义组件”,这些组件都加载相同的FXML文件。
因此,如果您的Subform2.fxml
看起来像之前:
<!-- headers, etc -->
<GridPane xmlns="..." fx:controller="...">
<!-- -->
</GridPane>
你要用:
替换根元素<!-- headers, etc -->
<fx:root type="GridPane" xmlns="..." >
<!-- -->
</fx:root>
请注意,此处不再指定控制器。
现在你可以创建一个类似控制器的类,它只需要扩展GridPane
(或者更一般地说,它扩展了fx:root
元素的“type”属性中指定的类)。在构造函数中,为FXML文件创建FXMLLoader
,并将根和控制器都设置为当前对象:
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文件并动态指定控制器。