How to make it so there are no duplicates in JavaFX ComboBox - java

In this program, you can add words to the ComboBox using a textfield. How would you make it so the program will not accept any words that are already in the ComboBox? I tried to make it so if the textfield input is equal to something in the ComboBox then it shouldn't add it but it will not work.
package gps_destinations_controller;
import gps_destinations_model.Model;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.control.TextField;
public class Controller implements Initializable
{
#FXML TextField destinationInput;
#FXML ComboBox<String> destinationList;
private SingleSelectionModel<String> selectionModel;
private Model model;
#Override
public void initialize(URL url, ResourceBundle rb)
{
model = new Model();
destinationList.getItems().addAll(model.getDestinations());
selectionModel = destinationList.getSelectionModel();
selectionModel.select(model.getSelectedIndex());
System.out.println(destinationList.getItems());
}
#FXML protected void addDestination()
{
String input = destinationInput.getText();
if(!destinationList.getItems().equals(input))
{
destinationList.getItems().add(input);
}
model.addDestination(input);
destinationInput.clear();
}
#FXML protected void itemSelected( ActionEvent event )
{
int index = selectionModel.getSelectedIndex();
model.updateSelection(index);
}
}

You're comparing the items list itself to the element you want to add. This always results in false and the item is always added. You need to use contains to check, if an element is already in the list:
if(!destinationList.getItems().contains(input)) {
destinationList.getItems().add(input);
}

Related

JavaFX: Dynamically update Menu while showing

