简体   繁体   中英

How to set ComboBox editor event handler

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

The start method needs access to the ComboBox variable declared in the Controller class. It's not sufficient to simply annotate a ComboBox variable with @FXML in the Main class.

I solved the problem by adding a getComboBox method to the Controller class. This will return the ComboBox instance referred to by the combo variable.

public ComboBox getComboBox()
{
    return combo;
}

In the Main class, use this method to get to the editor underlying the ComboBox:

Controller c = loader.getController();
c.getComboBox().getEditor().setOnTyped(c::handleComboKeyPress);

Here is the revised Main class:

public class Main extends Application {

    @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();
        c.getComboBox().getEditor().setOnKeyTyped(c::handleComboKeyPress);
    }

Better Solution

As suggested by @Slaw below, an alternative (and better) solution is to set the keyTyped handler in the Controller. Use an initialize method which gets automatically called by the loader if it exists.

public void initialize()
{
    combo.getEditor().setOnKeyTyped(this::handleComboKeyPress);
}

Here is the entire Controller class (minus the imports):

public class Controller {
    @FXML
    private ComboBox combo;

    public void handleComboKeyPress(KeyEvent ke)
    {
        // Do stuff
        System.out.println("key pressed.");
    }

    public void initialize()
    {
        combo.getEditor().setOnKeyTyped(this::handleComboKeyPress);
    }
}

Yet another option is to hook the event handler directly in the fxml. Not entirely certain if this is safe in all contexts (not a fxml gal, just interested :) - but the fx:reference seems to allow access to (arbitrary?) properties of elements.

The following snippet sets the eventHandler for keyTyped to the combo's editor:

<ComboBox fx:id="combo" editable="true" />
<fx:reference source="combo.editor" onKeyTyped="#handleTyped" /> 

Which relieves the controller from coding anything except the handler method:

@FXML 
private ComboBox<String> combo;

@FXML 
private void handleTyped(KeyEvent ev) {
    System.out.println("ev: " + ev);
}

Update : this seems to not work in fx8 (thanks, Matt for the heads up!) - worksforme in fx11 (didn't test any other)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM