0

I am creating a order management system for a family member. I need the application to be able to update itself at runtime when the user selects whether the order is for cakes, cookies, cupcakes or other.

What I have been trying as a solution is add an empty Pane to the form, and on click have the form populate the Pane with components located in another FXML file. The problem is this: my fields in the controller are all set to null, so when I try to update the Pane, I get a null pointer exception. I cannot figure out why all of my fields are null, but I have a suspicion it is the @sfxml annotation not getting along with the way I have it set up. Maybe there is a better way to do this altogether. Any help is appreciated. Here is what I have so far; I will try to give a demonstratable example. The area where I plan to add the components is visible by the comment in the form, just under the checkboxes:

The Form: Form.FXML

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<BorderPane id="rootLayout" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
            prefHeight="500.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="controller.FormController">
    <center>
        <VBox prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
            <children>
                <TilePane maxHeight="119.0" minHeight="100.0" prefHeight="119.0" prefWidth="485.0">
                    <children>
                        <HBox maxHeight="30.0" maxWidth="485.0" minHeight="10.0" prefHeight="14.0" prefWidth="485.0"
                              TilePane.alignment="CENTER">
                            <children>
                                <TextField fx:id="fNameField" maxHeight="30.0" minHeight="17.0" prefHeight="18.0"
                                           prefWidth="156.0" promptText="first name"/>
                                <TextField fx:id="lNameField" maxHeight="30.0" minHeight="30.0" promptText="last name"/>
                                <TextField fx:id="dateField" maxHeight="30.0" minHeight="26.0" prefHeight="30.0"
                                           prefWidth="151.0" promptText="due date"/>
                            </children>
                        </HBox>
                        <HBox maxHeight="30.0" maxWidth="485.0" minHeight="10.0" prefHeight="14.0" prefWidth="485.0"
                              TilePane.alignment="CENTER">
                            <children>
                                <TextField fx:id="phoneNumber" maxHeight="30.0" minHeight="17.0" prefHeight="18.0"
                                           prefWidth="156.0" promptText="phone number"/>
                                <TextField fx:id="email" maxHeight="30.0" minHeight="30.0" prefHeight="30.0"
                                           prefWidth="331.0" promptText="e-mail"/>
                            </children>
                        </HBox>

                        <TextField fx:id="streetField" maxHeight="30.0" minHeight="30.0" promptText="street"/>
                        <HBox maxHeight="44.0" minHeight="0.0" prefHeight="27.0" prefWidth="485.0">
                            <children>
                                <TextField fx:id="cityField" maxHeight="30.0" minHeight="30.0" prefHeight="30.0"
                                           prefWidth="374.0" promptText="city"/>
                                <TextField fx:id="stateField" maxHeight="30.0" minHeight="30.0" prefHeight="30.0"
                                           prefWidth="109.0" promptText="state"/>
                                <TextField fx:id="zipField" maxHeight="30.0" minHeight="30.0" prefHeight="30.0"
                                           prefWidth="136.0" promptText="zip"/>
                            </children>
                        </HBox>
                    </children>
                </TilePane>
                <HBox maxHeight="65.0" minHeight="30.0" prefHeight="33.0" prefWidth="485.0" spacing="95.0">
                    <children>
                        <CheckBox fx:id="cakeCB" mnemonicParsing="false" text="CK" onAction="#checkboxSelected">
                            <HBox.margin>
                                <Insets top="8.0"/>
                            </HBox.margin>
                        </CheckBox>
                        <CheckBox  fx:id="cookiesCB" mnemonicParsing="false" text="CO">
                            <HBox.margin>
                                <Insets top="8.0"/>
                            </HBox.margin>
                        </CheckBox>
                        <CheckBox  fx:id="cupcakesCB" mnemonicParsing="false" text="CC">
                            <HBox.margin>
                                <Insets top="8.0"/>
                            </HBox.margin>
                        </CheckBox>
                        <CheckBox  fx:id="otherCB" mnemonicParsing="false" text="O">
                            <HBox.margin>
                                <Insets top="8.0"/>
                            </HBox.margin>
                        </CheckBox>
                    </children>
                    <padding>
                        <Insets left="18.0"/>
                    </padding>
                </HBox>
                <HBox>

                 <!--


                 included data here-->
                <Pane id="selectionQuestions" />
                </HBox>
                <TextArea fx:id="notes" prefHeight="200.0" prefWidth="200.0"/>
                <HBox alignment="BOTTOM_RIGHT" maxHeight="30.0" minHeight="30.0" prefHeight="100.0" prefWidth="200.0">
                    <children>
                        <Button id="submitOrderButton" mnemonicParsing="false" onAction="#submitOrder" text="submit"/>
                    </children>
                </HBox>
            </children>
        </VBox>
    </center>
    <padding>
        <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
    </padding>
