JavaFX ComboBox working with delay - sqlite

So Im having Database, which contains Table with few parameters, one of those is "type" (TEXT). Im filling ComboBox with this "type" with this method:
public void loadTypefromDB()
{
types = FXCollections.observableArrayList();
try{
ResultSet rs = conn.createStatement().executeQuery("SELECT type FROM Products");
while(rs.next()){
String product = rs.getString("type");
types.add(product);
}
}
catch (SQLException e) {
e.printStackTrace();
}
choiceBox.setItems(types);
}
then I'm using this ComboBox to show all items of selected type in TableView.
public void choiceType(ActionEvent event)
{
choiceBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
loadTypeDataFromDB(newValue.toString());
}
});
}
and here is loadTypeDataFromDB method which executes query with selected parameter
public void loadTypeDataFromDB(String type){
products = FXCollections.observableArrayList();
try {
PreparedStatement pst = conn.prepareStatement("SELECT name, kcal, protein, carb, fat FROM Products WHERE type=?");
pst.setString(1, type);
ResultSet rs = pst.executeQuery();
while(rs.next()){
products.add(new productData(rs.getString("name"), rs.getString("kcal"), rs.getString("protein"), rs.getString("carb"), rs.getString("fat")));
}
}
catch (SQLException e) {
e.printStackTrace();
}
colProduct.setCellValueFactory(new PropertyValueFactory<productData, String>("name"));
colKcal.setCellValueFactory(new PropertyValueFactory<productData, String>("kcal"));
colProtein.setCellValueFactory(new PropertyValueFactory<productData, String>("protein"));
colCarbs.setCellValueFactory(new PropertyValueFactory<productData, String>("carb"));
colFat.setCellValueFactory(new PropertyValueFactory<productData, String>("fat"));
tableProduct.setItems(null);
tableProduct.setItems(products);
}
It ALMOST works correctly. I run my app, choose one of types from ComboBox and nothing happens (TableView stays clear). Then I choose other type from this ComboBox and suddenly it shows items of this type in TableView and from now on, I can display all types I want, everything works. So it looks like first choice from ComboBox is null (I dont get any exceptions tho). After first choice everything starts to work correctly...

Your ChangeListener setup seems dubious to me, and likely the source of your problems (though, I can't know for certain without seeing more code):
public void choiceType(ActionEvent event)
{
choiceBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
loadTypeDataFromDB(newValue.toString());
}
});
}
Where do you call this method? It takes an ActionEvent as the input; are you not setting up your listener until after you receive an event? Because that would certainly account for why you aren't seeing the first one.
Consider setting up the listener after adding the products (better still, where you create the ComboBox), and see if that helps:
public void loadTypefromDB()
{
types = FXCollections.observableArrayList();
try{
ResultSet rs = conn.createStatement().executeQuery("SELECT type FROM Products");
while(rs.next()){
String product = rs.getString("type");
types.add(product);
}
}
catch (SQLException e) {
e.printStackTrace();
}
choiceBox.setItems(types);
choiceBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
loadTypeDataFromDB(newValue.toString());
}
});
}

Related

Bind CheckBoxTableCell to BooleanBinding

