Search code examples
javajavafxjavafx-8embedvisual-glitch

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


I want to build a wizard-style application in JavaFX. Therefore I have got a wizard.fxml with navigation buttons and an empty AnchorPane for each step/task which is in a seperate fxml file respectively to embed them in the (main) wizard during runtime.

Now when I want to call the first step like that in the WizardController:

FXMLLoader loader = SpringFXMLLoader.getLoader(this.getClass().getClassLoader().getResource("logon.fxml"));  //creates a loader with the current application context
loader.setController(this);
loader.setRoot(this.taskPane /*The AnchorPane*/);
loader.load();

it gets loaded as expected but I get this problem: Now whenever I hover over things like a button or a TextField, the cursor starts flickering between a "hand"-cursor and a "default"-cursor. Buttons in the wizard.fxml are flickering but still working, but I can't click and/or type into the TextFields of the embedded logon.fxml. I thought maybe it has something to do with overlapping objects, so I disabled PickOnBounds for every node but the problem remains. Also running the program consumes a hell lot of CPU and RAM resources, I cannot even drag the program windows smoothly.

Using this code (without fx:root in logon.fxml):

FXMLLoader loader = SpringFXMLLoader.getLoader(this.getClass().getClassLoader().getResource("logon.fxml"));  //creates a loader with the current application context
loader.setController(this);
Node task = loader.load();
taskPane.getChildren().clear();
taskPane.getChildren().add(task);
AnchorPane.setTopAnchor(task, 0d);
AnchorPane.setRightAnchor(task, 0d);
AnchorPane.setBottomAnchor(task, 0d);
AnchorPane.setLeftAnchor(task, 0d);

gives me the exact same output and problem. When I comment out the single line loader.load(); the problem does not occur, but obviously I need to load.

wizard.fxml (without imports)

<BorderPane stylesheets="@application.css" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="path.to.my.controller.WizardController">
   <bottom>
      <ButtonBar id="buttonBar" BorderPane.alignment="CENTER">
        <buttons>
          <Button id="btnPrevious" alignment="CENTER" contentDisplay="CENTER" mnemonicParsing="false" pickOnBounds="false" text="&lt; Zurück" />
            <Button id="btnNext" alignment="CENTER" contentDisplay="CENTER" defaultButton="true" mnemonicParsing="false" pickOnBounds="false" text="Weiter &gt;" />
            <Button id="btnFinish" alignment="CENTER" contentDisplay="CENTER" mnemonicParsing="false" pickOnBounds="false" text="Fertigstellen" />
            <Button id="btnCancel" alignment="CENTER" cancelButton="true" contentDisplay="CENTER" mnemonicParsing="false" pickOnBounds="false" text="Abbrechen" />
        </buttons>
      </ButtonBar>
   </bottom>
   <center>
      <AnchorPane fx:id="taskPane" pickOnBounds="false" BorderPane.alignment="CENTER" />
   </center>
</BorderPane>

logon.fxml (with fx:root)

<fx:root type="AnchorPane" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <TitledPane alignment="TOP_LEFT" animated="false" collapsible="false" contentDisplay="CENTER" pickOnBounds="false" styleClass="taskPane" stylesheets="@application.css" text="Logon" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
         <font>
            <Font name="System Bold" size="18.0" />
         </font>
         <content>
            <GridPane maxHeight="150.0" maxWidth="250.0" pickOnBounds="false" styleClass="inputGrid">
              <columnConstraints>
                <ColumnConstraints hgrow="SOMETIMES" maxWidth="94.0" minWidth="10.0" prefWidth="58.0" />
                <ColumnConstraints hgrow="SOMETIMES" maxWidth="142.0" minWidth="10.0" prefWidth="140.0" />
              </columnConstraints>
              <rowConstraints>
                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
              </rowConstraints>
               <children>
                  <Label pickOnBounds="false" text="Mandant" />
                  <Label pickOnBounds="false" text="Benutzer" GridPane.rowIndex="1" />
                  <Label pickOnBounds="false" text="Kennwort" GridPane.rowIndex="2" />
                  <TextField id="fldMandant" fx:id="fldMandant" pickOnBounds="false" GridPane.columnIndex="1" />
                  <TextField fx:id="fldBenutzer" pickOnBounds="false" prefWidth="110.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
                  <TextField fx:id="fldKennwort" pickOnBounds="false" GridPane.columnIndex="1" GridPane.rowIndex="2" />
               </children>
            </GridPane>
         </content>
      </TitledPane>
   </children>
</fx:root>

Flickering cursor problem gif


Solution

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