JavaFX软件设计

时间:2015-09-23 12:09:08

标签: java javafx

在JavaFX应用程序中,必须对javafx.application.Application进行子类化,并且必须从此派生类中调用继承的launch()方法,尽管它是公共的,否则抛出异常。然后,launch()方法使用反射来实例化派生类,这使得在启动时很难为类成员设置值而不会丢失它们。所有这一切对我来说都是不寻常的,我想知道为什么启动JavaFX应用程序是如此复杂,如果那种软件设计(设计模式?)有一个名字,或者它是否只是糟糕的设计?

修改

更具体地说,我想使用观察者模式,因此我的java应用程序在加载文档时会收到通知,如下所示:

public class MyDocumentLoader extends Application
{
    private ChangeListener<Worker.State> changeListener;

    public void setChangeListener(ChangeListener<Worker.State> changeListener)
    {
        this.changeListener = changeListener;
    }

    ...

    public void loadDocument(String url)
    {
        webEngine.getLoadWorker().stateProperty().addListener(changeListener);
        webEngine.load(url);
    }

    ...

}

我需要几个方法中的回调成员,理想情况下我可以有多个加载文档的类实例,所以我可以为不同的URL设置不同的ChangeListener。

2 个答案:

答案 0 :(得分:7)

我的猜测是,这个设计是由(大量)错误编写的Swing应用程序推动的,其中“主要”JFrame被实例化并显示在错误的线程上(即不在AWT上)事件派遣线程)。我的猜测是,如此多的Swing应用程序被错误地编写,他们必须针对不正确的使用情况进行防御性编码,并且他们希望避免使用JavaFX这种情况。

强制(好吧,几乎强迫,有黑客攻击)FX应用程序以这种方式启动使得以类似的方式错误地编写应用程序变得更加困难。 launch方法(以及等效的Oracle JVM启动过程,如果你有一个没有Application方法的main子类并且调用launch)会做相当多的样板工作:它启动FX工具包,实例化Application子类并调用其init()方法,然后在FX应用程序线程上实例化主Stage并将其传递给Application子类的start(...)方法。这样就可以确保一切都在正确的线程上运行。

您应该基本上将JavaFX应用程序中的start(...)方法视为“传统”Java应用程序中main(...)方法的替代方法,并理解它是在FX应用程序线程上调用的。 / p>

我的建议是Application子类应该尽可能小;它应该委托给其他人来实际创建UI,然后将它放在主要阶段并显示它。包括main方法除了调用launch(...)之外什么都不做,作为非JavaFX感知JVM的后备。您应该只有一个Application子类的实例存在于任何JVM中。这样,您的Application子类就没有要设置的类成员,因此您所描述的问题不会出现。

如果使用FXML,这实际上相当自然:start(...)方法基本上只委托FXML控制器对来完成实际工作。如果您不使用FXML,请创建一个单独的类来执行实际布局等,并委托给它。请参阅this related question,它有同样的想法。

另请注意您的陈述

  

继承的launch()方法虽然是公共的,但必须被调用   来自这个派生类

并不完全准确,因为有一个overloaded form of the launch(...) method,您可以在其中指定应用程序子类。所以,如果你真的需要,你可以创建一个存根来启动FX工具包:

public class FXStarter extends Application {

    @Override
    public void start(Stage primaryStage) {
        // no-op
    }
}

现在你可以做到:

public class MyRegularApplication {

    public static void main(String[] args) {
        // start FX toolkit:
        new Thread(() -> Application.launch(FXStarter.class)).start();
        // other stuff here...
    }
}

请注意,在FX工具包关闭之前,launch不会返回,因此必须将此调用放在另一个线程中。这可能会造成竞争条件,在launch(...)实际初始化之前你可能会尝试做一些需要FX工具包的事情,所以你应该防范:

public class FXStarter extends Application {

    private static final CountDownLatch latch = new CountDownLatch(1);

    public static void awaitFXToolkit() throws InterruptedException {
       latch.await();
    }

    @Override
    public void init() {
        latch.countDown();
    }

    @Override
    public void start(Stage primaryStage) {
        // no-op
    }
}

然后

public class MyRegularApplication {

    public static void main(String[] args) throws InterruptedException {
        // start FX toolkit:
        new Thread(() -> Application.launch(FXStarter.class)).start();
        FXStarter.awaitFXToolkit();
        // other stuff here...
    }
}

SSCCE(我只使用内部类来处理所有内容,因此这样可以方便地运行,但在现实生活中,这些将是独立的类):

import java.util.Random;
import java.util.concurrent.CountDownLatch;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class BackgroundProcessDrivenApp {

    public static void main(String[] args) throws InterruptedException {
        Platform.setImplicitExit(false);
        new Thread(() -> Application.launch(FXStarter.class)).start();
        FXStarter.awaitFXToolkit();
        new MockProcessor().doStuff() ;
    }

    public static class FXStarter extends Application {

        private static final CountDownLatch latch = new CountDownLatch(1);

        @Override
        public void init() {
            latch.countDown();
        }

        public static void awaitFXToolkit() throws InterruptedException {
            latch.await();
        }

        @Override
        public void start(Stage primaryStage) { }
    }

    public static class MockProcessor {

        private final int numEvents = 10 ;

        public void doStuff() {
            Random rng = new Random();
            try {
                for (int event = 1 ; event <= numEvents; event++) {
                    // just sleep to mimic waiting for background service...
                    Thread.sleep(rng.nextInt(5000) + 5000);
                    String message = "Event " + event + " occurred" ;
                    Platform.runLater(() -> new Messager(message).showMessageInNewWindow());
                }
            } catch (InterruptedException exc) {
                Thread.currentThread().interrupt();
            } finally {
                Platform.setImplicitExit(true);
            }
        }
    }

    public static class Messager {
        private final String message ;

        public Messager(String message) {
            this.message = message ;
        }

        public void showMessageInNewWindow() {
            Stage stage = new Stage();
            Label label = new Label(message);
            Button button = new Button("OK");
            button.setOnAction(e -> stage.hide());
            VBox root = new VBox(10, label, button);
            root.setAlignment(Pos.CENTER);
            Scene scene = new Scene(root, 350, 120);
            stage.setScene(scene);
            stage.setAlwaysOnTop(true);
            stage.show();
        }
    }
}

答案 1 :(得分:2)

JavaFX支持大量的部署和打包策略,参考。 https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/toc.html,并且具有标准化的生命周期进入和退出点简化了对所有这些策略的支持。

如果您正在努力初始化主应用程序类,由于它是由JavaFX启动程序实现的,您最好的选择是使用Application.init()和Application.stop()方法,正如James_D指出的那样。 / p>