Creating a Autocomplete search form in javafx - java

To get an idea of what I want
When the textfield is clicked, the dropdown appears with suggestions that are filtered out as the user types in the text field. The height of the box should also adjust real-time to either contain all of the items, or a maximum of 10 items.
I managed to get this somewhat working using a ComboBox, but it felt a bit rough around the edges and it didn't seem possible to do what I wanted (The dropdown doesn't resize unless you close it and re-open it).
New idea, have a text field and then show a VBox of buttons as the dropdown. The only problem is that I don't know how to position the dropdown so that it doest stay in the noral flow so it can overlay any exisiting elements below the text field. Any ideas?

Please consider this Example, you can take the idea and apply it to your project.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class SearchFormJavaFX extends Application{
#Override
public void start(Stage ps) throws Exception {
String[] options = {"How do I get a passport",
"How do I delete my Facebook Account",
"How can I change my password",
"How do I write some code in my question :D"};
// note that you don't need to stick to these types of containers, it's just an example
StackPane root = new StackPane();
GridPane container = new GridPane();
HBox searchBox = new HBox();
////////////////////////////////////////////////////
TextField text = new TextField();
// add a listener to listen to the changes in the text field
text.textProperty().addListener((observable, oldValue, newValue) -> {
if(container.getChildren().size()>1){ // if already contains a drop-down menu -> remove it
container.getChildren().remove(1);
}
container.add(populateDropDownMenu(newValue, options),0,1); // then add the populated drop-down menu to the second row in the grid pane
});
// those buttons just for example
// note that you can add action listeners to them ..etc
Button close = new Button("X");
Button search = new Button("Search");
searchBox.getChildren().addAll(text,close,search);
/////////////////////////////////////////////////
// add the search box to first row
container.add(searchBox, 0, 0);
// the colors in all containers only for example
container.setBackground(new Background(new BackgroundFill(Color.GRAY, null,null)));
////////////////////////////////////////////////
root.getChildren().add(container);
Scene scene = new Scene(root, 225,300);
ps.setScene(scene);
ps.show();
}
// this method searches for a given text in an array of Strings (i.e. the options)
// then returns a VBox containing all matches
public static VBox populateDropDownMenu(String text, String[] options){
VBox dropDownMenu = new VBox();
dropDownMenu.setBackground(new Background(new BackgroundFill(Color.GREEN, null,null))); // colors just for example
dropDownMenu.setAlignment(Pos.CENTER); // all these are optional and up to you
for(String option : options){ // loop through every String in the array
// if the given text is not empty and doesn't consists of spaces only, as well as it's a part of one (or more) of the options
if(!text.replace(" ", "").isEmpty() && option.toUpperCase().contains(text.toUpperCase())){
Label label = new Label(option); // create a label and set the text
// you can add listener to the label here if you want
// your user to be able to click on the options in the drop-down menu
dropDownMenu.getChildren().add(label); // add the label to the VBox
}
}
return dropDownMenu; // at the end return the VBox (i.e. drop-down menu)
}
public static void main(String[] args) {
launch();
}
}

What you're trying to do has already been implemented, and is included in ControlsFx. It's open source, and I think it would suit you need. It looks some what like this
You can even add custom nodes to it, so that cross can be done too.

public void pushEmails(TextField Receptient) {
ArrayList<CustomTextField> list = new ArrayList<>();
for (int i = 0; i < Sendemails.size(); i++) {
CustomTextField logo=new CustomTextField(Sendemails.get(i));
ImageView logoView=new ImageView(new Image("/Images/Gmail.png"));
logo.setRight(logoView);
list.add(logo);
}
TextFields.bindAutoCompletion(Receptient, list);
}

Related

How to maniupulate the population of the Controls in JavaFX?

