Wrap the container that holds the text field(s) in an AnchorPane
. Add the ListView
to the AnchorPane
after the text field container (so it stays on top). Then you need to position the ListView
appropriately relative to the text field when you make it visible; I think the best way to do this is to first convert the bounds of the text field from local coordinates to Scene
coordinates, then convert those bounds to the coordinates relative to the AnchorPane
.
Here's an SSCCE:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class SuggestionList extends Application {
@Override
public void start(Stage primaryStage) {
AnchorPane root = new AnchorPane();
ListView<String> suggestionBox = new ListView<>();
suggestionBox.getItems().addAll("Here", "Are", "Some", "Suggestions");
suggestionBox.setMaxHeight(100);
suggestionBox.setVisible(false);
// Grid pane to hold a bunch of text fields:
GridPane form = new GridPane();
for (int i=0; i<10; i++) {
form.addRow(i, new Label("Enter Text:"), createTextField(suggestionBox));
}
// just move the grid pane a little to test suggestion box positioning:
AnchorPane.setLeftAnchor(form, 20.0);
AnchorPane.setRightAnchor(form, 20.0);
AnchorPane.setTopAnchor(form, 20.0);
AnchorPane.setBottomAnchor(form, 20.0);
// allows focus on grid pane, so user can click on it to remove focus from text field.
form.setFocusTraversable(true);
root.setPadding(new Insets(20));
root.getChildren().addAll(form, suggestionBox);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private TextField createTextField(ListView<String> suggestionBox) {
TextField textField = new TextField();
ChangeListener<String> selectionListener = (obs, oldItem, newItem) -> {
if (newItem != null) {
textField.setText(newItem);
}
};
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
suggestionBox.setVisible(true);
// compute bounds of text field relative to suggestion box's parent:
Parent parent = suggestionBox.getParent(); // (actually the anchor pane)
Bounds tfBounds = textField.getBoundsInLocal();
Bounds tfBoundsInScene = textField.localToScene(tfBounds);
Bounds tfBoundsInParent = parent.sceneToLocal(tfBoundsInScene);
// position suggestion box:
suggestionBox.setLayoutX(tfBoundsInParent.getMinX());
suggestionBox.setLayoutY(tfBoundsInParent.getMaxY());
suggestionBox.setPrefWidth(tfBoundsInParent.getWidth());
suggestionBox.getSelectionModel().selectedItemProperty().addListener(selectionListener);
} else {
suggestionBox.setVisible(false);
suggestionBox.getSelectionModel().selectedItemProperty().removeListener(selectionListener);
}
});
textField.setOnAction(event -> {
suggestionBox.setVisible(false);
suggestionBox.getSelectionModel().selectedItemProperty().removeListener(selectionListener);
});
return textField ;
}
public static void main(String[] args) {
launch(args);
}
}
You might be able to use similar positional tricks and just add it to the same scene, with managed
set to false.