I would like to add a list of options to a JavaFX menu. The order of the options should be modified while the menu is showing (I will use fuzzy matching but the method for that is irrelevant for my issue). I can mimic such a behavior with CustomMenuItems that contain TextField and ListView, respectively:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.ListView;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuButton;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MWE extends Application {
#Override
public void start(Stage primaryStage) {
final Menu menu = new Menu("MENU");
final List<String> options = Arrays.asList(
"AbC",
"dfjksdljf",
"skdlfj",
"stackoverflow");
final StringProperty currentSelection = new SimpleStringProperty(null);
final TextField fuzzySearchField = new TextField(null);
final CustomMenuItem fuzzySearchItem = new CustomMenuItem(fuzzySearchField, false);
// TODO unfortunately we seem to have to grab focus like this!
fuzzySearchField.addEventFilter(MouseEvent.MOUSE_MOVED, e->{fuzzySearchField.requestFocus(); fuzzySearchField.selectEnd();});
final ObservableList<String> currentMatches = FXCollections.observableArrayList();
// just some dummy matching here
fuzzySearchField.textProperty().addListener((obs, oldv, newv) -> currentMatches.setAll(options.stream().filter(s -> s.toLowerCase().contains(newv)).collect(Collectors.toList())));
final ListView<String> lv = new ListView<>(currentMatches);
lv.addEventFilter(MouseEvent.MOUSE_MOVED, e -> lv.requestFocus());
final CustomMenuItem lvItem = new CustomMenuItem(lv, false);
menu.getItems().setAll(fuzzySearchItem, lvItem);
fuzzySearchField.textProperty().addListener((obs, oldv, newv) -> currentSelection.setValue(currentMatches.size() > 0 ? currentMatches.get(0) : null));
fuzzySearchField.setText("");
menu.setOnShown(e -> fuzzySearchField.requestFocus());
final MenuButton button = new MenuButton("menu");
button.getItems().setAll(menu);
Platform.runLater(() -> {
final Scene scene = new Scene(button);
primaryStage.setScene(scene);
primaryStage.show();
});
}
}
However having a ListView inside a menu structure feels strange. That's why I tried to use MenuItems instead of the ListView:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.ListView;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MWE2 extends Application {
#Override
public void start(Stage primaryStage) {
final Menu menu = new Menu("MENU");
final List<String> options = Arrays.asList(
"AbC",
"dfjksdljf",
"skdlfj",
"stackoverflow");
final StringProperty currentSelection = new SimpleStringProperty(null);
final TextField fuzzySearchField = new TextField(null);
final CustomMenuItem fuzzySearchItem = new CustomMenuItem(fuzzySearchField, false);
// TODO unfortunately we seem to have to grab focus like this!
fuzzySearchField.addEventFilter(MouseEvent.MOUSE_MOVED, e->{fuzzySearchField.requestFocus(); fuzzySearchField.selectEnd();});
final ObservableList<String> currentMatches = FXCollections.observableArrayList();
// just some dummy matching here
fuzzySearchField.textProperty().addListener((obs, oldv, newv) -> currentMatches.setAll(options.stream().filter(s -> s.toLowerCase().contains(newv)).collect(Collectors.toList())));
currentMatches.addListener((ListChangeListener<String>)change -> {
List<MenuItem> items = new ArrayList<>();
items.add(fuzzySearchItem);
currentMatches.stream().map(MenuItem::new).forEach(items::add);
System.out.println("Updating menu items!");
menu.getItems().setAll(items);
});
fuzzySearchField.textProperty().addListener((obs, oldv, newv) -> currentSelection.setValue(currentMatches.size() > 0 ? currentMatches.get(0) : null));
fuzzySearchField.setText("");
menu.setOnShown(e -> fuzzySearchField.requestFocus());
final MenuButton button = new MenuButton("menu");
button.getItems().setAll(menu);
Platform.runLater(() -> {
final Scene scene = new Scene(button);
primaryStage.setScene(scene);
primaryStage.show();
});
}
}
In that example, the menu does not get updated while showing, even though I can see "Updating menu items!" printed to the console, so the items of menu are being updated. The menu on the screen, however, does not change.
Is there a way of requesting a repaint of the menu?
Related questions:
JavaFX: Update menu sub-items everytime menu shown The solution seems incorrect, Menu does not seem to have a method setOnMouseEntered
Adding dynamic entries to Menu in JavaFX does not update the menu while it is showing (if I understand correctly)
I followed the suggestions by #Enigo and #Jesse_mw and used a custom node. Instead of a ListView, I decided to go with a VBox that contains only Labels because I only need basic functionality and do not want to have additional confusing handlers or highlighting. Also note that #kleopatra pointed out, that dynamic updates of items works out of the box for ContextMenu (and a potential bug in Menu), which is not an adequate choice for my use-case, unfortunately.
Here is my minimum working example code:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuButton;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MWE extends Application {
private static Label label(final String text) {
final Label label = new Label(text);
label.addEventFilter(MouseEvent.MOUSE_MOVED, e -> label.requestFocus());
label.setMaxWidth(200);
final Background background = label.getBackground();
label.setOnMouseEntered(e -> label.setBackground(new Background(new BackgroundFill(Color.GRAY.brighter(), CornerRadii.EMPTY, Insets.EMPTY))));
label.setOnMouseExited(e -> label.setBackground(background));
// Do something on mouse press; in real world scenario, also hide menu
label.setOnMousePressed(e -> {
if (e.isPrimaryButtonDown()) {
System.out.println(label.getText());
e.consume();
}
});
return label;
}
#Override
public void start(Stage primaryStage) {
final Menu menu = new Menu("MENU");
final List<String> options = Arrays.asList(
"AbC",
"dfjksdljf",
"skdlfj",
"stackoverflow","ssldkfjsdaf", "sjsdlf", "apple juice", "banana", "mango", "sdlfkjasdlfjsadlfj", "lkjsdflsdfj",
"stackoverflow","ssldkfjsdaf", "sjsdlf", "apple juice", "banana", "mango", "sdlfkjasdlfjsadlfj", "lkjsdflsdfj",
"stackoverflowstackoverflowstackoverflowstackoverflowstackoverflowstackoverflow","ssldkfjsdaf", "sjsdlf", "apple juice", "banana", "mango", "sdlfkjasdlfjsadlfj", "lkjsdflsdfj");
final StringProperty currentSelection = new SimpleStringProperty(null);
final TextField fuzzySearchField = new TextField(null);
final CustomMenuItem fuzzySearchItem = new CustomMenuItem(fuzzySearchField, false);
fuzzySearchItem.setDisable(true);
// TODO unfortunately we seem to have to grab focus like this!
fuzzySearchField.addEventFilter(MouseEvent.MOUSE_MOVED, e->{fuzzySearchField.requestFocus(); fuzzySearchField.selectEnd();});
final ObservableList<String> currentMatches = FXCollections.observableArrayList();
// just some dummy matching here
fuzzySearchField.textProperty().addListener((obs, oldv, newv) -> currentMatches.setAll(options.stream().filter(s -> s.toLowerCase().contains(newv)).collect(Collectors.toList())));
final VBox labels = new VBox();
currentMatches.addListener((ListChangeListener<String>) change -> labels.getChildren().setAll(currentMatches.stream().map(MWE::label).collect(Collectors.toList())));
final CustomMenuItem labelItem = new CustomMenuItem(labels, false);
menu.getItems().setAll(fuzzySearchItem, labelItem);
fuzzySearchField.textProperty().addListener((obs, oldv, newv) -> currentSelection.setValue(currentMatches.size() > 0 ? currentMatches.get(0) : null));
fuzzySearchField.setText("");
menu.setOnShown(e -> fuzzySearchField.requestFocus());
final MenuButton button = new MenuButton("menu");
button.getItems().setAll(menu);
Platform.runLater(() -> {
final Scene scene = new Scene(button);
primaryStage.setScene(scene);
primaryStage.show();
});
}
}
To properly update lists dynamically in JavaFX you can use Binding with an observable list like I did so in your code. The only problem with the following code is it will not work as expected due the functionality of the Menu class, every time the list is updated the menu will hide. I think you should just style a list-view like discussed in the comments, and use the following binding again as it applies to all views that use Observable Lists I believe.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.lang.management.PlatformManagedObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MWE2 extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
final Menu menu = new Menu("MENU");
final MenuButton button = new MenuButton("menu");
final List<String> options = Arrays.asList(
"AbC",
"dfjksdljf",
"skdlfj",
"stackoverflow");
final StringProperty currentSelection = new SimpleStringProperty(null);
final TextField fuzzySearchField = new TextField(null);
final CustomMenuItem fuzzySearchItem = new CustomMenuItem(fuzzySearchField, false);
// TODO unfortunately we seem to have to grab focus like this!
fuzzySearchField.addEventFilter(MouseEvent.MOUSE_MOVED, e -> {
fuzzySearchField.requestFocus();
fuzzySearchField.selectEnd();
});
final ObservableList<String> currentMatches = FXCollections.observableArrayList();
// just some dummy matching here
fuzzySearchField.textProperty().addListener((obs, oldv, newv) -> {
currentMatches.setAll(options.stream().filter(s -> s.toLowerCase().contains(newv)).collect(Collectors.toList()));
});
//changed from ArrayList to ObservableArray
ObservableList<MenuItem> items = FXCollections.observableArrayList();
currentMatches.addListener((ListChangeListener<String>) change -> {
items.clear();//Clearing items to in-case of duplicates and NULL duplicates.
items.add(fuzzySearchItem);
currentMatches.stream().map(MenuItem::new).forEach(items::add);
System.out.println("Updating menu items!");
menu.getItems().setAll(items);
});
// Binding to Observable items.
Bindings.bindContent(menu.getItems(), items);
fuzzySearchField.textProperty().addListener((obs, oldv, newv) -> currentSelection.setValue(currentMatches.size() > 0 ? currentMatches.get(0) : null));
fuzzySearchField.setText("");
button.getItems().setAll(menu);
final Scene scene = new Scene(button);
primaryStage.setScene(scene);
primaryStage.show();
}
}

