I'm trying to add a key event handler to an editable ComboBox in a simple JavaFX application. Since the Scene Builder doesn't provide access to the TextField in the ComboBox, I have to add the event handler in code.
Here is my attempt to add the handler.
Main class
package sample;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.stage.Stage;
public class Main extends Application {
@FXML
private ComboBox combo;
@Override
public void start(Stage primaryStage) throws Exception{
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root = loader.load();
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 400, 300));
primaryStage.show();
Controller c = loader.getController();
combo.getEditor().setOnKeyTyped(c::handleComboKeyPress);
}
public static void main(String[] args) {
launch(args);
}
}
Controller class
package sample;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.input.KeyEvent;
public class Controller {
@FXML
private ComboBox combo;
public void handleComboKeyPress(KeyEvent ke)
{
System.out.print("key press. "); // debugging
String query = combo.getEditor().getText();
System.out.println(query); // debugging
}
}
FXML (sample.fxml)
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.layout.Pane?>
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<children>
<ComboBox fx:id="combo" editable="true" layoutX="75.0" layoutY="34.0" prefWidth="150.0" promptText="City, State" visibleRowCount="5" />
</children>
</Pane>
This crashes with a null pointer exception on the last line of the start method. The problem is combo doesn't have a value yet because (I assume) the FXML loader runs in a separate thread and isn't finished by the time my code tries to call getEditor.
What is a more appropriate way to set the event handler?
Edit: added complete source code
comboand where it is initialized? - acarlsteinFXMLLoaderis aware of theClassof your controller; in your case, from the fully qualified name specified byfx:controller. When you load the FXML file it creates an instance of the controller for you. Later, theFXMLLoaderobtains theFields of theClassand searches them for ones whose names match thefx:ids. When a match is found it attempts to reflectively set theFieldto the object created from the FXML element. This injection only happens with the controller instance — theFXMLLoaderis not aware of every object instance in your application. - Slaw@FXMLannotation is only necessary when the field or method is non-public. The annotation is basically just telling theFXMLLoaderit's okay to try and access the non-public member. Also, you hypothesized your issue was related to threads—it is not. Everything theFXMLLoaderdoes happens on the thread that calledload. In other words, the code you've shown is sequential. For some more about the fundamentals of FXML, read Introduction to FXML. - Slaw