I have this view designed via Scene Builder for JavaFX. There are 4 ComboBox in it. I would like to have the possibility to have something to let the user choose how many and which ComboBox use.
For example, my aim is having 3 modes:
allow the user to use all the 4 ComboBox;
allow the user to use only one ComboBox and let him choose it;
allow the user to use only two ComboBox and let them choose the preferred combination of the four Controls
Any design or idea (and its implementation) are well welcomed since I am not having a very good solution at this moment. I was thinking something like using the CheckBox element near to every ComboBox to enable or disable them, but anyway it is not very good. Also I was thinking about putting 3 Buttons to select the 3 modes and dynamically populate my Container, but I do not know where to start with the implementation.
If you want to let the user select a specific ComboBox, you can enable it using the JavaFX function setDisable() that is on all classes that inherit from the Node class.
(See difference between: setDisabled() vs setDisable())
In the case below, I bind the disabledProperty() to the inverse selectedProperty() on each CheckBox. This way you can select specific ComboBoxes to choose from. Hopefully this will get you started on seeing how JavaFX bindings work.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Test extends Application
{
public static void main(String[] args){
launch(args);
}
#Override
public void start (Stage primaryStage) throws Exception {
VBox vBox = new VBox();
HBox hBox1 = generateComboBoxHBox();
HBox hBox2 = generateComboBoxHBox();
HBox hBox3 = generateComboBoxHBox();
HBox hBox4 = generateComboBoxHBox();
vBox.getChildren().addAll(hBox1, hBox2, hBox3, hBox4);
primaryStage.setScene(new Scene(vBox));
primaryStage.show();
}
// Create 4 of the same HBoxes for an example. Each HBox has a checkbox and combobox
private HBox generateComboBoxHBox(){
HBox hBox = new HBox();
CheckBox checkBox = new CheckBox();
ComboBox<String> comboBox = new ComboBox<>(FXCollections.observableArrayList("Option1", "Option2", "Option3", "Option4"));
comboBox.disableProperty().bind(checkBox.selectedProperty().not());
hBox.getChildren().addAll(checkBox, comboBox);
return hBox;
}
}

How do I bind CheckMenuItem.selectedProperty() to another ObservableValue?