How to set the value of JFXcombobox without using index?

How to set the value of combobox from the elements present in that combobox in java/javafx. This is the code what i have done till now.
package LetMeTest;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXComboBox;
import java.net.URL;
import java.util.ArrayList;
import java.util.Observable;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
public class TryController implements Initializable {
#FXML
private JFXComboBox<?> cb;
#FXML
private JFXButton btnGet;
#FXML
private JFXButton btnSet;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb)
{
ObservableList oo=FXCollections.observableArrayList("b","a");
cb.setItems(oo);
}
#FXML
private void doGetItem(ActionEvent event)
{
System.out.println("Selected Item="+cb.getSelectionModel().getSelectedItem());
}
#FXML
private void doSetItem(ActionEvent event)
{
cb.setValue("b");
}
}
I am trying to do it using ObservableList. Tell me there is any other possibility to do it.

javafx communication between controller and main

Why does the following code always show null in the console when I want to get the controller ?
RenewCardFXML2_controller controller=loader.getController();
and the console prints null when i press the button.
I have two controllers and want to use textfield from the main app(membershipcards)-txt_numberOfCard_GENERAL inside the second fxml file whick has its own controller.
txt_numberOfCard_GENERAL.getText command from seccond controller and use its value.
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package membershipcards;
import java.io.IOException;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.SplitPane;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import membershipcards.RenewCardFXML2_controller;
/**
*
* #author Primary
*/
public class mainGUIController implements Initializable {
#FXML
private ImageView logoimg;
#FXML
private SplitPane splitMenuContent;
#FXML
private Button btnCards;
#FXML
private Button btnRenewCard;
#FXML
private Button bntNewClient;
#FXML
private Button btnEditCard;
#FXML
private Button btnStatistics;
#FXML
private AnchorPane detailsPane;
#FXML
private Button btnCardN;
#FXML
public TextField txt_numberOfCard_GENERAL;
#FXML
public TextField txt_memberName_GENERAL;
#Override
public void initialize(URL url, ResourceBundle rb) {
}
#FXML
private void loadCardsFXML1(ActionEvent event) {
try {
detailsPane = (AnchorPane) FXMLLoader.load(getClass().getResource("CardsFXML1.fxml"));
} catch (IOException ex) {
Logger.getLogger(mainGUIController.class.getName()).log(Level.SEVERE, null, ex);
}
splitMenuContent.getItems().set(1, detailsPane);
}
#FXML
private void loadRenewCardFXML2(ActionEvent event)throws IOException {
FXMLLoader loader = new FXMLLoader();
detailsPane = (AnchorPane) loader.load(getClass().getResource("RenewCardFXML2.fxml"));
splitMenuContent.getItems().set(1, detailsPane);
RenewCardFXML2_controller controller=loader.getController();
controller.setMGC(this);
System.out.println(controller);
}
}
The FXMLLoader.load(URL) method you are calling is a static method. Consequently, you have not called load on the FXMLLoader instance you created, and since that instance hasn't loaded the FXML, it has not initialized its controller field.
(A good IDE will give you a warning on your loader.load(...) line about calling a static method from a non-static context, or something similar. Eclipse, for example, says "The static method load(URL) should be accessed in a static way.")
The following will work:
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("RenewCardFXML2.fxml"));
detailsPane = loader.load();
splitMenuContent.getItems().set(1, detailsPane);
RenewCardFXML2_controller controller=loader.getController();
Note that you can reduce the first two lines of that code block to a single line
FXMLLoader loader = new FXMLLoader(getClass().getResource("RenewCardFXML2.fxml"));
detailsPane = loader.load();

