Javafx 8 Compatibility Issues - Fxml Static Fields

javafx 8 compatibility issues - FXML static fields

It sounds like you are trying to inject a TextField into a static field. Something like

@FXML
private static TextField myTextField ;

This apparently worked in JavaFX 2.2. It doesn't work in JavaFX 8. Since no official documentation ever supported this use, it's doesn't really violate backward compatibility, though in fairness the documentation on exactly what the FXMLLoader does is pretty woeful.

It doesn't really make much sense to make @FXML-injected fields static. When you load an FXML file, it creates new objects for each of the elements in the FXML file. A new controller instance is associated with each call to FXMLLoader.load(...) and the fields in that controller instance are injected with the corresponding objects created for the FXML elements. So the injected fields are necessarily specific to the controller instance. If you had a static injected fields in the controller, and you loaded the same FXML file twice and displayed it twice in the UI, then you would have no way of referencing both sets of controls.

Update: Response to question in comments

In particular, don't use static fields just to enable them to be accessible from outside the class. A static field has a single value belonging to the class, instead of a value for each instance of the class, and the decision to make fields static should only be made if that makes sense. In other words, static defines scope, not accessibility. To allow access to instance data, you just have to have a reference to the instance. The FXMLLoader has a getController() method that allows you to retrieve a reference to the controller.

A related point: it's also not a good idea to expose the UI controls from the controller. You should instead expose the data. For example, instead of defining a getTextField() method in the controller, instead define a textProperty() method that returns a StringProperty representing the contents of the TextField. The reason for this is that when your boss comes to the office and tells you he wants the TextField to be replaced by a TextArea, or a ComboBox<String>, or some other control, then it's going to be a lot harder if classes outside of the controller are using your TextField. The structure of the data represented by your controller is much less likely to change than the implementation of how that data is presented to the user.

For some examples

  • Passing Parameters JavaFX FXML
  • JavaFX Data Mangement
  • Access fields from another Controller in 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

(JavaFx) Proper way represent static FXML fields (and why this is a bad idea)

This question is probably too opinion-based for this forum, but I think the opinions I'm expressing here are held widely enough for this to be acceptable.

Why are static fields a bad idea

Suppose you did what you are trying to do here, and it was allowed to work. Imagine your boss comes into your office in 6 months, and says something like "The customers love the application, but they want to be able to open multiple node editors at the same time. Can you make the application so they can open new windows, each with a node editor in?"

This kind of thing happens in real life all the time.

So in theory this should be easy. You have the FXML that defines the layout, and a controller than defines the logic associated with it. So all you need to do is have an event handler (say on a menu) that creates a new Stage and a Scene, loads the FXML again, and sets the root of the scene to the root of the FXML.

Except that it doesn't work. The problem is, you make the root pane static. So when you loaded the FMXL again, it replaced the current value of canvas with the new one from the newly-loaded FXML. If you now try to add new elements to the first canvas, you can't.

So now you have to refactor the code, so that canvas is an instance variable. Worse still, you publicly exposed it via the static getCanvas() method, which might have been called anywhere in your application. So you have to track down all those invocations. And you end up at 3am trying to grep your entire codebase to figure out what you have to change.

The problem (one problem) with making things static is that you commit to only ever having one copy of that variable, and essentially you commit to that for all time (or commit to rewriting much of the application if you change your mind). That decision leaks upwards: by only ever having one canvas, you can only ever have one controller, so you can only ever have one node editor. You have essentially prevented the code you have written from being reusable.


Your underlying assumption here is wrong. The static keyword really has nothing to do with accessibility: it is about scope. A static field is scoped to the class: a non-static field is scoped to each instance of that class. You made the field accessible by defining a public getCanvas() method and making the scope of that method - the class - available (by making the class public). If canvas is an instance variable, you can make it accessible by similarly defining a public getCanvas() method and making the scope of that method -- now the CanvasController instance -- available. So you load the FXML, retrieve the controller by calling getController() on the FXMLLoader, and pass the controller instance to whoever needs access to it. Notice now you have control in the code over who has received a reference to the controller, and if you need any refactoring you at least have a path to follow to find where the controller is used.