I want to bind a CheckMenuItem's selectedProperty to another observable value, like cmi.selectedProperty().bind(myObs). However, this is not possible, since the framework sets the selection property when the check menu item is clicked (see line 1394 of ContextMenuContent.java).
Is there a way to intercept the click—so that I can do my own custom processing—and still bind the selection property to another observable?
I suppose I'm thinking of the click as a request to update some state. The user clicks the menu item, then the program attempts to change some state accordingly, and the selection changes if the state successfully updated. Under 'normal' conditions, the check should toggle upon every click; however, if something bad happens, I'd prefer that the check doesn't toggle and instead reflects the true state of the program.
One way to do this (without getting into writing a skin for the menu item) is to roll your own menu item with a graphic. You can just use a region for the graphic and steal the CSS from the standard modena stylesheet. Then bind the visible property of the graphic to the observable value, and toggle the observable value in the menu item's action handler:
import java.util.Random;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
public class VetoableMenuItemWithCheck extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
MenuBar menuBar = new MenuBar() ;
Menu choices = new Menu("Choices");
// observable boolean value to which we're going to bind:
BooleanProperty selected = new SimpleBooleanProperty();
// graphic for displaying checkmark
Region checkmark = new Region();
checkmark.getStyleClass().add("check-mark");
// bind visibility of graphic to observable value:
checkmark.visibleProperty().bind(selected);
MenuItem option = new MenuItem("Option", checkmark);
choices.getItems().add(option);
Random rng = new Random();
// when menu item action occurs, randomly fail (with error alert),
// or update boolean property (which will result in toggling check mark):
option.setOnAction(e -> {
if (rng.nextDouble() < 0.25) {
Alert alert = new Alert(AlertType.ERROR, "I'm sorry Dave, I'm afraid I can't do that", ButtonType.OK);
alert.showAndWait();
} else {
selected.set(! selected.get());
}
});
menuBar.getMenus().add(choices);
root.setTop(menuBar);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add("check-menu.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
and
check-menu.css:
.check-mark {
-fx-background-color: -fx-mark-color;
-fx-shape: "M0,5H2L4,8L8,0H10L5,10H3Z";
-fx-scale-shape: false;
-fx-padding: 0em 0.11777em 0em 0em;
}
There may be a simpler approach, but this seems not too bad.
A version for a vetoable radio menu item could follow the same basic idea, but with
ObjectProperty<MenuItem> selectedItem = new SimpleObjectProperty<>();
and then for each menu item do
checkmark.visibleProperty().bind(selectedItem.isEqualTo(option));
option.setOnAction(e -> {
if (successful()) {
selectedItem.set(option);
}
});

JavaFX: Hardcode a "Ctrl+C" keypress

I have a JavaFX application that has various TextField widgets in the main frame. I have a MenuBar that includes the MenuItem objects "Copy" and "Paste" like a standard production application would have. Since any or none of the various TextField objects could be selected at any given time, it seems easier to just hardcode a "Ctrl+C" or "Ctrl+V" key press in the setOnAction events of the "Copy" and "Paste" MenuItem objects rather than use a Clipboard object and loop iterating through all TextFields to find the highlighted text (if any).
Is there a way to hardcode this key press action in Java? I looked into the KeyCombination class but it does not actually trigger the action described by the given key combination.
I think by "Since any or none of the various TextField objects could be selected at any given time" you are referring to which (if any) text field has the keyboard focus.
You can easily get this information from the scene: just do
Node focusOwner = scene.getFocusOwner();
if (focusOwner instanceof TextField) {
TextField textField = (TextField) focusOwner ;
String selectedText = textField.getSelectedText();
// ...
}
Note also that TextInputControl defines a copy() method that copies the selected text to the system clipboard. (Similarly, there's a paste() method too.) So you can leverage those to make the functionality easy.
Here's a SSCCE:
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FocusMenuTest extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
VBox textFields = new VBox(5, new TextField("One"), new TextField("Two"), new TextField("Three"));
MenuBar menuBar = new MenuBar();
Menu edit = new Menu("Edit");
MenuItem copy = new MenuItem("Copy");
copy.setOnAction(e -> {
Node focusOwner = menuBar.getScene().getFocusOwner();
if (focusOwner instanceof TextInputControl) {
((TextInputControl)focusOwner).copy();
}
});
MenuItem paste = new MenuItem("Paste");
paste.setOnAction(e -> {
Node focusOwner = menuBar.getScene().getFocusOwner();
if (focusOwner instanceof TextInputControl) {
((TextInputControl)focusOwner).paste();
}
});
menuBar.getMenus().add(edit);
edit.getItems().addAll(copy, paste);
root.setCenter(textFields);
root.setTop(menuBar);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

How to programmatically set a string value in a JavaFX ComboBox

Basically, here is what I need:
I have a JavaFX ComboBox, and it is set to Editable. Since it is editable, there is a little text field in there where someone can enter in a String. I want to use previously generated data to populate that little text field. How do I accomplish this?
enterSchoolName.setSelectionModel((SingleSelectionModel<String>) FXCollections.observableArrayList(studentData.getSchoolName()));
This is all i have in the way of relevant code and an "attempt" at a solution.
You can set the data items of a ComboBox in the constructor:
ObservableList<String> data = FXCollections.observableArrayList("text1", "text2", "text3");
ComboBox<String> comboBox = new ComboBox<>(data);
or later:
comboBox.setItems(data);
To select a data item, you can select the appropriate index in the SelectionModel or the item itself:
comboBox.getSelectionModel().select(0);
comboBox.getSelectionModel().select("text1");
It's also possible to set a value to the combobox editor, which is not contained in the underlying datamodel:
comboBox.setValue("textXXX");
The "little text field" in a editable ComboBox is known as the editor of the ComboBox. And it's a normal TextField object. To access that object, you need to use the method ComboBox#getEditor(). This way you can use the methods of the TextField class. If I understand you correctly, all you want to do is set the text of that TextField.
This is done by doing comboBox.getEditor().setText(text) or comboBox.setValue(text). Both of these methods will set the text of the ComboBox.
But there's a difference when you want to fetch that text. ComboBox#getValue() ComboBox#getEditor()#getText() doesn't necessarily return the same value.
Consider the following example:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TestComboBox extends Application {
#Override
public void start(Stage stage) {
ComboBox<String> comboBox = new ComboBox<String>();
comboBox.setEditable(true);
comboBox.setValue("Test");
comboBox.getItems().addAll("Test", "Test2", "Test3");
VBox content = new VBox(5);
content.getChildren().add(comboBox);
content.setPadding(new Insets(10));
GridPane valueGrid = new GridPane();
Label cbValue = new Label();
cbValue.textProperty().bind(comboBox.valueProperty());
Label cbText = new Label();
cbText.textProperty().bind(comboBox.getEditor().textProperty());
valueGrid.add(new Label("ComboBox value: "), 0, 0);
valueGrid.add(new Label("ComboBox text: "), 0, 1);
valueGrid.add(cbValue, 1, 0);
valueGrid.add(cbText, 1, 1);
content.getChildren().add(valueGrid);
stage.setScene(new Scene(content));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
If you change the text in the ComboBox by chosing an alternative in the list, both ComboBox#valueProperty() and ComboBox#getEditor#textProperty() changes. But as you can see if you type something into the ComboBox, only the textProperty changes.
So use whichever method you want when you set the text of the ComboBox, but be aware of the difference when you want to retrieve that text.

JavaFX: Make node take no space, but let its parent decide its position

I have a TextField and a ListView. As the user types in the TextField, suggestions come up in the ListView:
When the TextField is empty, the ListView disappears, by setting the visible and managed properties to false.
However, when the user starts to type, the ListView takes up space and pushes everything down. Using .setManaged(false) allows it not to take up any space, but it doesn't display anymore, as I haven't defined a position for it. I have tried setting the layoutX and layoutY of the search list, but it still doesn't display.
Ideally I'd like the ListView's position to be affected by the layout but not to take up any space.
Any ideas?
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.

Categories

Resources