I want to bind a CheckBox in a TableViewCell to a BooleanBinding. The following sample consists of a TableView with a column name and isEffectiveRequired. The checkbox in the column is bound to the Expression:
isRequired.or(name.isEqualTo("X"))
So an item is "effectivly required" when the item in the row is required OR the name is an X, then the expression should be true.
Unfortunately the CheckBox does not reflect the change. For debugging I added a textfield, showing the nameProperty, requiredProperty and the computed effectiveRequiredProperty.
Interestingly when returning just the isRequiredProperty instead of the binding the checkbox works.
public ObservableBooleanValue effectiveRequiredProperty() {
// Bindings with this work:
// return isRequired;
// with this not
return isRequired.or(name.isEqualTo(SPECIAL_STRING));
}
So what is the difference between a Property and a ObservableValue in regard to a CheckBox?
public class TableCellCBBinding extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
private void init(Stage primaryStage) {
primaryStage.setScene(new Scene(buildContent()));
}
private Parent buildContent() {
TableView<ViewModel> tableView = new TableView<>();
tableView.setItems(sampleEntries());
tableView.setEditable(true);
tableView.getColumns().add(buildRequiredColumn());
tableView.getColumns().add(buildNameColumn());
// Add a Textfield to show the values for the first item
// As soon as the name is set to "X", the effectiveRequiredProperty should evaluate to true and the CheckBox should reflect this but it does not
TextField text = new TextField();
ViewModel firstItem = tableView.getItems().get(0);
text.textProperty()
.bind(Bindings.format("%s | %s | %s", firstItem.nameProperty(), firstItem.isRequiredProperty(), firstItem.effectiveRequiredProperty()));
return new HBox(text, tableView);
}
private TableColumn<ViewModel, String> buildNameColumn() {
TableColumn<ViewModel, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nameColumn.setEditable(true);
return nameColumn;
}
private TableColumn<ViewModel, Boolean> buildRequiredColumn() {
TableColumn<ViewModel, Boolean> requiredColumn = new TableColumn<>("isEffectiveRequired");
requiredColumn.setMinWidth(50);
// This is should bind my BindingExpression from to ViewModel to the CheckBox
requiredColumn.setCellValueFactory( p -> p.getValue().effectiveRequiredProperty());
requiredColumn.setCellFactory( CheckBoxTableCell.forTableColumn(requiredColumn));
return requiredColumn;
}
private ObservableList<ViewModel> sampleEntries() {
return FXCollections.observableArrayList(
new ViewModel(false, "A"),
new ViewModel(true, "B"),
new ViewModel(false, "C"),
new ViewModel(true, "D"),
new ViewModel(false, "E"));
}
public static class ViewModel {
public static final String SPECIAL_STRING = "X";
private final StringProperty name;
private final BooleanProperty isRequired;
public ViewModel(boolean isRequired, String name) {
this.name = new SimpleStringProperty(this, "name", name);
this.isRequired = new SimpleBooleanProperty(this, "isRequired", isRequired);
this.name.addListener((observable, oldValue, newValue) -> System.out.println(newValue));
}
public StringProperty nameProperty() {return name;}
public final String getName(){return name.get();}
public final void setName(String value){
name.set(value);}
public boolean isRequired() {
return isRequired.get();
}
public BooleanProperty isRequiredProperty() {
return isRequired;
}
public void setRequired(final boolean required) {
this.isRequired.set(required);
}
public ObservableBooleanValue effectiveRequiredProperty() {
// Bindings with this work:
// return isRequired;
// with this not
return isRequired.or(name.isEqualTo(SPECIAL_STRING));
}
}
}
When typing an X into the name the checkbox in the row should be checked.
When typing an X into the name the checkbox in the row is not checked. It's never checked like it is not bound at all.
CheckBoxXXCells don't live up to their doc when it comes to binding their selected state, f.i. (citing here just for signature, even if not set explicitely):
public final Callback <Integer,​ObservableValue<Boolean>> getSelectedStateCallback()
Returns the Callback that is bound to by the CheckBox shown on screen.
clearly talks about an ObservableValue, so we would expect that it at least shows the selection state.
Actually, the implementation does exactly nothing if it's not a property, the relevant part from its updateItem:
StringConverter<T> c = getConverter();
if (showLabel) {
setText(c.toString(item));
}
setGraphic(checkBox);
if (booleanProperty instanceof BooleanProperty) {
checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
}
ObservableValue<?> obsValue = getSelectedProperty();
if (obsValue instanceof BooleanProperty) {
booleanProperty = (ObservableValue<Boolean>) obsValue;
checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
}
checkBox.disableProperty().bind(Bindings.not(
getTableView().editableProperty().and(
getTableColumn().editableProperty()).and(
editableProperty())
));
To work around, use a custom cell that updates the selected state in its updateItem. With the added quirk that we need to disable the check's firing to really keep the visuals in sync with backing state:
requiredColumn.setCellFactory(cc -> {
TableCell<ViewModel, Boolean> cell = new TableCell<>() {
CheckBox check = new CheckBox() {
#Override
public void fire() {
// do nothing - visualizing read-only property
// could do better, like actually changing the table's
// selection
}
};
{
getStyleClass().add("check-box-table-cell");
check.setOnAction(e -> {
e.consume();
});
}
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
check.setSelected(item);
setGraphic(check);
}
}
};
return cell;
});

