我在创建应用程序时设置了多个自定义控制器,并且需要一些帮助来使用JavaFX中的setControllerFactory来组织这些控制器。
我对JavaFX缺乏经验,但是花了很多时间用Scenebuilder和JavaFX创建一个小型应用程序。
应用程序的背景 该应用程序包括: -地图(实现为imageView) -带有用于拖放事件的按钮和图标的边栏。 -地图还具有单独的图层作为拖放不同图标类型的目标。
作为拖放事件的原型,我使用了Joel Graff(https://monograff76.wordpress.com/2015/02/17/developing-a-drag-and-drop-ui-in-javafx-part-i-skeleton-application/)的说明。他写道:“为了使对象在容器的边缘之外可见,它必须是父容器或其他祖先容器的子级-它必须属于层次结构的上级。在我们的拖曳图标的情况下,这意味着我们必须将其作为子级添加到RootLayout的顶级AnchorPane中。”并且他为项目使用了动态的根源。
为了自学如何在FXML中使用自定义控件,我使用了Irina Fedortsova的教程https://docs.oracle.com/javafx/2/fxml_get_started/custom_control.htm。 为了了解如何设置多个屏幕,我使用了视频https://www.youtube.com/watch?v=5GsdaZWDcdY和来自https://github.com/acaicedo/JFX-MultiScreen的代码。
构建应用程序后,应用程序的逻辑层越来越多地与表示层纠缠在一起,我觉得我的代码似乎将从某种重构中受益匪浅。 我的问题似乎是缺乏对控制器类的加载和初始化过程的了解。由于必须从一开始就加载拖动图标和RootLayout,因此如何加载这些类以便以后可以再次调用它们对我来说是一个谜。
当我寻找更多解决方案时,我反复遇到了setControllerFactory方法。不幸的是,我找不到关于如何正确使用它以及其特定用途的良好解释。 我发现的唯一教程是:https://riptutorial.com/javafx/example/8805/passing-parameters-to-fxml---using-a-controllerfactory,不幸的是,这似乎不足以满足我的目的。
我觉得我可以从一个方法/类中获得最大的好处,我可以使用该方法/类来组织我的所有自定义控制器,在适当的时间加载并初始化它们,然后稍后再次访问它们(类似于该类中的接口和超类JFX-MultiScreen的视频)。
答案 0 :(得分:1)
我反复遇到了setControllerFactory方法。不幸的是,我找不到如何正确使用它的特定用途的很好的解释
默认情况下,FXMLLoader.load()
方法使用0-arg构造函数实例化fxml文档中命名的控制器。当您希望FXMLLoader对象以某种方式实例化控制器时,可以使用FXMLLoader.setControllerFactory
方法。在特定参数上使用其他控制器构造函数,在返回之前在控制器上调用方法,依此类推,如
FXMLLoader loader = new FXMLLoader(...);
loader.setControllerFactory(c -> {
return new MyController("foo", "bar");
});
现在,当您调用loader.load()
时,将如上所述创建控制器。但是,在预先存在的控制器上调用FXMLLoader.setController
方法可能会更容易。
我觉得我可以从一个方法/类中受益最大,可以用来组织所有自定义控制器,在适当的时间加载并初始化它们,然后稍后再次访问它们
当您第一次遇到这个问题时,我尝试并尝试了许多方法。我最终决定将主应用程序类转变为单例。当您需要创建一个在整个程序中都可以访问的类的实例时,单例模式非常有用。我知道会有很多人对此表示怀疑(因为它本质上是具有附加结构的全局变量),但是我发现它显着降低了复杂性,因为我不再需要管理某种程度上人为的对象引用结构顺其自然。
单例使控制器可以通过调用例如MyApp.getSingleton()与您的主应用程序类进行通信。仍在主应用程序类中,然后可以在私有HashMap中组织所有视图,并添加可以添加或删除视图的公共add(...),remove(...)和Activate(...)方法。从地图中激活地图或激活地图中的视图(即将场景的根设置为新视图)。
对于具有许多可能放在不同包中的视图的应用程序,您可以使用枚举来组织它们的位置:
public enum View {
LOGIN("login/Login.fxml"),
NEW_USER("register/NewUser.fxml"),
USER_HOME("user/UserHome.fxml"),
ADMIN_HOME("admin/AdminHome.fxml");
public final String location;
View(String location) {
this.location = "/views/" + location;
}
}
以下是主要应用程序类的示例:
public final class MyApp extends Application {
// Singleton
private static MyApp singleton;
public MyApp() { singleton = this; }
public static MyApp getSingleton() { return singleton; }
// Main window
private Stage stage;
private Map<View, Parent> parents = new HashMap<>();
@Override
public void start(Stage primaryStage) {
stage = primaryStage;
stage.setTitle("My App");
add(View.LOGIN);
stage.setScene(new Scene(parents.get(View.LOGIN)));
stage.show();
}
public void add(View view) {
var loader = new FXMLLoader(getClass().getResource(view.location));
try {
Parent root = loader.load();
parents.put(view, root);
} catch (IOException e) { /* Do something */ }
}
public void remove(View view) {
parents.remove(view);
}
public void activate(View view) {
stage.getScene().setRoot(parents.get(view));
}
public void removeAllAndActivate(View view) {
parents.clear();
add(view);
activate(view);
}
}
如果您拥有应用程序范围的资源,则可以将其放入应用程序类中,并添加getter / setter,以便您的控制器可以访问它们。这是一个示例控制器类:
public final class Login implements Initializable {
MyApp app = MyApp.getSingleton();
// Some @FXML variables here..
@FXML private void login() {
// Authenticate..
app.removeAllAndActivate(View.USER_HOME);
}
@FXML private void createAccount() {
app.add(View.NEW_USER);
app.activate(View.NEW_USER);
}
@Override
public void initialize(URL url, ResourceBundle rb) {}
}