escritorio aplicacion java design javafx

aplicacion - Diseño de software JavaFX



java y javafx (2)

JavaFX admite una gran cantidad de estrategias de implementación y empaquetado, ref. https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/toc.html , y tener un punto de entrada y salida del ciclo de vida estandarizado simplifica el soporte de todas estas estrategias.

Si tiene dificultades para inicializar su clase de aplicación principal, debido a que el iniciador JavaFX la instancia, su mejor opción es utilizar los métodos Application.init () y Application.stop (), como señala James_D.

En una aplicación JavaFX, javafx.application.Application debe subclasificarse, y el método launch () heredado, aunque es público, debe llamarse desde esta clase derivada; de lo contrario, se genera una excepción. El método launch () luego usa la reflexión para crear instancias de la clase derivada, lo que dificulta establecer valores para los miembros de la clase sin perderlos al iniciar. Todo eso me parece totalmente inusual, y me preguntaba por qué iniciar una aplicación JavaFX es tan complicado, si ese tipo de diseño de software (¿patrón de diseño?) Tiene un nombre, o si es simplemente un mal diseño?

EDITAR:

Para ser más específico, quiero usar el patrón de observador, por lo que mi aplicación Java recibe una notificación cuando se carga un documento, como este:

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); } ... }

Necesito el miembro de devolución de llamada en varios métodos, e idealmente puedo tener más de una instancia de la clase que carga documentos, por lo que puedo configurar diferentes ChangeListeners para diferentes URL.


Mi conjetura es que este diseño fue motivado por el (vasto) número de aplicaciones Swing que se escribieron incorrectamente, con el JFrame "primario" siendo instanciado y mostrado en el hilo incorrecto (es decir, no en el hilo de despacho de eventos AWT). Supongo que tantas aplicaciones Swing se escribieron incorrectamente que tuvieron que codificar defensivamente el marco contra el uso incorrecto, y que querían evitar este escenario con JavaFX.

Forzar (bueno, casi forzar, hay trucos) una Aplicación FX para comenzar de esta manera hace que sea mucho más difícil escribir una aplicación de manera incorrecta de manera similar. El método de launch (y el proceso de inicio de Oracle JVM equivalente si tiene una subclase de Application sin un método main y una llamada para launch ) hace bastante trabajo repetitivo: inicia el kit de herramientas FX, crea una instancia de la subclase de Application y llama a su init() , luego en el subproceso de aplicación FX crea una instancia de la Stage primaria y la pasa al método de start(...) la subclase de Application . Esto garantiza que todo se ejecute en el hilo correcto.

Básicamente, debe considerar el método de start(...) en una aplicación JavaFX como el reemplazo del método main(...) en una aplicación Java "tradicional", con el entendimiento de que se invoca en el subproceso de aplicación FX.

Mi recomendación es que la subclase de Application sea ​​lo más mínima posible; simplemente debe delegar a otra cosa para crear realmente la interfaz de usuario, y luego debe colocarla en la etapa primaria y mostrarla. Incluya un método main que no haga nada más que launch(...) llamadas launch(...) como respaldo para JVM no compatibles con JavaFX. Solo debe tener una instancia de una subclase de Application presente en cualquier JVM. De esta manera, su subclase de Application no tiene miembros de clase para configurar, por lo que los problemas que describe simplemente no surgen.

Si usa FXML, esto es bastante natural: el método de start(...) esencialmente solo delega al par FXML-controlador para hacer el trabajo real. Si no usa FXML, cree una clase separada para hacer el diseño real, etc., y delegue en ella. Vea esta pregunta relacionada que tiene el mismo tipo de idea.

Tenga en cuenta también que su declaración

el método launch () heredado, aunque es público, debe llamarse desde esta clase derivada

no es del todo exacto, ya que existe una forma sobrecargada del método de launch(...) en la que puede especificar la subclase de la aplicación. Entonces, si realmente lo necesita, puede crear un código auxiliar para iniciar el kit de herramientas FX:

public class FXStarter extends Application { @Override public void start(Stage primaryStage) { // no-op } }

Ahora puedes hacer:

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

Tenga en cuenta que el launch no regresa hasta que el kit de herramientas FX se apaga, por lo que es imprescindible colocar esta llamada en otro hilo. Potencialmente, esto crea condiciones de carrera, donde puede intentar hacer algo que necesite el kit de herramientas FX antes de que el launch(...) haya inicializado, por lo que probablemente debería protegerse contra eso:

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 } }

y entonces

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 (Acabo de usar clases internas para todo, por lo que es conveniente ejecutarlo, pero en la vida real serían clases independientes):

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(); } } }