简体   繁体   中英

Why doesn't JavaFX TextField listener work?

I want to add a listener to a textField in a window that appears when I press a button on a previous window. The problem is that the listener doesn't work at all. It doesn't detect any change.

public class WindowTwoController {

private Stage stage;

@FXML
private TextField imieTF = new TextField();


public void show() throws IOException {

    FXMLLoader loader = new FXMLLoader(getClass().getResource("winTwo.fxml"));
    Parent root = loader.load();

    stage = new Stage();
    stage.initModality(Modality.APPLICATION_MODAL);

    stage.setTitle("Sell Art");
    stage.setScene(new Scene(root, 500, 500));

    imieTF.textProperty().addListener((observable, oldValue, newValue) -> {
        System.out.println("textfield changed from " + oldValue + " to " + newValue);
    });

    stage.showAndWait();

}

I'm changing the value of the textField, but nothing is printed out to the console. The show method is called when a button is pressed on the previous window. Please help me. This is my winTwo.fxml:

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>

<GridPane alignment="center" hgap="10" prefHeight="441.0" prefWidth="500.0" vgap="10" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="windowTwo.WindowTwoController">
<columnConstraints>
    <ColumnConstraints />
</columnConstraints>
<rowConstraints>
    <RowConstraints />
</rowConstraints>
<children>
    <VBox prefHeight="354.0" prefWidth="455.0">
        <children>
            <Label alignment="CENTER" prefHeight="45.0" prefWidth="457.0" text="Sprzedaż dzieła">
                <font>
                    <Font size="30.0" />
                </font>
            </Label>
            <Separator prefWidth="200.0" />
            <GridPane prefHeight="139.0" prefWidth="455.0">
                <columnConstraints>
                    <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                    <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                </columnConstraints>
                <rowConstraints>
                    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                </rowConstraints>
                <children>
                    <Label alignment="CENTER" prefHeight="40.0" prefWidth="220.0" text="Imię">
                        <font>
                            <Font size="28.0" />
                        </font>
                    </Label>
                    <Label alignment="CENTER" prefHeight="40.0" prefWidth="220.0" text="Nazwisko" GridPane.rowIndex="1">
                        <font>
                            <Font size="28.0" />
                        </font>
                    </Label>
                    <TextField fx:id="imieTF" GridPane.columnIndex="1">
                        <GridPane.margin>
                            <Insets right="20.0" />
                        </GridPane.margin></TextField>
                    <TextField fx:id="nazwiskoTF" GridPane.columnIndex="1" GridPane.rowIndex="1">
                        <GridPane.margin>
                            <Insets right="20.0" />
                        </GridPane.margin>
                    </TextField>
                </children>
            </GridPane>
            <Separator prefWidth="200.0" />
            <Label alignment="CENTER" prefHeight="17.0" prefWidth="450.0" text="Klient powracający">
                <font>
                    <Font size="22.0" />
                </font>
            </Label>
            <Separator prefWidth="200.0" />
            <GridPane prefHeight="126.0" prefWidth="455.0">
                <columnConstraints>
                    <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                    <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                </columnConstraints>
                <rowConstraints>
                    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                </rowConstraints>
                <children>
                    <Label alignment="CENTER" prefHeight="40.0" prefWidth="220.0" text="Poziom Zaprzyjaźnienia" textFill="#00000080">
                        <font>
                            <Font size="19.0" />
                        </font>
                    </Label>
                    <ComboBox fx:id="cb" opacity="0.5" prefHeight="25.0" prefWidth="207.0" GridPane.columnIndex="1" />
                    <Button mnemonicParsing="false" prefHeight="25.0" prefWidth="175.0" text="Akceptuj" GridPane.rowIndex="1">
                        <GridPane.margin>
                            <Insets left="25.0" />
                        </GridPane.margin>
                    </Button>
                    <Button mnemonicParsing="false" prefHeight="25.0" prefWidth="175.0" text="Anuluj" GridPane.columnIndex="1" GridPane.rowIndex="1">
                        <GridPane.margin>
                            <Insets left="27.0" />
                        </GridPane.margin>
                    </Button>
                </children>
            </GridPane>
        </children>
    </VBox>
</children>
</GridPane>

You should add an anotation to the private member imieTF like so

public class WindowTwoController {
  @FXML
  private TextField imieTF;

  @FXML
  private void initialize() {
      imieTF.textProperty().addListener((observable, oldValue, newValue) -> 
       {
         System.out.println("textfield changed from " + oldValue + " to " + newValue);
       });
  }

  public static void show() {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("winTwo.fxml"));
    Parent root = loader.load();

    stage = new Stage();
    stage.initModality(Modality.APPLICATION_MODAL);

    stage.setTitle("Sell Art");
    stage.setScene(new Scene(root, 500, 500));
    stage.showAndWait();
  }
}

This should bind the TextField instance to the controller. But from what I can find out the FXML will create a new instance of WindowTwoController when the window belonging to the fxml file gets created.

Also see https://www.callicoder.com/javafx-fxml-form-gui-tutorial/ for a basic example on how this works.

Note that all manipulations of the textfield should be part of the JavaFX flow and cannot be done in a manual instance of WindowTwoController. Keep in mind that JavaFX will create it's own instance of WindowTwoController during the loader.load(); operation.

Your WindowTwoController#show() method is an instance method. This means you need to create an instance of WindowTwoController in order to call show() . However, when you use fx:controller in the FXML file the FXMLLoader will create its own instance of the controller class. The end result is that the TextField you add your listener to is not the same one the FXMLLoader created, injected, and added to the scene graph.

Note: You never want to instantiate fields that will be injected by the FXMLLoader (ie fields with matching fx:id attributes). Doing so is a waste of resources at best and leads to subtle bugs at worst. If removing the instantiation logic leads to a NullPointerException then there's something wrong with your setup.

One way to fix your problem is to not use fx:controller and instead set the controller manually:

@FXML private TextField imieTF; // don't instantiate manually

public void show() {
    FXMLLoader loader = new FXMLLoader(/* location */);
    loader.setController(this); // must set before calling load()
    Parent root = loader.load();

    // remaining code omitted
}

Another other option (and one I personally prefer over the above in this case) is to follow Gerben Jongerius's answer : Make show() a static method and move the add-listener-to- TextField logic into the initialize() method. If you're not aware, the initialize() method is invoked after all appropriate fields have been injected.


Just to show another way of doing things:

Note you don't have to add the ChangeListener in code; you can also register a property listener in the FXML file. This is documented by Introduction to FXML .

FXML:

<TextField onTextChange="#handleTextChange"/>

Controller method:

@FXML
private void handleTextChange(ObservableValue<? extends String> observable, 
                              String oldValue, String newValue) {
    // do something...
}

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