</BorderPane>

To be added in the HBox area: check_box_selected.fxml

<?xml version="1.0" encoding="UTF-8"?>

  <?import javafx.scene.layout.HBox?>
  <?import javafx.scene.control.Label?>  

    <HBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" minWidth="30" minHeight="30">
      <Label text="Here's my selected checkbox label" />
    </HBox>

FormController.scala:

package controller

import javafx.event.ActionEvent
import javafx.scene._
import javafx.stage.Stage

import javafx.scene.layout.Pane
import javafx.scene.control.CheckBox
import scalafxml.core.macros.sfxml
import scalafxml.core.{FXMLView, NoDependencyResolver}
import javafx.fxml.FXMLLoader


@sfxml
class FormController(private val selectionQuestions: Pane,
                     private val cakeCB: CheckBox,
                     private val cookiesCB: CheckBox,
                     private val cupcakesCB: CheckBox,
                     private val otherCB: CheckBox) {

  def submitOrder(actionEvent: ActionEvent) {
    val source = actionEvent.getSource.asInstanceOf[Node].getScene.getWindow.asInstanceOf[Stage]
    source.hide;
  }

  def checkboxSelected(actionEvent: ActionEvent) {
    val source = actionEvent.getSource
    val box: CheckBox = source.asInstanceOf[CheckBox]

    box.getId match {
      case "cakeCB" => {
        val resource = getClass.getResource("/scala/cake_box_selected.fxml")
        val root = FXMLView(resource, NoDependencyResolver)

        //This is the null pointer => 
        selectionQuestions.getChildren.clear
        selectionQuestions.getChildren.add(root)
      }
    }
  }

}
Patrick S.
  • 275
  • 1
  • 5
  • 19

1 Answers1

1

You have to use the ScalaFX versions of the controls in the controller class' constructor. So simply instead of

import javafx.scene.layout.Pane
import javafx.scene.control.CheckBox

use

import scalafx.scene.layout.Pane
import scalafx.scene.control.CheckBox

This is a limitation of the scalafxml library which should be fixed. (Created an issue for it: https://github.com/vigoo/scalafxml/issues/19)

EDITED

As you explained in the comments, when using the ScalaFX types, it fails with cast exception when trying to use the ActionEvent's source. This property has the type Object, and holds a reference to the JavaFX control itself, which has type javafx.scene.control.CheckBox. This can be implicitly converted to the ScalaFX wrapper that you have to use because of the limitations of the macro.

So the solution is to:

  • Use the ScalaFX types everywhere
  • Except in the cast, where you have to use the JavaFX type - the compiler will take care of the conversion.

Example:

import scalafx.event.ActionEvent
import scalafx.scene.control.{CheckBox, ComboBox, TextField}

// ...

  def testCheckBoxAction(event: ActionEvent): Unit = {
    val source: CheckBox = event.source.asInstanceOf[javafx.scene.control.CheckBox]
    // ...
  }
vigoo
  • 301
  • 4
  • 12
  • There's actually a reason I used those imports. When I have to "get" the source of the click in the controller, I use this syntax: val source = actionEvent.getSource val box: CheckBox = source.asInstanceOf[CheckBox] If I am using the scalafx import, I get a ClassCastException saying javafx.scene.control.CheckBox cannot be cast to scalafx.scene.control.CheckBox. So, I have been using the javafx versions in my controllers so far. Is there another way to do this that will allow me to use the scalafx versions in my controller that you know of? – Patrick S. May 30 '16 at 21:44
  • I know the cast exception is because the FXML import is javafx and if I use scalafx in my controller I get the exception. – Patrick S. May 30 '16 at 21:51
  • `@sxml` also translates the event handler parameters so you can use scalafx's `ActionEvent` wrapper in them. Not sure if this fixes the issue, I'll try to reproduce your problem later. – vigoo May 31 '16 at 13:18