Formatted TextField

I'm trying to create a TextField whose content is validated with a template. To do this, I create a TextFormatter to which I pass a StringConverter.
However, I do notice a weird thing about using StringConverter<String>. When I enter invalid data and the field loses focus, it does not clear its content (it only clears it after subsequent focusing). For comparison, when I use StringConverter<LocalTime> this problem is not noticed.
If I catch the change of focus and validate the data, the problem is solved, but I wonder why there is a discrepancy in the validation in both cases.
public class Sample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
TextField fieldA = new TextField();
fieldA.setPromptText("00000");
fieldA.setTextFormatter(new TextFormatter<>(new StringConverter<String>() {
#Override
public String toString(String object) {
if(object == null) return "";
return object.matches("[0-9]{5}") ? object : "";
}
#Override
public String fromString(String string) {
if(string == null) return null;
return string.matches("[0-9]{5}") ? string : null;
}
}));
// fieldA.focusedProperty().addListener((observable, oldValue, newValue) -> {
// if(!fieldA.textProperty().getValueSafe().matches("[0-9]{5}")) {
// fieldA.setText(null);
// }
// });
TextField fieldB = new TextField();
fieldB.setPromptText("HH:MM:SS");
fieldB.setTextFormatter(new TextFormatter<>(new StringConverter<LocalTime>() {
#Override
public String toString(LocalTime object) {
if(object == null) return "";
return object.format(DateTimeFormatter.ofPattern("HH:mm:ss"));
}
#Override
public LocalTime fromString(String string) {
if(string == null) return null;
return LocalTime.parse(string, DateTimeFormatter.ofPattern("HH:mm:ss"));
}
}));
VBox vBox = new VBox(fieldA, fieldB);
vBox.setSpacing(5);
primaryStage.setScene(new Scene(vBox));
primaryStage.show();
}
}
ps: note that the purpose is not to create a TextField that can only accept 5 numbers. This is just an example.
I found the reason for the discrepancy in behavior. The main problem is that updating controls is done by binding valueProperty (in TextFormatter) with textProperty (in TextField). Because notifications of change to all Property objects are only saturated when the value of the wrapper is changed, sequential null submission causes a one-time notification.
The different behavior when using StringConverter<LocalTime> is because LocalTime::parse() throws a DateTimeParseException exception in invalid formatting. This in turn leads to a new valueProperty value being set, and to a previous valid control value.
This is the specific snippet of TextFormatter that is responsible for this behavior.
void updateValue(String text) {
if (!value.isBound()) {
try {
V v = valueConverter.fromString(text);
setValue(v);
} catch (Exception e) {
updateText(); // Set the text with the latest value
}
}
}
And the solution to the problem is that implementing StringConverter::fromString with an invalid value, instead of returning null, should throw unchecked exceptions.
public class Sample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
TextField fieldA = new TextField();
fieldA.setPromptText("00000");
fieldA.setTextFormatter(new TextFormatter<>(new StringConverter<String>() {
#Override
public String toString(String object) {
if(object == null) return "";
return object.matches("[0-9]{5}") ? object : "";
}
#Override
public String fromString(String string) {
if(string == null)
throw new RuntimeException("Value is null");
if(string.matches("[0-9]{5}")) {
return string;
}
throw new RuntimeException("Value not match");
}
}));
TextField fieldB = new TextField();
fieldB.setPromptText("HH:MM:SS");
fieldB.setTextFormatter(new TextFormatter<>(new StringConverter<LocalTime>() {
#Override
public String toString(LocalTime object) {
if(object == null) return "";
return object.format(DateTimeFormatter.ofPattern("HH:mm:ss"));
}
#Override
public LocalTime fromString(String string) {
if(string == null) return null;
return LocalTime.parse(string, DateTimeFormatter.ofPattern("HH:mm:ss"));
}
}));
VBox vBox = new VBox(fieldA, fieldB);
vBox.setSpacing(5);
primaryStage.setScene(new Scene(vBox));
primaryStage.show();
}
}