You mention "JavaFX doesn't have a NodeProperty or an AnchorPaneProperty: it has an ObjectProperty, so you can use an ObjectProperty<Node> or an ObjectProperty<AnchorPane> if you want/need.


In general, though, I think it's also a bad idea to expose view elements (like your canvas) outside of the controller, and even to share controllers with other parts of the application. You should instead consider using a MVC-type approach. What is missing from your question is the "M": the model. So your model would use an object model with classes like Image, Scale, Composite, etc. The classes should use JavaFX properties to make them observable and use ObservableList (and similar) to store collections of these objects and relationships among them. Create a single model instance and pass it to your controller. The controller can observe the data in the model and update the view if it changes, and update the model according to user input. Then share the model instance with the other parts of the application that need access to it. This keeps things more maintainable: if you decide, for example, canvas needs to be a Pane instead of an AnchorPane, that change is now confined to your FXML and controller, and doesn't leak out over the rest of your application. A (much simpler) example of MVC (really more MVP) in JavaFX is in this question.

Can't set the value of a static TextField in javafx

JavaFX does not inject components into static fields. If possible, you should pass an instance of this class around instead of using static fields. This can be accomplished by allowing methods and class using this class to take this class as a parameter. You are also able to get the controller using FXMLLoader#getController.

However, if necessary, you can have a non-static field and set a static field on initialize:

@FXML
private TextField ReferenceKey;

public static TextField referenceKeyPublic;

...

@Override
public void initialize(URL url, ResourceBundle rb) {
ClassName.referenceKeyPublic = ReferenceKey;
}

javafx 8 compatibility issues - FXML static fields

It sounds like you are trying to inject a TextField into a static field. Something like

@FXML
private static TextField myTextField ;

This apparently worked in JavaFX 2.2. It doesn't work in JavaFX 8. Since no official documentation ever supported this use, it's doesn't really violate backward compatibility, though in fairness the documentation on exactly what the FXMLLoader does is pretty woeful.

It doesn't really make much sense to make @FXML-injected fields static. When you load an FXML file, it creates new objects for each of the elements in the FXML file. A new controller instance is associated with each call to FXMLLoader.load(...) and the fields in that controller instance are injected with the corresponding objects created for the FXML elements. So the injected fields are necessarily specific to the controller instance. If you had a static injected fields in the controller, and you loaded the same FXML file twice and displayed it twice in the UI, then you would have no way of referencing both sets of controls.

Update: Response to question in comments

In particular, don't use static fields just to enable them to be accessible from outside the class. A static field has a single value belonging to the class, instead of a value for each instance of the class, and the decision to make fields static should only be made if that makes sense. In other words, static defines scope, not accessibility. To allow access to instance data, you just have to have a reference to the instance. The FXMLLoader has a getController() method that allows you to retrieve a reference to the controller.

A related point: it's also not a good idea to expose the UI controls from the controller. You should instead expose the data. For example, instead of defining a getTextField() method in the controller, instead define a textProperty() method that returns a StringProperty representing the contents of the TextField. The reason for this is that when your boss comes to the office and tells you he wants the TextField to be replaced by a TextArea, or a ComboBox<String>, or some other control, then it's going to be a lot harder if classes outside of the controller are using your TextField. The structure of the data represented by your controller is much less likely to change than the implementation of how that data is presented to the user.

For some examples

  • Passing Parameters JavaFX FXML
  • JavaFX Data Mangement
  • Access fields from another Controller in 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

Issues with upgrading from JavaFX 2.2 to JavaFX 8 (Possible bug?)

I believe support for static access via @FXML disappeared in JavaFX 8. Remove the static modifier.

Static @FXML variables in FXML Controller

