javaFX :: popup window launches, but controller doesn't...? - javafx

I am writing an app where I need the same custom popup window to appear when different buttons are clicked. Right now the popup is just a simple "Are You Sure? OK/Cancel" window, but later it will expand to include more customized features... so I can't use the quickie Dialog built-ins.
Here's the weird thing. When Button X is pushed, the popUp (defined in FXML) launches just fine, but my controller class doesn't seem to be running. I didn't think that you could do that. What I can't figure out is why the controller isn't running. I would have thought the app would crash if the controller wasn't working.
Here' the code a button will call to launch the popup:
private void popUpLaunch(Button caller){
Stage popUpStage = new Stage();
Parent root;
try {
root = FXMLLoader.load(getClass().getResource("popUp1.fxml"));
popUpStage.setScene(new Scene(root));
popUpStage.initModality(Modality.APPLICATION_MODAL); // popup
popUpStage.initOwner(caller.getScene().getWindow());
popUpStage.showAndWait();
} catch (IOException e) {
e.printStackTrace();
}
}
All of that works great. Here's the complete FXML, /src/sl/view/popUp1.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="130.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sl.view.PopUp1Controller">
<children>
<Text fx:id="popUpMessageText" layoutX="14.0" layoutY="14.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Message Goes Here" textAlignment="CENTER" wrappingWidth="577.6708984375" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<font>
<Font size="38.0" />
</font>
</Text>
<Button fx:id="btnPopUpOK" layoutX="126.0" layoutY="68.0" mnemonicParsing="false" prefHeight="31.0" prefWidth="157.0" text="OK" />
<Button fx:id="btnPopUpCancel" layoutX="286.0" layoutY="68.0" mnemonicParsing="false" prefHeight="31.0" prefWidth="169.0" text="Cancel" />
</children>
</AnchorPane>
The window loads just fine. And finally, here's the complete controller, /src/sl/view/PopUp1Controller.java:
package sl.view;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class PopUp1Controller {
#FXML Text popUpMessageText;
#FXML Button btnPopUpOK;
#FXML Button btnPopUpCancel;
Stage stage;
public void start(Stage stage) throws Exception{
System.out.println("Popup controller launched!"); // never reach this... so the controller is not launching???
this.stage = stage;
popUpMessageText.setText("Interesting message here!");
btnPopUpOK.setOnAction(event -> {
System.out.println("You cliced OK...");
});
btnPopUpCancel.setOnAction(event -> {
System.out.println("You cliced Cancel");
stage.close();
});
}
}
Some thoughts...
I used SceneBuilder to generate the FXML. When I assigned the Controller Class for that AnchorPane, I picked "sl.view.PopUp1Controller" from the drop-down menu. So I'm pretty sure that's right.
Also: I've looked through the other "JavaFX Popup" posts, but I don't see one that specifically addresses my issue. A lot of post are like the below post which are basically, "Why not use these other popup options rather than re-invent the wheel?" e.g.:
JavaFX 2 custom popup pane
In my case, I do want to reinvent the wheel, because I need my popups to carry more-then-usual functionality, they will not be simple dialog boxes.

If you want code in a controller to execute when the controller is initialized, put it in the initialize() method (see the documentation):
public class PopUp1Controller {
#FXML Text popUpMessageText;
#FXML Button btnPopUpOK;
#FXML Button btnPopUpCancel;
public void initialize() {
System.out.println("Popup controller launched!"); // never reach this... so the controller is not launching???
popUpMessageText.setText("Interesting message here!");
btnPopUpOK.setOnAction(event -> {
System.out.println("You cliced OK...");
});
btnPopUpCancel.setOnAction(event -> {
System.out.println("You cliced Cancel");
btnPopupCancel.getScene().getWindow().hide();
});
}
}

Related

Request focus to shape is getting to the wrong control