Table cell combobox - action not performed

I'm applying the below cell factory to a column.
targetEnviroment.setCellFactory(new Callback<TableColumn<DevWorkTabBench, String>, TableCell<DevWorkTabBench, String>>() {
#Override
public TableCell<DevWorkTabBench, String> call(TableColumn<DevWorkTabBench, String> param) {
TableCell<DevWorkTabBench, String> cell = new TableCell<DevWorkTabBench, String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
String status = null;
try {
status = getTableView().getItems().get(getIndex()).getObjectStatus();
} catch (IndexOutOfBoundsException ex) {
status = "";
}
if (status.equalsIgnoreCase("ReadyForDeployment")) {
ComboBox<String> comboBox = new ComboBox(environmentList);
comboBox.valueProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
commitEdit(newValue);
}
});
comboBox.setOnShown(new EventHandler<Event>() {
#Override
public void handle(Event event) {
getTableView().edit(getIndex(), getTableColumn());
getTableView().getSelectionModel().select(getIndex());
}
});
comboBox.setValue(item);
setGraphic(comboBox);
} else {
setGraphic(null);
}
if (empty) {
setGraphic(null);
}
}
};
return cell;
}
});
When I change the status to the mentioned status, I get the look of ComboBox in that particular cell but the drop down does not occur. Even after multiple clicks no action seems to be performed on the combobox. I do not get any exception other than the handled one. Other columns are editable and performing task as expected.
I have no idea what is wrong here. Can anyone please help me.
Since you are always displaying the combo box in the (non-empty) cells, you don't really need to go into "editing" mode as the standard TextFieldTableCell etc does. Your implementation is more similar to the CheckBoxTableCell, which essentially bypasses the editing mechanism. From the documentation for that class:
Note that the CheckBoxTableCell renders the CheckBox 'live', meaning
that the CheckBox is always interactive and can be directly toggled by
the user. This means that it is not necessary that the cell enter its
editing state (usually by the user double-clicking on the cell). A
side-effect of this is that the usual editing callbacks (such as on
edit commit) will not be called. If you want to be notified of
changes, it is recommended to directly observe the boolean properties
that are manipulated by the CheckBox.
So your cell implementation behaves similarly: don't invoke edit(...) (which I think is messing things up) and don't rely on the commitEdit(...), cancelEdit() etc methods (which won't work as you're not in editing state), but just update the model class directly.
I can't test, since there isn't a MCVE to work from, so this might not work directly, but it should be enough to get you started toward something that will work.
targetEnviroment.setCellFactory(new Callback<TableColumn<DevWorkTabBench, String>, TableCell<DevWorkTabBench, String>>() {
#Override
public TableCell<DevWorkTabBench, String> call(TableColumn<DevWorkTabBench, String> param) {
TableCell<DevWorkTabBench, String> cell = new TableCell<DevWorkTabBench, String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null) ;
} else {
String status = getTableView().getItems().get(getIndex()).getObjectStatus();
if (status.equalsIgnoreCase("ReadyForDeployment")) {
ComboBox<String> comboBox = new ComboBox(environmentList);
comboBox.valueProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
//commitEdit(newValue);
getTableView().getItems().get(getIndex()).setObjectStatus(newValue);
}
});
comboBox.setValue(item);
setGraphic(comboBox);
} else {
setGraphic(null);
}
}
}
};
return cell;
}
});

JavaFX - Update a text field through observer pattern

