FXML锅炉板代码

时间:2014-09-06 02:12:00

标签: javafx fxml fxmlloader

有没有办法解决典型的锅炉板代码来实例化和加载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>为根。这只是我的偏好。加载和匹配到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();
    }
}

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

这段代码怎么能被改进?或者perhap可以建议另一种类型的代码来解决这个问题吗?

1 个答案:

答案 0 :(得分:0)

问题是(可能;你没有显示你的Null Pointer Exceptions来自哪里),你只需设置局部变量rootcontroller,然后让它们出去范围。

请记住,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>并返回控制器来利用此功能(在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");