0

有没有办法解决典型的样板代码来实例化和加载 FXML?

一个典型的版本:

public void start(Stage primaryStage) 
{   
    URL fxmlUrl = this.getClass().getResource( /* your string path*/ );

    FXMLLoader loader = new FXMLLoader(fxmlUrl);

    try 
    {   
        primaryStage = loader.load();
        this.controller = loader.getController();
    } 
    catch(IOException ioe)
    {
        ioe.printStackTrace();
    }

    primaryStage.show();
}

请注意上面的代码,支持 FXML 文件具有<Stage></Stage>root 身份。这只是我的偏好。加载和匹配到 FXML 根可以相应地调整。在任何情况下,加载 FXML 文件和获取对控制器的引用是关键部分(在大多数情况下)。

我试图创建一种实用方法,如下所示,以帮助防止出现此样板代码:

<R, C> void loadFXML(R root, C controller, String path)
{
    URL fxmlUrl = this.getClass().getResource( path );

    FXMLLoader loader = new FXMLLoader(fxmlUrl);

    try 
    {   
        root = loader.load();
        controller = loader.getController();
    } 
    catch(IOException ioe)
    {
        ioe.printStackTrace();
    }
}

但是,在某些阶段出现空指针错误,或者加载不正确。我相信这与泛型和/或铸造有关。

如何改进此代码?或者也许可以建议另一种类型代码来解决这个问题?

4

1 回答 1

0

问题是(可能;您没有显示空指针异常的来源)您仅设置局部变量rootand controller,然后让它们超出范围。

请记住,Java 以按值传递的方式处理方法参数:因此该方法会创建rootcontroller引用的本地副本。当您的方法调用时

root = loader.load();
controller = loader.getController();

它只是更新本地引用,而不是传递给方法的引用。所以如果你这样做

Parent root = null ;
MyController controller = null ;
loadFXML(root, controller, "Main.fxml");
System.out.println(root);
System.out.println(controller);

你会看到价值观null

一种解决方法是传入对象的可变包装器,并更新这些包装器。ObjectProperty为此工作:

<R, C> void loadFXML(String path, ObjectProperty<R> root, ObjectProperty<C> controller)
{
    URL fxmlUrl = this.getClass().getResource( path );

    FXMLLoader loader = new FXMLLoader(fxmlUrl);

    try 
    {   
        root.set(loader.load());
        controller.set(loader.getController());
    } 
    catch(IOException ioe)
    {
        ioe.printStackTrace();
    }

}

另一种选择是创建一个封装这两个值的类并返回它的一个实例:

public class ViewController<V,C> {
    private final V view ;
    private final C controller ;
    public ViewController(V view, C controller) {
        this.view = view ;
        this.controller = controller ;
    }
    public V getView() {
        return view ;
    }
    public C getController() {
        return controller ;
    }
}

接着

<R, C> ViewController<R, C> loadFXML(String path)
{
    URL fxmlUrl = this.getClass().getResource( path );

    FXMLLoader loader = new FXMLLoader(fxmlUrl);

    try 
    {   
        R root = loader.load();
        C controller = loader.getController();
        return new ViewController<R,C>(root, controller);
    } 
    catch(IOException ioe)
    {
        ioe.printStackTrace();
        throw new UncheckedIOException(ioe); // this is likely fatal, so just throw an unchecked exception
    }
}

但是,您是否在冗长方面得到任何节省是有争议的:

ViewController<Parent, MyController> viewController = loadFXML("Main.fxml");
Parent root = viewController.getView();
MyController controller = viewController.getController();

最后的观察是,在几乎所有用例中,您实际上只是在之后才对控制器进行任何重要的工作。您通常只需将视图设置为场景的根,或将其添加到另一个布局窗格。Consumer<R>您可以通过让方法接受 a并返回控制器来利用这一点(在 Java 8 中) :

public <R, C> C loadFXML(Consumer<R> rootHandler, String path) {
    URL fxmlUrl = this.getClass().getResource( path );

    FXMLLoader loader = new FXMLLoader(fxmlUrl);

    try 
    {   
        R root = loader.load();
        C controller = loader.getController();
        rootHandler.accept(root);
        return controller ;
    } 
    catch(IOException ioe)
    {
        ioe.printStackTrace();
        throw new UncheckedIOException(ioe); // this is likely fatal, so just throw an unchecked exception
    }
}

你会使用类似的东西来调用它

Scene scene = new Scene();
MyController controller = loadFXML(scene::setRoot, "Main.fxml");
于 2014-09-06T02:55:12.363 回答