I am trying to update a text field through observer pattern. The update function in the observer (FXML controller) is called after clicking on a listItem in another controller class. And that works fine. The only problem is that my textfield won't update.
Here is my update function in the observer.
#Override
public void update(Observable o, final Object arg) {
System.out.println("test"); // works
firstNameTextField.setText("test"); // doesn't work (text field is still empty)
System.out.println(firstNameTextField.getText()); //works and shows me the word "test" on my console
}
The funny thing is, if I print the text from the text field on my console it's printing the word "test" on the console. It seems like the text field value is updated but it doesn't show up on the ui.
EDIT:
This is my MainController
public class MainController extends Observable implements Initializable {
private ObservableList<String> items = FXCollections.observableArrayList("item1", "item2");
private List<UserProfile> userProfiles = new ArrayList<UserProfile>();
private String[] tabTitles = { "Profile"};
#FXML
private TabPane tabPane;
#FXML
ListView<String> listView;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
for (String tabTitle : tabTitles) {
Tab tab = new Tab(tabTitle);
tab.setClosable(false);
tabPane.getTabs().add(tab);
}
tabPane.getSelectionModel().clearSelection();
for (Tab tab : tabPane.getTabs()) {
try {
String newStringValue = tab.getText();
Parent root = FXMLLoader.load(getClass().getResource("profile.fxml"));
tab.setContent(root);
FXMLLoader fxmlLoader = new FXMLLoader();
Object p = fxmlLoader.load(getClass().getResource("profile.fxml").openStream());
if (fxmlLoader.getController() instanceof ProfileController) {
ProfileController profileController = (ProfileController) fxmlLoader.getController();
this.addObserver(profileController);
}
} catch (IOException e) {
e.printStackTrace();
}
}
tabPane.getSelectionModel().selectFirst();
listView.setItems(items);
listView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
setChanged();
notifyObservers();
}
});
}
}
ProfileController
public class ProfileController implements Initializable, Observer {
#FXML
TextField firstNameTextField;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
}
#Override
public void update(Observable o, final Object arg) {
System.out.println("test"); // works
firstNameTextField.setText("test"); // doesn't work (text field is still empty)
System.out.println(firstNameTextField.getText()); //works and shows me the word "test" on my console
}
}
Can anybody help me out with this?
Thanks!
When you execute
Object p = fxmlLoader.load(getClass().getResource("profile.fxml").openStream());
if (fxmlLoader.getController() instanceof ProfileController) {
ProfileController profileController = (ProfileController) fxmlLoader.getController();
this.addObserver(profileController);
}
you load the structure represented by profile.fxml, and place that hierarchy (including firstNameTextField) in the object you called p. When you invoke update(...) on profileController, it changes the text in the text field that is part of the hierarchy of p. However, you never do anything with p: you don't display it in your UI. So when you change the text of the text field, the changes are of course invisible (because you are changing a text field that isn't displayed).
Presumably, since you said you have the text field displayed, somewhere in the code you couldn't be bothered to include you are loading profile.fxml and displaying the content in the UI. You need to get the reference to that controller, and register it as an observer. Registering an arbitrary instance of the same class will not have the desired effect.

Update validator

I'm using this code to validate TextField for network port.
fieldNport.textProperty().addListener(new ChangeListener<String>()
{
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
{
IpAddressNameValidator.hide();
if (!newValue.matches("-?\\d+(\\.\\d+)?"))
{
MenuItem cc = new MenuItem(newValue + " is not correct network port");
cc.getStyleClass().add("validator-item");
IpAddressNameValidator.getItems().clear();
IpAddressNameValidator.getItems().add(cc);
IpAddressNameValidator.show(fieldNport, Side.RIGHT, 10, 0);
}
}
});
I noticed that the validator is not updated when I delete the old value with backspace. The only solution that I found is this IpAddressNameValidator.hide(); and then show the validator message again.
I there other way to refresh the validator message when I add or remove values? This solution works but the message is blinking when I add new values.
Every time the text changes and the regex expression matches then you are unnecessarily recreating the MenuItem etc. Rather do it like this:
fieldNport.textProperty().addListener(new ChangeListener<String>()
{
private MenuItem cc = new MenuItem();
{
cc.getStyleClass().add("validator-item");
ipAddressNameValidator.getItems().clear();
ipAddressNameValidator.getItems().add(cc);
}
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
{
if (!newValue.matches("-?\\d+(\\.\\d+)?"))
{
if ( ! ipAddressNameValidator.isShowing() )
{
ipAddressNameValidator.show(fieldNport, Side.RIGHT, 10, 0);
}
cc.setText( newValue + " is not correct network port" );
}
}
});

Resources