Trigger action on default value Combo Box JavaFX

I am trying to trigger an action on default value of the combo box when it is initialized but I'm only able to find the method to trigger an action when the value is changed using method print() set in OnAction of ComboBox.
Here is my presenter of the FXML file:
package tre;
import java.net.URL;
import java.util.ResourceBundle;
import javax.swing.event.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
public class presenter implements Initializable {
public #FXML ComboBox<String> combo;
private final ObservableList<String> statistic = FXCollections.observableArrayList("Logged In", "Anonymous",
"Logged Off");
#Override
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
combo.setItems(statistic);
combo.setValue("Anonymous");
//TODO When Anonymous is displayed as default value in the combo box, it will print it.
}
public #FXML void print(){
String sel = combo.getSelectionModel().getSelectedItem();
System.out.println(sel);
}
}
Is there a solution for it? Thanks

How to iterate through a JavaFX-combobox?

My question is simple: How do I iterate throught a javafx-combobox? I tried so much and nothing works. The foreach-construct does not work and combobox does not even have methods for size and single object retrieval, eliminating the possibility to use a simple-for-loop. Am I missing something?
Yes you are missing..or maybe not. But, nevertheless, just return ObservableList of objects from your ComboBox and use for each to iterate through that collection. Here is an example:
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class ComboBoxExample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
GridPane gridPane = new GridPane();
Button button = new Button("Print items from ComboBox");
final ComboBox<String> comboBox = new ComboBox<String>();
//Add some items
comboBox.getItems().addAll("John","Jane","Laina");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
//To iterate through ComboBox
ObservableList<String> items = comboBox.getItems();
for(String item : items){
System.out.println(item.toString());
}
}
});
gridPane.add(comboBox,0,0);
gridPane.add(button,0,1);
primaryStage.setScene(new Scene(gridPane,320,280));
primaryStage.show();
}
}

Categories