Problemas de compatibilidad con javafx 8: campos o métodos estáticos de FXML

Resuelto Christopher_Daniel asked hace 10 años • 1 respuestas

He diseñado una aplicación javafx que funciona bien en jdk 7. Cuando intento ejecutarla en java 8, obtengo las siguientes excepciones:

javafx.fxml.LoadException: 
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2617)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2595)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3230)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3191)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3164)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3140)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3132)


Exception in thread "JavaFX Application Thread" java.lang.NullPointerException: Root cannot be null
    at javafx.scene.Scene.<init>(Scene.java:364)
    at javafx.scene.Scene.<init>(Scene.java:232)
        at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:204)
    at javafx.concurrent.EventHelper.fireEvent(EventHelper.java:219)
    at javafx.concurrent.Task.fireEvent(Task.java:1357)
    at javafx.concurrent.Task.setState(Task.java:720)
    at javafx.concurrent.Task$TaskCallable$2.run(Task.java:1438)
    at com.sun.javafx.application.PlatformImpl$6$1.run(PlatformImpl.java:301)
    at com.sun.javafx.application.PlatformImpl$6$1.run(PlatformImpl.java:298)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl$6.run(PlatformImpl.java:298)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.access$300(WinApplication.java:39)
    at com.sun.glass.ui.win.WinApplication$4$1.run(WinApplication.java:112)
    at java.lang.Thread.run(Thread.java:744)

Descubrí que la razón de esto es que en el método de inicialización de la clase de controlador no puedo usar los métodos incorporados en ningún componente estático. (Por ejemplo: staticMyTextField.setText() está causando el problema en Java 8 pero no en Java 7). No puedo encontrar nada documentado sobre esto en las guías de javafx. ¿Alguien puede darnos algunas ideas sobre por qué esto está causando un problema en Java 8? Y también compartir documentos relacionados con esto, si los hubiera.

Christopher_Daniel avatar Apr 16 '14 16:04 Christopher_Daniel
Aceptado

Parece que estás intentando inyectar a TextFielden un campo estático. Algo como

@FXML
private static TextField myTextField ;

Aparentemente esto funcionó en JavaFX 2.2. No funciona en JavaFX 8. Dado que ninguna documentación oficial admitió este uso, en realidad no viola la compatibilidad con versiones anteriores, aunque, para ser justos, la documentación sobre lo que hace exactamente FXMLLoaderes bastante lamentable.

Realmente no tiene mucho sentido hacer que @FXMLlos campos inyectados sean estáticos. Cuando carga un archivo FXML, crea nuevos objetos para cada uno de los elementos del archivo FXML. Se asocia una nueva instancia de controlador con cada llamada FXMLLoader.load(...)y los campos en esa instancia de controlador se inyectan con los objetos correspondientes creados para los elementos FXML. Por tanto, los campos inyectados son necesariamente específicos de la instancia del controlador. Si tuviera campos inyectados estáticos en el controlador, cargara el mismo archivo FXML dos veces y lo mostrara dos veces en la interfaz de usuario, entonces no tendría forma de hacer referencia a ambos conjuntos de controles.


@FXMLLos métodos estáticos marcados a los que se hace referencia desde FXML tampoco funcionarán.

Por ejemplo, con:

@FXML
private static void changeUser(ActionEvent e) {
    // System.out.println("test");
}

Y:

<Button text="Change User" onAction="#changeUser"/>

Esto fallará con:

javafx.fxml.LoadException: Error resolving onAction='#changeUser', either the event handler is not in the Namespace or there is an error in the script.

Para solucionar este problema, simplemente elimine el modificador estático de la especificación del método.


Actualización : respuesta a la pregunta en los comentarios.

En particular, no utilice campos estáticos sólo para permitir que sean accesibles desde fuera de la clase. Un campo estático tiene un valor único que pertenece a la clase, en lugar de un valor para cada instancia de la clase, y la decisión de hacer que los campos sean estáticos sólo debe tomarse si eso tiene sentido. En otras palabras, staticdefine alcance , no accesibilidad . Para permitir el acceso a los datos de la instancia, sólo hay que tener una referencia a la instancia. Tiene FXMLLoaderun getController()método que le permite recuperar una referencia al controlador.

Un punto relacionado: tampoco es una buena idea exponer los controles de la interfaz de usuario desde el controlador. En su lugar, debería exponer los datos. Por ejemplo, en lugar de definir un getTextField()método en el controlador, defina un textProperty()método que devuelva un valor que StringPropertyrepresente el contenido del archivo TextField. La razón de esto es que cuando su jefe viene a la oficina y le dice que quiere que sea TextFieldreemplazado por un TextArea, o un ComboBox<String>, o algún otro control, entonces será mucho más difícil si clases fuera del controlador están usando su TextField. Es mucho menos probable que cambie la estructura de los datos representados por su controlador que la implementación de cómo se presentan esos datos al usuario.

Para algunos ejemplos

  • Aplicando MVC con JavaFx
  • Pasando parámetros JavaFX FXML
  • Gestión de datos JavaFX
  • Acceder a campos desde otro Controlador en JavaFX
  • https://github.com/james-d/LeastCommonMultipleCalculator
  • https://github.com/james-d/Nested-Controller-Example
  • https://github.com/james-d/Dialog-FXML-Example
  • https://github.com/james-d/Shared-Data-Controller
James_D avatar Apr 16 '2014 12:04 James_D