I am trying to request focus to a rectangle shape in my scene. When I do that using the mouse the focus property listener hasn't fired even the shape is traversable to focus. I tried to add an EventListener to the shape to call the requestFocus() method on it but after the rectangle gain the focus another button that doesn't have any listeners or additional code stole the focus immediately. After I removed that button the same thing is done by another control (all the controls that stole the focus are before the rectangle in the order of addition to the root node). I tried to traverse the focusing using the Tab button and it works. So, how can I request the focusing to the rectangle using the mouse?
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="232.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<ToolBar prefHeight="40.0" prefWidth="200.0">
<items>
<Button mnemonicParsing="false" text="Button" />
</items>
</ToolBar>
<HBox prefHeight="100.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
<children>
<VBox prefHeight="200.0" prefWidth="100.0">
<children>
<ListView id="slotsList" prefHeight="200.0" prefWidth="200.0" />
</children>
</VBox>
<FilmTimeLine HBox.hgrow="ALWAYS" fx:id="timeLine"/>
</children>
</HBox>
</children>
</VBox>
Rectangle class that I am adding it to the scene inside the FilmTimeLine (the FilmTimeLine is a ScrollPane that has a Pane child. The rectangle is inside that Pane):
public class EventSlot extends Rectangle {
public EventSlot() {
setFocusTraversable(true);
setWidth(100);
setHeight(25);
setFill(Color.web("#9DD3DF"));
focusedProperty().addListener((observable, oldValue, newValue) -> {
if (isFocused()) {
setEffect(FOCUSED_EFFECT);
} else {
setEffect(INNER_SHADOW);
}
});
addEventHandler(MouseEvent.MOUSE_PRESSED, (mouseEvent) -> {
requestFocus();
});
}
}
gif of what is happening
When an action occurs, the system constructs event route.
Looks like your mouse event is handled by Rectangle (I assume it is inherited from Node), Pane and FilmTimeLine.
So we need to construct event route to ignore/filter all nodes except of selected rectangle.
Options to modify the route:
Consuming of an Event. Invoke mouseEvent.consume() to stop event propagation.
addEventHandler(MouseEvent.MOUSE_CLECKED, (mouseEvent) -> {
requestFocus();
mouseEvent.consume();
});
Making parent node mouse transparent Node.setMouseTransparent(true). If true, this node (together with all its children) is completely transparent so make sure that all rectagles mouseTransparent property is false.
pane.setMouseTransparent(true);//the FilmTimeLine is a ScrollPane that has a Pane child
Adding event filter Node.addEventFilter
pane.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) { event.consume(); };
});

JavaFX Custom Control revisited

