Estoy tratando de aprender de JavaFX y convertir un swing de aplicación JavaFX.
Lo que quiero hacer es usar JavaFX para mostrar el progreso de un programa.

Lo que yo estaba haciendo en el Swing fue de los primeros en crear un JFrame con un custom JComponent. Mi programa principal llama a un método de la costumbre JComponent que iba a cambiar el color de una forma dentro de la JComponent y repaint().

El siguiente da una idea de la clase de cosas que quiero lograr en JavaFX:

//Run JavaFX in a new thread and continue with the main program.
public class Test_Main{
    public static void main(String[] args) {
        Test test = new Test();
        Thread t = new Thread(test);
        t.start();

        //Main Program
        JOptionPane.showMessageDialog(null, "Click 'OK' to continue.",
                "Pausing", JOptionPane.INFORMATION_MESSAGE);

        //Update Progress
        test.setText("Hello World!");
    }    
}

Actualmente tengo esto como mi ejecutables.

public class Test extends Application implements Runnable{
    Button btn;

    @Override
    public void run() {
        launch();
    }

    @Override
    public void start(Stage stage) throws Exception {
        StackPane stack = new StackPane();
        btn = new Button();
        btn.setText("Testing");
        stack.getChildren().add(btn);
        Scene scene = new Scene(stack, 300, 250);
        stage.setTitle("Welcome to JavaFX!");
        stage.setScene(scene);
        stage.show();        
    }    

    public void setText(String newText){
        btn.setText(newText);
    }
}

Todo funciona bien hasta que trate de actualizar el texto del botón en el que tengo un NullPointerException. Supongo que esto tiene algo que ver con la aplicación JavaFX hilo. No puedo encontrar nada en la red a través de los cuales se describe cómo actualizar las cosas externamente.

Veo un montón de mencionar acerca de Platform.runLater y Task pero estos son por lo general anidado en el método de inicio y de ejecución en los temporizadores.

ACTUALIZACIÓN:
Solo para aclarar espero lograr algo como esto:

public class Test_Main{
    public static void main(String[] args) {
        final boolean displayProgress = Boolean.parseBoolean(args[0]);

        Test test = null;
        if(displayProgress){    //only create JavaFX application if necessary
            test = new Test();
            Thread t = new Thread(test);
            t.start();
        }

        //main program starts here

        //...

        //main program occasionally updates JavaFX display
        if(displayProgress){    //only update JavaFX if created
            test.setText("Hello World!");
        }

        //...

        //main program ends here
    }    
}
  • Usted puede encontrar las cosas más fáciles si intenta aprender de JavaFX como una pizarra en blanco en lugar de tratar de convertir un Swing de aplicación JavaFX (que en mi opinión es una cosa bastante difícil de hacer para una aplicación no trivial).
  • La solución es tan simple, que me he perdido algo básico? Me hizo empezar de cero. Pero nada parece describir esta situación en particular (tal vez eso es un indicio de que no es posible!?).
  • Parece que tu pregunta real es sólo «Es posible separar la lógica de la funcionalidad de la interfaz de usuario?». La respuesta es sí: basta con crear una clase con los datos y la referencia de la interfaz de usuario si usted está mostrando una vista de ella. He actualizado mi respuesta para mostrar esto, yo voy a dejar el resto de la respuesta para el beneficio de aquellos que podrían estar buscando tu pregunta como que en realidad dijo.
  • Mi pregunta era más «¿Cómo puedo ‘add-on’/adjuntar un JavaFX mostrar a la parcela en vivo el progreso de un programa/algoritmo sin cambiar drásticamente la estructura del programa?» En lugar de eso parece que JavaFX tiene que ser la parte principal del programa y la lógica de ejecutar el método start (). Por ejemplo, supongamos que deseamos, opcionalmente, la trama de la serie de tiempo, mientras que se calcula. El principal programa calcula la serie usando un bucle for en el tiempo. No necesita ninguna interfaz de usuario para funcionar y puede sólo la salida a un archivo de datos. De todos modos, voy a aceptar su respuesta. Ciertamente explicó algunas cosas!
  • Creo que el último ejemplo en mi respuesta hace eso, ¿no? Los datos es completamente independiente de si hay o no hay una interfaz de usuario de la observación y la muestre.
  • Tipo, pero en TestApp la ‘principal’ programa de código se ejecuta desde un Application. Pero en HeadlessApp se ejecuta desde main. La conmutación entre las dos es más que una especie de torpe en comparación con la estructura que tengo con mi Swing versión (similar a mi versión actualizada del código anterior). Sin embargo, creo que voy a tener que aceptarlo. Estoy empezando a ver cómo puedo adaptar sus soluciones (en particular su productionApp/lanzador solución a mi problema. Muchas gracias por toda su ayuda.

InformationsquelleAutor sharp-spark | 2014-10-13

3 Comentarios

  1. 5

    La NullPointerException no tiene nada que ver con el roscado (aunque también tiene roscado errores en el código).

    Application.launch() es un método estático. Se crea una instancia de la Application subclase, se inicializa el Java FX sistema, se inicia el FX Subproceso de Aplicación, y se invoca start(...) en la instancia que se creó, la ejecución en el FX Subproceso de Aplicación.

    Por lo que la instancia de Test en el que start(...) se invoca es una instancia diferente a la que usted ha creado en su main(...) método. De ahí el btn campo en la instancia creada en el Test_Main.main() nunca se inicializa.

    Si agrega un constructor que hace algunos simple registro:

    public Test() {
        Logger.getLogger("Test").log(Level.INFO, "Created Test instance");
    }

    usted verá que las dos instancias se crean.

    La API simplemente no está diseñado para ser utilizado de esta manera. Usted debe considerar start(...) esencialmente como un reemplazo para la main método cuando se utiliza JavaFX. (De hecho, en Java 8, se puede omitir el main método completo de su Application subclase y aún de lanzamiento de la clase desde la línea de comandos.) Si usted desea que una clase sea reutilizable, no hacen que sea una subclase de Application; hacer una subclase de algún contenedor de tipo nodo, o (mejor en mi opinión) le dan un método que tiene acceso a un nodo.

    Hay problemas de subprocesamiento en el código también, aunque estos no están causando la excepción de puntero nulo. Los nodos que forman parte de un escenario gráfico sólo se puede acceder desde la Aplicación JavaFX Hilo. Existe una norma similar en el Swing: los componentes swing sólo se puede acceder desde el AWT de manejo de eventos hilo, por lo que realmente debe llamar a JOptionPane.showMessageDialog(...) en ese hilo. En JavaFX, puede utilizar Platform.runLater(...) para programar una Runnable para ejecutarse en el FX Subproceso de Aplicación. En Swing, puede utilizar SwingUtilities.invokeLater(...) para programar una Runnable para ejecutarse en el AWT envío de evento hilo.

    La mezcla de Swing y JavaFX es un bonito tema avanzado, porque necesariamente tienen que comunicarse entre los dos hilos. Si usted está buscando para iniciar un diálogo como un control externo para un JavaFX etapa, es probablemente mejor para hacer del diálogo un JavaFX ventana también.

    Actualizado:

    Siguiente discusión en los comentarios, estoy suponiendo que el JOptionPane es sólo un mecanismo para proporcionar un retraso: voy a modificar su ejemplo aquí tan sólo espera cinco segundos antes de cambiar el texto del botón.

    La línea de fondo es que cualquier código que desee volver a utilizar de diferentes maneras no debe ser en un Application subclase. Crear un Application subclase únicamente como un mecanismo de inicio de. (En otras palabras, Application subclases en realidad no son reutilizables; poner todo excepto el proceso de inicio en otro lugar.) Desde que potencialmente desea utilizar la clase que usted llama Test en más de una forma, se debe colocar en un POJO (plain old Java object) y crear un método que da acceso a la parte de interfaz de usuario se define (y ganchos a toda lógica; aunque en una aplicación real, usted probablemente querrá la lógica de producirse en una clase diferente):

    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    import javafx.scene.Parent;
    import javafx.scene.control.Button;
    import javafx.scene.layout.Pane;
    import javafx.scene.layout.StackPane;
    
    public class Test {
    
        private Button btn;
        private Pane view ;
    
        public Test(String text) {
            Logger.getLogger("Test").log(Level.INFO, "Created Test instance");
    
            view = new StackPane();
            btn = new Button();
            btn.setText(text);
            view.getChildren().add(btn);
    
        }   
    
        public Parent getView() {
            return view ;
        }
    
        public void setText(String newText){
            btn.setText(newText);
        }
    }

    Ahora supongamos que usted desea ejecutar esto de dos maneras. Para ilustrarlo, vamos a tener un TestApp que se inicia el botón con el texto «Prueba», luego de cinco segundos más tarde, se transforma en «Hola Mundo!»:

    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    public class TestApp extends Application {
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {
    
            //launch app:
    
            Test test = new Test("Testing");
            primaryStage.setScene(new Scene(test.getView(), 300, 250));
            primaryStage.show();
    
            //update text in 5 seconds:
    
            Thread thread = new Thread(() -> {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException exc) {
                    throw new Error("Unexpected interruption", exc);
                }
                Platform.runLater(() -> test.setText("Hello World!"));
            });
            thread.setDaemon(true);
            thread.start();
    
        }    
    }

    Ahora un ProductionApp que acaba de lanza de inmediato con el texto inicializado directamente a «Hola Mundo!»:

    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    
    public class ProductionApp extends Application {
        @Override
        public void start(Stage primaryStage) {
            Test test = new Test("Hello World!");
            primaryStage.setScene(new Scene(test.getView(), 300, 250));
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }

    Nota que hay una sobrecarga del formulario de Application.launch(...) que toma el Application subclase como un parámetro. Así que usted podría tener un método main en algún lugar que tomó una decisión en cuanto a que Application se va a ejecutar:

    import javafx.application.Application;
    
    public class Launcher {
    
        public static void main(String[] args) {
            if (args.length == 1 && args[0].equalsIgnoreCase("test")) {
                Application.launch(TestApp.class, args) ;
            } else {
                Application.launch(ProductionApp.class, args);
            }
        }
    }

    Tenga en cuenta que sólo puede llamar a los launch(...) una vez por invocación de la JVM, lo que significa que es una buena práctica sólo para llamar nunca de un main método.

    Continuar en el «divide y vencerás», el tema, si desea que la opción para ejecutar la aplicación «headlessly» (es decir, sin interfaz de usuario en todos), entonces debes tomar en cuenta los datos que está siendo manipulado desde la interfaz de usuario código. En cualquier tamaño de la aplicación, esta es una buena práctica, de todos modos. Si usted tiene la intención de utilizar los datos en una aplicación JavaFX, es muy útil el uso de JavaFX propiedades para que la represente.

    En este juguete ejemplo, el único dato es una Cadena, por lo que el modelo de datos se ve bastante simple:

    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    
    public class DataModel {
        private final StringProperty text = new SimpleStringProperty(this, "text", "");
    
        public final StringProperty textProperty() {
            return this.text;
        }
    
        public final java.lang.String getText() {
            return this.textProperty().get();
        }
    
        public final void setText(final java.lang.String text) {
            this.textProperty().set(text);
        }
    
        public DataModel(String text) {
            setText(text);
        }
    }

    La modificación de Test clase que encapsula la reutilizables de código de interfaz de usuario se parece a:

    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    import javafx.scene.Parent;
    import javafx.scene.control.Button;
    import javafx.scene.layout.Pane;
    import javafx.scene.layout.StackPane;
    
    public class Test {
    
        private Pane view ;
    
        public Test(DataModel data) {
            Logger.getLogger("Test").log(Level.INFO, "Created Test instance");
    
            view = new StackPane();
            Button btn = new Button();
            btn.textProperty().bind(data.textProperty());
            view.getChildren().add(btn);
    
        }   
    
        public Parent getView() {
            return view ;
        }
    }

    La interfaz de usuario-baed aplicación parece:

    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    public class TestApp extends Application {
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {
    
            //launch app:
            DataModel data = new DataModel("Testing");
            Test test = new Test(data);
            primaryStage.setScene(new Scene(test.getView(), 300, 250));
            primaryStage.show();
    
            //update text in 5 seconds:
    
            Thread thread = new Thread(() -> {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException exc) {
                    throw new Error("Unexpected interruption", exc);
                }
    
                //Update text on FX Application Thread:
                Platform.runLater(() -> data.setText("Hello World!"));
            });
            thread.setDaemon(true);
            thread.start();
    
        }    
    }

    y una aplicación que acaba de manipulación de los datos sin ver adjunto se ve como:

    public class HeadlessApp {
    
        public static void main(String[] args) {
            DataModel data = new DataModel("Testing");
            data.textProperty().addListener((obs, oldValue, newValue) -> 
                System.out.printf("Text changed from %s to %s %n", oldValue, newValue));
            Thread thread = new Thread(() -> {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException exc) {
                    throw new Error("Unexpected Interruption", exc);
                }
                data.setText("Hello World!");
            });
            thread.start();
        }
    
    }
    • En primer lugar, gracias por su respuesta! El JOptionPane no ser parte de la final del programa, que era sólo un marcador de posición que podría bloquear una actualización inmediata para el botón, así que el cambio podría ser mejor observados. Además tenía la esperanza de que yo no tendría que encapsular el programa principal dentro de una aplicación JavaFX como deseo opcionalmente activar ‘la barra de progreso o bajar en el inicio del programa. Por lo tanto potencialmente no uso de JavaFX en todo.
    • Definir toda la lógica en algo que no es una subclase de Application. A continuación, definir dos clases de aplicación, con y sin la «barra de progreso». Nota: también hay una versión de la launch método que toma la clase de la aplicación, como primer argumento, por lo que podría tener un main(...) método que condicionalmente se ejecutó uno o el otro.
    • ¿Cuál sería el objetivo de la aplicación sin la ‘barra de progreso si no se muestra nada? Simplemente ejecutar la lógica del programa principal? No es posible ejecutar la lógica del programa sin una aplicación ficticia? Gracias.
    • Yo estaba respondiendo a usted diciendo que quería «opcionalmente activar o desactivar la barra de progreso». Permítanme actualización de la respuesta a ver si se aclara…
    • Por favor, ver mi edición. A partir de tu respuesta, entiendo que nada de lo que he sugerido en mi edición es imposible. Y que el código se debe ejecutar desde el inicio() para acceder a la instancia correcta de button que se muestra por JavaFX. La correcta?
    • Está usted diciendo que usted no desee mostrar una interfaz de usuario en todos los? (Esto no es claro en el titulo de tu pregunta…).
    • Correcto. Pedimos disculpas por cualquier confusión. Traté de mantener la pregunta tan conciso como sea posible.

  2. 1

    Este código hace lo que yo creo que usted está buscando para hacer:

    package javafxtest;
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.embed.swing.JFXPanel;
    import javafx.event.ActionEvent;
    import javafx.event.EventHandler;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.layout.StackPane;
    import javafx.stage.Stage;
    /**
    * @author ericjbruno
    */
    public class ShowJFXWindow {
    {
    //Clever way to init JavaFX once
    JFXPanel fxPanel = new JFXPanel();
    }
    public static void main(String[] args) {
    ShowJFXWindow dfx = new ShowJFXWindow();
    dfx.showWindow();
    }
    public void showWindow() {
    //JavaFX stuff needs to be done on JavaFX thread
    Platform.runLater(new Runnable() {
    @Override
    public void run() {
    openJFXWindow();
    }
    });
    }
    public void openJFXWindow() {
    Button btn = new Button();
    btn.setText("Say 'Hello World'");
    btn.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent event) {
    System.out.println("Hello World!");
    }
    });
    StackPane root = new StackPane();
    root.getChildren().add(btn);
    Scene scene = new Scene(root, 300, 250);
    Stage stage = new Stage();
    stage.setTitle("Hello World!");
    stage.setScene(scene);
    stage.show();
    }
    }
    • Gracias por tu respuesta. No se trata exactamente de lo que quiero. Lo que quiero hacer es actualizar dinámicamente JavaFX componentes de main(). Puedo acercarme haciendo Button btn un miembro de la clase, poniendo setText(.) y setOnAction(.) a un nuevo método de setBtnText(String newText), a continuación, llamar a dfx.setBtnText(.) de main(). Esto a la larga (se toma un tiempo; no hay una repaint() equivalente en JavaFX?) las actualizaciones de la etiqueta. El controlador de eventos no se ve afectada, sin embargo, y puedo obtener excepción: java.lang.IllegalStateException: Not on FX application thread.
    • Como usted dice «JavaFX cosas deben hacerse en JavaFX el hilo». Pero no quiero encapsular todos mis no-GUI código en un JavaFX hilo, quiero tener la opción de ejecutar mi programa sin invocar ninguna de JavaFX en todo. En su ejemplo, yo podría hacer esto por encapsular el código actualmente en main() con if(display){.}.
  3. 0

    Intentar llamar desde dentro del subproceso de interfaz de usuario así como:

    public void setText(final String newText) {
    Platform.runLater(new Runnable() {
    @Override
    public void run() {
    btn.setText(newText);
    }
    });
    }

    En cualquier momento que desee realizar cambios en un elemento de la interfaz de usuario, debe hacerse desde dentro del subproceso de interfaz de usuario. Platform.runLater(new Runnable()); va a hacer precisamente eso. Esto evita el bloqueo, y otras extrañas oscuras de la interfaz de usuario relacionados con los errores y las excepciones que se produzcan.

    Sugerencia: lo que usted ha leído/visto con el Platform.runLater que se llama en el inicio de la aplicación, y en los temporizadores generalmente es una forma de cargar la mayoría de la interfaz de usuario inmediatamente, a continuación, rellene las otras partes después de un segundo o dos (el temporizador) para que no cuadra en el inicio. Pero Platform.runLater no es sólo para el inicio, es para cualquier momento que usted necesita para cambiar/uso/interactuar con un elemento de interfaz de usuario.

    • Eso no es realmente la causa de la NullPointerException aunque.
    • sí, su respuesta es mucho más completa y correcta. Voy a +1 tuyo
    • Yo iba a decir que me dan un NullPointerException cuando trato de implementar esto.

Dejar respuesta

Please enter your comment!
Please enter your name here