Anyway I refactored everything and it wasn't as hard as I thought. I spent about half the time refactoring as I did searching for a solution.

I'll leave the question in case any other poor souls have the same problem.

JavaFX-8 setRoot on embedded fxml causes cursor flickering and memory / cpu resource issues

Alright, so the problem was accidental recursion/loop. The code I executed to load a Node from a FXML was in the initialize(URL, ResourceBundle)-method. Now, setting the controller of the new loaded fxml to the same controller-object from where it is created from (loader.setController(this);) causes the initialize(URL, ResourceBundle)-method to run again and therefore load the same fxml again. This never stops.

My solution was to remove the loader.setController(this); call and (more importantly) set the controller in the fxml itself to some other controller. To keep connection between the controllers I created an interface called SubController. All controllers which represent a step/task and not the wizard itself implement it. The interface specifies the four onButtonClick-methods I want to forward to the SubController. Actual handling of the event now happens in the SubController.

@FXML
void onCancel(ActionEvent event)
{
this.currentSub.onCancel(event);
}

@FXML
void onFinish(ActionEvent event)
{
this.currentSub.onFinish(event);
}

@FXML
void onNext(ActionEvent event)
{
this.currentSub.onNext(event);
}

@FXML
void onPrevious(ActionEvent event)
{
this.currentSub.onPrevious(event);
}

Root cannot be null exception with multiple FXML/Controller in JavaFX

You're creating a new WorkoutsController instance in your WorkoutsMainController, instead of using the one created for you by the FXMLLoader. The @FXML-annotated fields in the instance you create will all be null, so you are probably getting a null pointer exception somewhere. (You should be seeing the "Error when trying to load WorkoutsMain.fxml" message in the console.) Thus anchorPane in your start() method is never initialized and you get an exception when you pass the null anchorPane reference to the Scene constructor.

Note also you have (for some unknown reason) made splitPane and anchorPane static: it makes no sense to do that, and the FXMLLoader will not initialize static fields. So those fields are also null in the controller.

If you don't squash exceptions thrown by loading the FXML, you will be able to see the actual stack trace for the underlying exception.

The fixes are:

  1. don't make @FXML-annotated fields static. See javafx 8 compatibility issues - FXML static fields

  2. To inject the actual controller instance for the included FXML that is created by the FXMLLoader, see Nested Controllers in the documentation. In short, since your fx:include has fx:id="workoutsPane", you should replace

     private final WorkoutsController workoutsController = new WorkoutsController();

    with

     @FXML private WorkoutsController workoutsPaneController ;

    (and replace all occurrences of workoutsController with workoutsPaneController in the remainder of WorkoutsMainController).

Running my app returns me : Javafx.fxml.LoadExeption

You get a NullPointerException in your initialize() method. That is because your ComboBox methode is declared static.

The FXML injection does not work with static variables in JavaFX8.

Refer to the answer by James_D to this question:

javafx 8 compatibility issues - FXML static fields