I have run the Mastering FXML example, How to create custom components in JavaFX 2.0 using FXML and tried various other solutions from this site, but I still haven't found a simple enough example showing how to set up a custom control that is NOT the only part of the GUI. Since the question is still popping up it seems we need a simpler example for some of us..
I'm trying to create a simple control consisting of a vertical SplitPane with a Button in the top section and a label in the lower section. Then I want to place instances of this SplitPane-control in multiple tabs in a TabPane.
Either the control won't show up, or I get stuck in various errors, depending on which example I try to follow. So, I'll backtrack a bit and will just simply ask: How do I separate out the SplitPane to be the custom control here?
Here is the FXML document:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<TabPane prefHeight="256.0" prefWidth="477.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="customcontroltest.FXMLDocumentController">
<tabs>
<Tab>
<content>
<SplitPane dividerPositions="0.5" orientation="VERTICAL" prefHeight="114.0" prefWidth="160.0">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<Button fx:id="button" onAction="#handleButtonAction" text="Click Me!" />
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<Label fx:id="label" minHeight="16" minWidth="69" />
</children>
</AnchorPane>
</items>
</SplitPane>
</content>
</Tab>
</tabs>
</TabPane>
And the controller class:
package customcontroltest;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
public class FXMLDocumentController implements Initializable
{
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event)
{
label.setText("Hello World!");
}
#Override
public void initialize(URL url, ResourceBundle rb)
{
// TODO
}
}
And the main test class:
package customcontroltest;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class CustomControlTest extends Application
{
#Override
public void start(Stage stage) throws Exception
{
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
I made a new FXML file and cut/pasted the whole SplitPane tag and all its contents into it. I replaced the SplitPane tag in the FXML document with <packageName.ControlClassName />. Then I made the controller class to extend SplitPane. I've tried specifying the controller in the FXML tag and/or in the controller class, but never got it right.
Would someone with the right knowledge be willing to take a few minutes to just show a working example of this? I'm guessing more people would find such an example very useful.
So, the SplitPane should be the new custom control, and you can then by default load it into the first tab in the TabPane. Then I will write code to add more instances into subsequent tabs.
Thank you so very much in advance.
UPDATE:
I have broken out the SplitPane into its own FXML and controller class.
Here is the FXML (CustomSplitPane.fxml):
<fx:root type="javafx.scene.control.SplitPane" dividerPositions="0.5" orientation="VERTICAL" prefHeight="114.0" prefWidth="160.0" xmlns:fx="http://javafx.com/fxml/1" fx:controller="customcontroltest.CustomSplitPaneController">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<Button fx:id="button" onAction="#handleButtonAction" text="Click Me!" />
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<Label fx:id="label" minHeight="16" minWidth="69" />
</children>
</AnchorPane>
</items>
</fx:root>
And the controller class (CustomSplitPaneController.java):
package customcontroltest;
public class CustomSplitPaneController extends AnchorPane
{
#FXML
private Label label;
private SplitPane mySplitPane;
public CustomSplitPaneController()
{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("CustomSplitPane.fxml"));
try
{
fxmlLoader.load();
} catch (IOException exception)
{
throw new RuntimeException(exception);
}
}
#FXML
private void handleButtonAction(ActionEvent event)
{
label.setText("Hello World!");
}
}
And the original main FXML now looks like this:
<TabPane prefHeight="256.0" prefWidth="477.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="customcontroltest.FXMLDocumentController">
<tabs>
<Tab>
<content>
<customcontroltest.CustomSplitPaneController />
</content>
</Tab>
</tabs>
</TabPane>
The fxmlLoader.load() in the CustomSplitPaneController seems to be causing a java.lang.StackOverflowError.
Maybe it's more evident now to someone here what I'm missing?
Thanks again.
The fx:controller attribute in an FXML file is an instruction for the FXML loader to create an instance of the specified class and use it as the controller for the UI hierarchy defined by the FXML.
In the attempt to create a custom split pane that you posted, what happens when you create an instance of CustomSplitPaneController is:
You create an FXMLLoader in the constructor of CustomSplitPaneController, which loads CustomSplitPane.fxml
CustomSplitPane.fxml has a fx:controller attribute specifying CustomSplitPaneController as the controller class, so it creates a new instance of CustomSplitPaneController (by calling its constructor, of course)
The constructor of CustomSplitPaneController creates an FXMLLoader which loads CustomSplitPane.fxml
and so on.
So very quickly, you get a stack overflow exception.
Control classes in JavaFX encapsulate both the view and the controller. In the standard JavaFX control classes the view is represented by a Skin class, and the controller by a Behavior class. The control class itself extends Node (or a subclass: Region) and when you instantiate it, it instantiates both the skin and the behavior. The skin defines the layout and appearance of the control, and the behavior maps various input actions to actual code that modifies the properties of the control itself.
In the pattern you are trying to replicate, shown here and here, this is slightly modified. In this version, the "view" is defined by an FXML file, and the controller (the behavior) is implemented directly in the control class itself (there is no separate behavior class).
To make this work, you have to use FXML slightly differently to usual. First, when you use your custom control, you are going to instantiate the control class directly (without any knowledge of the FXML that defines its layout). So if you are using this in java, you would do new CustomSplitPane(), and if you are using it in FXML you would do <CustomSplitPane>. Either way, you invoke the constructor of your custom control (which I'm calling CustomSplitPane).
To use CustomSplitPane in the UI hierarchy, it must of course be a Node subclass. If you want it to be a kind of SplitPane, you would make it extend SplitPane:
public class CustomSplitPane extends SplitPane {
// ...
}
Now, in the constructor of CustomSplitPane, you need to load the FXML file that defines the layout, but you need it to lay out the current object. (In the usual usage of an FXML file, the FXMLLoader creates a new node for the root of the hierarchy, of the type specified, and the load() method returns it. You want the FXMLLoader to use the existing object as the root of the hierarchy.) To do this, you use the <fx:root> element as the root element of the FXML file, and you tell the FXMLLoader to use this as the root:
loader.setRoot(this);
Additionally, since the handler methods are defined in the current object, you also want the controller to be the current object:
loader.setController(this);
Since you are specifying an existing object as the controller, you must not have a fx:controller attribute in the FXML file.
So you end up with:
package customcontroltest;
public class CustomSplitPane extends SplitPane {
#FXML
private Label label;
public CustomSplitPaneController() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("CustomSplitPane.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
#FXML
private void handleButtonAction(ActionEvent event)
label.setText("Hello World!");
}
}
and the FXML file:
<fx:root type="javafx.scene.control.SplitPane" dividerPositions="0.5" orientation="VERTICAL" prefHeight="114.0" prefWidth="160.0" xmlns:fx="http://javafx.com/fxml/1" >
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<Button fx:id="button" onAction="#handleButtonAction" text="Click Me!" />
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<Label fx:id="label" minHeight="16" minWidth="69" />
</children>
</AnchorPane>
</items>
</fx:root>
And now you can use this in another FXML file as you need:
<TabPane prefHeight="256.0" prefWidth="477.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="customcontroltest.FXMLDocumentController">
<tabs>
<Tab>
<content>
<customcontroltest.CustomSplitPane />
</content>
</Tab>
</tabs>
</TabPane>
Your custom control class should extends one of the Parent class, for example Region or Pane. If you are too lazy to do layout stuff, just extend an advanced pane like GridPane.
Then your custom control class should load an FXML containing the SplitPane and its children. The controller for the FXML can be simply this custom control class, or you can still separate it out to its personal controller class. The SplitPane node should be added as a child of the custom control class; this means that your custom control (which is a Parent type), has to handle some layout logic. At this point, your custom control is complete.
This control is ready to be used in FXML. However, if you want to use it in Scene Builder, you need to package it into a JAR file, and add it to Scene Builder. Note that in order for Scene Builder to work, your custom control class MUST have a parameterless constructor defined.
I'm not entirely sure if this is what you're looking for, but JavaFX controls are based on the Model, View, Controller (MVC) pattern.
Model
Where the Model class is where the any information is stored for your system. For example, if you had a textField, you'd store what value the text field is holding in the model class. I always think of it as a miniature database for my control.
View
The View class is visually what your control looks like. Defining the size, shape, color, etc. A note on "color", this is where you'd set the default color of your control. (this could also be done using FXML, but I personally would rather use java code). The model is often passed to the View constructor as an argument for binding using bean properties. (For java, not sure how you'd do it for FXML)
Controller
The controller class is where manipulation can occur. If I click a button, or change something in my textField, what does the controller do or how does it manipulate the model. The Model and View are both passed as argument to the controller. This gives the controller a reference to the model and the view which allows the controller to manipulate each as designed. Other outside classes can interact with your controller class and your controller class acts on the model and view.
With that said, without any additional information, it looks like everything you're doing is just combining existing controls into something pre-defined for reuse. It may be worth looking into defining a class that extends SplitPane, and a constructor that already adds your button and label to where you want them. Your new class could then be treated like a SplitPane as well as have your action for your button built in.
A really good break down of this is in the book,
Apress JavaFX 8 Introduction By Example Chapter 6
OK, so here is the working solution all laid out, file by file. Hopefully this is useful to someone else too.
CustomControlTest.java
package customcontroltest;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class CustomControlTest extends Application
{
#Override
public void start(Stage stage) throws Exception
{
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
FXMLDocument.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<TabPane prefHeight="256.0" prefWidth="477.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="customcontroltest.FXMLDocumentController">
<tabs>
<Tab>
<content>
<customcontroltest.CustomSplitPaneController />
</content>
</Tab>
</tabs>
</TabPane>
FXMLDocumentController.java
package customcontroltest;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.Initializable;
public class FXMLDocumentController implements Initializable
{
#Override
public void initialize(URL url, ResourceBundle rb)
{
// TODO
}
}
CustomSplitPane.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<fx:root type="javafx.scene.control.SplitPane" dividerPositions="0.5" orientation="VERTICAL" prefHeight="114.0" prefWidth="160.0" xmlns:fx="http://javafx.com/fxml/1" >
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<Button fx:id="button" onAction="#handleButtonAction" text="Click Me!" />
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<Label fx:id="label" minHeight="16" minWidth="69" />
</children>
</AnchorPane>
</items>
</fx:root>
The NetBeans IDE will give an error on #handleButtonAction, saying "Controller is not defined on root component", but it won't actually give compilation errors. (This is where I was tricked into not even trying to compile when I saw that highlighted error!)
CustomSplitPaneController.java
package customcontroltest;
import java.io.IOException;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
public class CustomSplitPaneController extends SplitPane
{
#FXML
private Label label;
public CustomSplitPaneController()
{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("CustomSplitPane.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try
{
fxmlLoader.load();
} catch (IOException exception)
{
throw new RuntimeException(exception);
}
}
#FXML
private void handleButtonAction(ActionEvent event)
{
label.setText("Hello World!");
}
}

How do you get the SimpleMenuButton to display the MenuItem text in JavaFX?

I am building an inventory management system for work and am having a lot of trouble getting the SplitMenuButton to display the MenuItem when I click on the item. I can not find much information on the 'SplitMenuButton' on the internet and have tried just MenuButton as well with no luck. For example, the default text is 'Department' and I would like it to display 'Aseptic' when that menu item is selected or 'Facilities' when that menu item is selected.
I have tried to create a new instance of SplitMenuButton and setText("Aseptic") when the #buildDataAseptic method is run, but this still didn't work.
My fxml code is:
<SplitMenuButton mnemonicParsing="false" prefHeight="25.0" prefWidth="217.0" text="Department" textAlignment="CENTER">
<items>
<MenuItem fx:id="asepticMenuItem" mnemonicParsing="false" onAction="#buildDataAseptic" text="Aseptic" />
<MenuItem fx:id="generalMenuItem" mnemonicParsing="false" onAction="#buildDataGeneral" text="General" />
<MenuItem fx:id="facilitiesMenuItem" mnemonicParsing="false" onAction="#buildDataFacilities" text="Facilities" />
</items>
</SplitMenuButton>
any help is greatly appreacitead, thanks!
As others have pointed out, ComboBox is a much better choice for your use case.
Here is a self contained example. First the FXML:
<?import javafx.scene.control.ComboBox?>
<?import javafx.collections.FXCollections?>
<?import java.lang.String?>
<ComboBox xmlns:fx="http://javafx.com/javafx/null">
<items>
<FXCollections fx:factory="observableArrayList">
<String fx:value="Aseptic" />
<String fx:value="General" />
<String fx:value="Facilities" />
</FXCollections>
</items>
</ComboBox>
And the application with comments:
package sample;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.stage.Stage;
public class Main extends Application {
public void start(Stage primaryStage) throws Exception {
ComboBox<String> comboBox = FXMLLoader.load(getClass().getResource("test.fxml"));
// Create an observable property for the selection
SimpleStringProperty selected = new SimpleStringProperty();
// Bind the property to the comboBox
selected.bindBidirectional(comboBox.valueProperty());
// Set initial value
selected.set("Facilities");
// React to changes
selected.addListener((observable, oldValue, newValue) -> {
// newValue contains the new selection, update database
System.out.println(newValue);
});
primaryStage.setScene(new Scene(comboBox));
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
General advice: Don't hang on to the SplitMenuButton just because you've spent a lot of time on it. Think of this as a learning experience. If you don't learn to let go and refactor, you'll never create good software :)

null pointer on javaFX element

I have the following code and when trying to set the text of the textArea of my fxml I get a null pointer exception. The window appears as it should but the controller is unable to identify the elements within it.
declaration:
public TextArea txtArea;
This is the controller method body:
Stage stage = new Stage();
FXMLLoader loader = new FXMLLoader();
AnchorPane page = loader.load(getClass().getResource("demo.fxml"));
loader.setController(this);
Scene scene = new Scene(page);
stage.setScene(scene);
stage.initOwner(primaryStage);
stage.initModality(Modality.WINDOW_MODAL);
stage.show();
txtArea.setText("something"); //this is where it crashes -> null pointer
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity" prefHeight="160.0" prefWidth="353.0"
xmlns="http://javafx.com/javafx/2.2">
<children>
<BorderPane prefHeight="-1.0" prefWidth="-1.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<center>
<TextArea fx:id="txtArea" editable="false" focusTraversable="false" prefWidth="200.0" wrapText="true" />
</center>
</BorderPane>
</children>
</AnchorPane>
You have two errors in your code. First, you are calling
loader.setController(this);
after you call
loader.load();
This means there is no controller set on the FXMLLoader when it loads the FXML file, so it cannot initialize the controller fields. Switch the order of the calls.
Secondly, you are using the static FXMLLoader.load(URL) method. Since this is a static method, it won't know anything about the state of the FXMLLoader you created, including the controller. You need to set the location, and use the instance method FXMLLoader.load() with no parameters.
FXMLLoader loader = new FXMLLoader(getClass().getResource("demo.fxml"));
loader.setController(this);
AnchorPane page = loader.load();
Scene scene = new Scene(page);
I also strongly recommend making all fields private, not public. Annotate the fields with #FXML to allow injection:
#FXML
private TextArea txtArea ;
I am not sure why it worked but someone might be able to explain why. I just initialised the FXMLLoader with the .fxml file instead of creating a loader and passing in the load method the resource. I.e.
FXMLLoader loader = new FXMLLoader(getClass().getResource("demo.fxml"));
loader.setController(this);
AnchorPane page = loader.load();
Scene scene = new Scene(page);

JavaFX controller injection does not work

I have two fxml files. I connect them with an include statement:
The "main" fxmlfile looks like that:
<?import javafx.geometry.*?>
// ...
<BorderPane prefHeight="962" prefWidth="1280" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MyMainController">
<center>
<SplitPane dividerPositions="0.63" BorderPane.alignment="CENTER">
<items>
<fx:include source="AnotherFile.fxml" />
// ...
</items>
</SplitPane>
</center>
<top>
// ...
</top>
</BorderPane>
And the second one (= "AnotherFile.fxml") like that:
<?import java.lang.*?>
// ...
<SplitPane dividerPositions="0.15" orientation="VERTICAL" prefHeight="400.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
<items>
// ...
<Label fx:id="oneOfMyLabels" text="myText" GridPane.columnIndex="2" GridPane.rowIndex="1" />
</items>
</SplitPane>
Now, I am using injections in the "main"-controller application.MyMainController:
#FXML
private Label oneOfMyLabels;
If I run the controller I get a java.lang.NullPointerException exception, respectively a java.lang.reflect.InvocationTargetException one. In debugging mode I found out, that the injected Label is null!
Now, my question:
Can't reach the MyMainController from the "main fxml file" the components of the included fxml file?? Do I have to use an own controller on each fxml file, if it is included or not?!
Thanks for your help!!
You need to have a different controller for each FXML file, and the fx:id-annotated elements of each file will be injected into the corresponding controller instance.
When you have included FXML files, you can inject the controller for the included file into the controller for the including file, by setting an fx:id attribute on the fx:include element:
"main" fxml file:
<?import javafx.geometry.*?>
// ...
<BorderPane prefHeight="962" prefWidth="1280" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MyMainController">
<center>
<SplitPane dividerPositions="0.63" BorderPane.alignment="CENTER">
<items>
<fx:include fx:id="another" source="AnotherFile.fxml" />
// ...
</items>
</SplitPane>
</center>
<top>
// ...
</top>
</BorderPane>
and in the "main controller":
public class MyMainController {
#FXML
private AnotherController anotherController ;
// ...
}
(the rule being that the field name is the value of the fx:id attribute with "Controller" appended). Here AnotherController is the controller class for AnotherFile.fxml.
Now you can, for example, expose the data you need to access in the "included controller":
public class AnotherController {
#FXML
private Label oneOfMyLabels ;
public StringProperty textProperty() {
return oneOfMyLabels.textProperty();
}
public final String getText() {
return textProperty().get();
}
public final setText(String text) {
textProperty().set(text);
}
// ...
}
and then your main controller can do things like
anotherController.setText(...);
which will of course update the label. This preserves encapsulation, so that if you choose to use another control instead of a label, those changes do not have to propagate outside of the immediate controller.

Resources