public class HexaController implements Initializable {

private org.jdom2.Document document;

private JFrame fileDialog;
private Element racine;
private String Nom_dar;
private java.util.List<Element> listeDAS;
private ArrayList<String> Nom_das = new ArrayList();
private ArrayList<String> Nom_task = new ArrayList();
private ArrayList<String> Nom_Variable1 = new ArrayList();
private ArrayList<String> Resultat_Variable1 = new ArrayList();
private ArrayList<Double> Resultat_Variabledouble1 = new ArrayList<Double>();
private ArrayList<String> ResultatHexa_Variable1 = new ArrayList<String>();
private ArrayList<String> Lien = new ArrayList();
private java.util.List<Element> ListeTRIAL;
private java.util.List<Element> listeTASK;
private java.util.List<Element> listeOperation;
private List listeOperation_delta;
private java.util.List<Element> listeArgument;
private List listeArgument_delta;

@FXML
private ComboBox<String> hexa;
ObservableList<String> list = FXCollections.observableArrayList();
@FXML
private TextField entree;

@FXML
private TextField nomfichier;

@FXML
private TextField excel;

@FXML
private TextField sortie;

@FXML
private void dar(ActionEvent event) {
FileDialog fd1 = new FileDialog(fileDialog, "Choisissez un fichier .dar", FileDialog.LOAD);
fd1.setDirectory("C:\\");
fd1.setVisible(true);
String filename1 = fd1.getFile();
String Directory1 = fd1.getDirectory();
String path1 = Directory1 + filename1;
entree.setText(path1);
nomfichier.setText(filename1);
}

@FXML
private void modele(ActionEvent event) {
JFrame parentFrame = new JFrame();
FileDialog filechooser = new FileDialog(parentFrame, "Choisir un modèle Excel à copier", FileDialog.LOAD);
filechooser.setDirectory("C:\\");
filechooser.setVisible(true);
String directory_copy = filechooser.getDirectory();
String name_copy = filechooser.getFile();
String path_copy = (directory_copy + name_copy);
excel.setText(path_copy);
}

@FXML
private void sortie(ActionEvent event) {
JFrame parentFrame2 = new JFrame();
FileDialog filechooser2 = new FileDialog(parentFrame2, "Choisir une destination d'enregistrement", FileDialog.SAVE);
filechooser2.setDirectory("C:\\");
filechooser2.setVisible(true);
String directory_save = filechooser2.getDirectory();
String name_save = filechooser2.getFile();
String path_save = (directory_save + name_save + ".xls");
sortie.setText(path_save);
}

@FXML
private void annuler(ActionEvent event) {
System.exit(0);
}

@FXML
private ComboBox<Integer> methode;
ObservableList<Integer> nombre = FXCollections.observableArrayList();

@FXML
private void exe(ActionEvent event) throws Throwable {
SAXBuilder sxb = new SAXBuilder();
String filename1 = nomfichier.getText();
if (filename1 == null) {
System.out.println("Annulation");
System.exit(0);
} else {
try {
//On crée un nouveau document JDOM avec en argument le fichier XML
//Le parsing est terminé
String path1 = entree.getText();
document = sxb.build(new File(path1));
//On initialise un nouvel élément racine avec l'élément racine du document.
racine = document.getRootElement();
System.out.println("Lecture du premier .dar");
} catch (Exception e) {
System.out.println("FATAL ERROR !");
System.exit(0);
}
}
afficheALL();

Xl();
}

//RECUPERATION DES DONNEES DU FICHIER DAR
private void afficheALL() {
//Variables Locales
String Str;
String Str_bis;
Nom_dar = racine.getAttributeValue("label");

System.out.println("Nom du fichier dar : " + Nom_dar + "\n");

listeDAS = racine.getChildren("DAS");
//On crée un Iterator sur notre liste
Iterator<Element> i1 = listeDAS.iterator();

////TRAITEMENT POUR CHAQUE DAS
String m[] = new String[50];
m[0] = "Marqueur de Sortie";
m[1] = "Nb Occurences";
m[2] = "Valeur Physique";
m[3] = "Résultat";
m[4] = "Valeur de Sortie";

int s = methode.getValue();
int n;
for (n = 5; n < 5 + s; n++) {

//System.out.println("Entrez le nom de la methode");
//Scanner methode = new Scanner(System.in);
//String name=methode.nextLine();
//m[n]=name;
}
int j;
for (j = 0; j < 5 + s; j++) {
System.out.println("Le nom de la methode " + j + " est :" + m[j]);
}
while (i1.hasNext()) {
Element courant = (Element) i1.next();
//On affiche le nom de l’élément courant
Str = courant.getAttributeValue("dasFullFileName").toString();
System.out.println("Nom du DAS: " + Str);
Nom_das.add(Str);

ListeTRIAL = courant.getChildren("TRIAL");
@SuppressWarnings("rawtypes")
Iterator T1 = ListeTRIAL.iterator();

while (T1.hasNext()) {
Element courant2 = (Element) T1.next();
Lien.add(courant2.getAttributeValue("xconvFullFileName").toString());
}

listeTASK = courant.getChildren("TASK");
@SuppressWarnings("rawtypes")
Iterator k1 = listeTASK.iterator();

while (k1.hasNext()) {
Element courant2 = (Element) k1.next();
Str_bis = courant2.getAttributeValue("label").toString();
System.out.println(Str_bis);

listeOperation = courant2.getChildren("Operation");
@SuppressWarnings("rawtypes")
Iterator l1 = listeOperation.iterator();

while (l1.hasNext()) {
Element courant3 = (Element) l1.next();
listeArgument = courant3.getChildren("Argument");
@SuppressWarnings("rawtypes")
Iterator m1 = listeArgument.iterator();

while (m1.hasNext()) {

Element courant4 = (Element) m1.next();
int l;

for (l = 0; l < 5 + s; l++) {
if (courant4.getAttributeValue("parameterName").compareTo(m[l]) == 0) {
Nom_Variable1.add(courant4.getAttributeValue("argumentName"));
double d = Double.parseDouble(courant4.getAttributeValue("instantValue"));
DecimalFormat df = new DecimalFormat(".#");
df.setMaximumFractionDigits(20);
String sortie = df.format(d);
long entier = (long) d;
String hex = Long.toHexString(entier).toUpperCase();
//hex.toUpperCase();
Resultat_Variable1.add(sortie);
Resultat_Variabledouble1.add(d);
ResultatHexa_Variable1.add(hex);

}
}
}
}
}
System.out.println("Fin lecture XML File");
}
}

private void Xl() throws Throwable {

try {

System.out.println("Saisi utilisateur ok");

//CREATION DES FEUILLES NECESSAIRES A LA RECUPERATION DES DONNEES
String path_copy = excel.getText();
String path_save = sortie.getText();
Workbook copie = Workbook.getWorkbook(new File(path_copy));
WritableWorkbook sortie = Workbook.createWorkbook(new File(path_save), copie);
WritableFont bord = new WritableFont(WritableFont.ARIAL, 11);
WritableCellFormat border = new WritableCellFormat(bord);

//REMPLISSAGE DE LA PREMIERE FEUILLE AVEC LES DONNEES MESUREES
System.out.println("---" + Nom_Variable1.size() + "---");
for (int x = 0; x <= Nom_Variable1.size() - 1; x++) {
Label Col0 = new Label(0, x, Nom_Variable1.get(x), border);
sortie.getSheet(1).addCell(Col0);
}

for (int y = 0; y <= Resultat_Variabledouble1.size() - 1; y++) {
Number Col1 = new Number(1, y, Resultat_Variabledouble1.get(y), border);
sortie.getSheet(1).addCell((WritableCell) Col1);
}

for (int y = 0; y <= ResultatHexa_Variable1.size() - 1; y++) {
Label Col2 = new Label(2, y, ResultatHexa_Variable1.get(y), border);
sortie.getSheet(1).addCell(Col2);
}

System.out.println("Fin Copie Excel");
sortie.write();
sortie.close();

} catch (RowsExceededException e1) {
} catch (BiffException ex) {
} catch (WriteException e1) {
} catch (IOException e) {
} finally {
System.out.println("Le fichier à été généré correctement.");
System.out.println("Appuyez sur 'Entrée' pour terminer le programme");
System.exit(0);
}

System.exit(0);
}

public HexaController() {

}

@Override
public void initialize(URL url, ResourceBundle rb) {

list.add(new String("OUI"));
list.add(new String("NON"));
hexa.setItems(list);
nombre.add(new Integer(0));
nombre.add(new Integer(1));
nombre.add(new Integer(2));
nombre.add(new Integer(3));
nombre.add(new Integer(4));
nombre.add(new Integer(5));
methode.setItems(nombre);
}

}


Related Topics



Leave a reply



Submit