Check if TextFieldTableCell equals something on edit commit? - java

I am using a custom TextFieldTableCell in JavaFX 8 to allow users to edit the text field. When the user hits Enter, however, I want to check to see if the text field equals a certain value. If it does equal this certain value, I do not want the entry to save and for it to revert to the text it had before the user started editing. Is there a method I can override to produce this result? I cannot find one that fits what I am looking for.
Thank you in advance!

Since the model is bound to the cell's data, you can do the validation and reset part in the modell class, in your case in the Person class.
Here is a simple example how you can do it:
public class Controller implements Initializable {
#FXML
private TableColumn<Model, String> name;
#FXML
private TableView<Model> tableView;
#Override
public void initialize(URL location, ResourceBundle resources) {
name.setCellValueFactory(data -> data.getValue().nameProperty());
name.setCellFactory(cell -> new TextFieldTableCell<>(new StringConverter<String>() {
#Override
public String toString(String object) {
return object;
}
#Override
public String fromString(String string) {
return string;
}
}));
tableView.setEditable(true);
tableView.setItems(FXCollections.observableArrayList(new Model("Test")));
}
private class Model {
private StringProperty name;
ChangeListener<String> nameChangeListener = (observable, oldValue, newValue) -> {
if (!newValue.matches("[A-Z][a-zA-Z]*")) { // a validation example insert your here.
this.name.set(oldValue);
}
};
public Model(String name) {
this.name = new SimpleStringProperty(name);
this.name.addListener(nameChangeListener);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
}
}

From your description I assume that you are talking about an editable cell in a table view.
If that's the case, the following example should be working for you. You can use the .setOnEditCommit() method to add an event handler to the colum for which you would like to check the entered value.
//Create table
TableView<Person> table = new TableView<Person>();
table.setEditable(true);
//Create column
TableColumn<Person, String> column = new TableColumn<Person, String>("Full Name");
column.setCellValueFactory(new PropertyValueFactory<>("fullName"));
column.setCellFactory(TextFieldTableCell.<Person> forTableColumn());
column.setMinWidth(200);
column.setOnEditCommit(event -> {
//Get entered value
String newFullName = event.getNewValue();
//Get selected position
TablePosition<Person, String> pos = event.getTablePosition();
//Get row of position
int row = pos.getRow();
//Get data from selected row
Person person = event.getTableView().getItems().get(row);
//Check if text equals ...
if (newFullName.equals("Test")) {
person.setFullName(newFullName);
} else {
person.setFullName(event.getOldValue());
table.refresh();
}
});

Related

How to restrict textfields in TornadoFX to numbers only

The problem here is that I wanna make sure that the user doesn't enter any strings or text especially that I need to enter his choice into a database later so I don't things to get messed up in the database's part, here is part of code which is the view I wish to use the textview with restricted Integers (specifically the amount am field).
PS: I'm still new to both JavaFX and TornadoFX so hope this doesn't sound like a rather silly question.
My Code:
package com.company.view
import javafx.beans.property.SimpleIntegerProperty
import javafx.scene.control.CheckBox
import tornadofx.*
import javafx.scene.control.TextField
import javafx.util.converter.NumberStringConverter
import java.sql.Connection
class Add: View() {
override val root = Form()
private val mainMenu: MainMenu by inject()
private var cname: TextField by singleAssign()
private var address: TextField by singleAssign()
private var sname: TextField by singleAssign()
private var ch: CheckBox by singleAssign()
private var am: TextField by singleAssign()
var conn: Connection?= mainMenu.conn
init {
with(root) {
vbox(30.0) {
fieldset("Enter Your Info below") {
field("Enter The Customer's Name") {
cname = textfield()
}
field("Enter the Customer's address") {
address = textfield()
}
field("Enter Bought Stock's Name") {
sname = textfield()
}
field("Do you wish to pay now?") {
ch = checkbox()
}
field("Enter the amount you wish to buy"){
am = textfield()
}
button("Submit")
{
setOnAction {
addPayment(cname.text, address.text, sname.text, ch.isSelected, am.text)
}
}
}
}
}
}
private fun addPayment(cusName: String, caddress: String, stname: String, che: Boolean,am: String){
//required code for inserting into the database here.
}
}
You can use the filterInput extension function we've added to TextField and check that the text after the addition is in int. If it's not, deny the last input change:
textfield {
filterInput { it.controlNewText.isInt() }
}
On another note, you really need to look into ItemViewModel. It's an anti-pattern to assign each input element to a variable and extract the values from the input values on submit. Your code will be a lot cleaner and easier to reason about and refactor later if you use view models.
PS: The filterInput function is available in the soon to be released TornadoFX 1.7.15, in the mean time you can add this extension function to your project:
fun TextInputControl.filterInput(discriminator: (TextFormatter.Change) -> Boolean) {
textFormatter = TextFormatter<Any>(CustomTextFilter(discriminator))
}
From your example it seems like that you'd want to use a PropertySheet which comes from ControlsFX. I use it in production and it works well with TornadoFX.
Here is an example from the samples project which you can peruse. This will let you edit and bind multiple types not just numbers:
public class PropertySheetExample extends VBox {
private static Map<String, Object> customDataMap = new LinkedHashMap<>();
static {
customDataMap.put("Group 1#My Text", "Same text"); // Creates a TextField in property sheet
customDataMap.put("Group 1#My Date", LocalDate.of(2000, Month.JANUARY, 1)); // Creates a DatePicker
customDataMap.put("Group 2#My Enum Choice", SomeEnumType.EnumValue); // Creates a ChoiceBox
customDataMap.put("Group 2#My Boolean", false); // Creates a CheckBox
customDataMap.put("Group 2#My Number", 500); // Creates a NumericField
}
class CustomPropertyItem implements PropertySheet.Item {
private String key;
private String category, name;
public CustomPropertyItem(String key) {
this.key = key;
String[] skey = key.split("#");
category = skey[0];
name = skey[1];
}
#Override
public Class<?> getType() {
return customDataMap.get(key).getClass();
}
#Override
public String getCategory() {
return category;
}
#Override
public String getName() {
return name;
}
#Override
public String getDescription() {
return null;
}
#Override
public Object getValue() {
return customDataMap.get(key);
}
#Override
public void setValue(Object value) {
customDataMap.put(key, value);
}
}
public PropertySheetExample {
ObservableList<PropertySheet.Item> list = FXCollections.observableArrayList();
for (String key : customDataMap.keySet())
list.add(new CustomPropertyItem(key));
PropertySheet propertySheet = new PropertySheet(list);
VBox.setVgrow(propertySheet, Priority.ALWAYS);
getChildren().add(propertySheet);
}
}
You can also take a look at this question for more info.

Is there anyway to generate UI controllers (radio button, input fields, etc) using a loop in java

I'm a beginner in java. I want to create a javafx form which contains a few input elements such as radio buttons, input types, etc.. created using a loop.
A pseudo code as follows,
for (Suit **suit** : suits){
final TextField **suit** = new TextField();
lastName.setPromptText("Enter your last name.");
GridPane.setConstraints(lastName, 0, 1);
grid.getChildren().add(lastName);
}
The problem i came across is how to define the variable name of each element based on elements of the array.
The easiest way to do this is, if the class you want to edit contains javafx properties:
public class Person {
private final StringProperty firstName;
private final StringProperty familyName;
public String getFirstName() {
return firstName.get();
}
public String getFamilyName() {
return familyName.get();
}
public StringProperty firstNameProperty() {
return firstName;
}
public StringProperty familyNameProperty() {
return familyName;
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public void setFamilyName(String familyName) {
this.familyName.set(familyName);
}
public Person(String firstName, String familyName) {
this.firstName = new SimpleStringProperty(firstName);
this.familyName = new SimpleStringProperty(familyName);
}
#Override
public String toString() {
return familyName.get() + ", " + firstName.get();
}
}
You could simply use binding/listeners in this case to bind the properties of the ui to the properties of the person object:
Example:
Person[] persons = new Person[] {
new Person("Frank", "Miller"),
new Person("Jane", "Doe"),
new Person("Luke", "Skywalker"),
new Person("Leia", "Organa")
};
Button btn = new Button();
btn.setText("Print");
btn.setOnAction((ActionEvent event) -> {
System.out.println("-------------------------");
Arrays.stream(persons).forEach(System.out::println);
});
GridPane root = new GridPane();
int row = 0;
for (Person person : persons) {
row = addPerson(root, row, person);
}
GridPane.setConstraints(btn, 0, row);
root.getChildren().add(btn);
/**
* Adds a Person to a GridPane
* #param pane the pane to add to.
* #param startRow the first row used by this person
* #param person the person to add.
* #return first row below the Nodes added for the person.
*/
static int addPerson(GridPane pane, int startRow, Person person) {
// use binding
TextField firstNameText = new TextField();
firstNameText.textProperty().bindBidirectional(person.firstNameProperty());
// use listeners
TextField familyNameText = new TextField(person.getFamilyName());
familyNameText.textProperty().addListener((ObservableValue<? extends String> observable, String oldValue, String newValue) -> {
// you could do e.g. type conversion here
person.setFamilyName(newValue);
});
// this is only necessary, if you want to change the family name of a person
// from the code and the result should be visible in the ui
person.familyNameProperty().addListener((ObservableValue<? extends String> observable, String oldValue, String newValue) -> {
// you could do e.g. type conversion here
familyNameText.setText(newValue);
});
Label firstNameLabel = new Label("First Name");
GridPane.setConstraints(firstNameLabel, 0, startRow);
GridPane.setConstraints(firstNameText, 1, startRow);
Label familyNameLabel = new Label("Family Name");
GridPane.setConstraints(familyNameLabel, 2, startRow);
GridPane.setConstraints(familyNameText, 3, startRow);
pane.getChildren().addAll(firstNameLabel, firstNameText, familyNameLabel, familyNameText);
return startRow+1;
}
Note that a GridPane may not be the best way to add a list of persons to a scene your problem.
See Oracle Tutorial: JavaFX: Working with JavaFX UI Components: 13 Table View for an example using a TableView.

How to set validation check in ViewModel

I am newbie in building javafx MVVM app.
I've created a simple ViewModel:
public class PersonViewModel {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty age = new SimpleIntegerProperty();
public PersonViewModel() {}
// getters and setters
}
and simple View:
public class PersonView implements Initializable {
#FXML
TextField name;
#FXML
TextField age;
#FXML
Button ok;
#Override
public void initialize(URL location, ResourceBundle resources) {
PersonViewModel viewModel = new PersonViewModel();
name.textProperty().bindBidirectional(viewModel.name);
age.textProperty().bindBidirectional(viewModel.age);
}
}
Can you give me any idea how to make age validation? F.e. I wanna not to allow user to put characters into age (TextField) except [a-zA-Z]. And the main idea of my question to make this validation in ViewModel) Help me pls.
P.S. I wanna make it not using not standard javafx components.
You can use a TextFormatter both to filter input in a text input control, and to convert the text into a value of a specific type. If you want the view model to define the validation rules, then define a method in there representing the validation, and delegate to that method in the filter definition for the TextFormatter. For example:
public class PersonViewModel {
private final StringProperty name = new SimpleStringProperty();
public StringProperty nameProperty() {
return name ;
}
public final String getName() {
return nameProperty().get();
}
public final void setName(String name) {
nameProperty.set(name);
}
private final IntegerProperty age = new SimpleIntegerProperty();
public IntegerProperty ageProperty() {
return age ;
}
public final int getAge() {
return ageProperty().get();
}
public final void setAge(int age) {
ageProperty.set(age);
}
public boolean validAgeInput(String input) {
// must support partial entry while editing, including empty string
// accept any integer from 0 - 135 (arbitrary upper bound example)
String regex = "([0-9]{0,2})|(1[0-2][0-9])|(13[0-5])";
return input.matches(regex);
}
}
Now you can do:
public class PersonView implements Initializable {
#FXML
TextField name;
#FXML
TextField age;
#FXML
Button ok;
#Override
public void initialize(URL location, ResourceBundle resources) {
PersonViewModel viewModel = new PersonViewModel();
UnaryOperator<Change> filter = change -> {
if (viewModel.validAgeInput(change.getControlNewText()) {
// accept
return change ;
} else {
// reject
return null ;
}
};
TextFormatter<Integer> ageFormatter = new TextFormatter<>(new IntegerStringConverter(), 0, filter);
age.setTextFormatter(ageFormatter);
ageFormatter.valueProperty().bindBidirectional(viewModel.ageProperty().asObject());
name.textProperty().bindBidirectional(viewModel.nameProperty());
}
}
The filter defined here will only accept input in the control if it matches the rule defined by the method in the PersonViewModel. The valueProperty() of the TextFormatter represents the text in the TextField after passing it to the IntegerStringConverter: this is bound bidirectionally to the ageProperty() in the model. (The call to asObject() effectively just converts between an IntegerProperty and an ObjectProperty<Integer>.)

JavaFx - values from TableColumn do not change

I'm trying to get the value of the editable table using javaFX Scene builder, but I can not get the updated value.
I' have table with 2 columns: name column and value column.
whenever the user change on of the value of value column, I want to react.
But when I print the new change value, it always displays the default value.
public class MesssageField
{
private final StringProperty fieldName;
private final StringProperty fieldValue;
public MesssageField(String fieldName, String fieldValue) {
this.fieldName = new SimpleStringProperty(fieldName);
this.fieldValue = new SimpleStringProperty(fieldValue);
}
public StringProperty getFieldNameProperty() {
return fieldName;
}
public StringProperty getFieldValueProperty() {
return fieldValue;
}
public void setFieldValue(String fieldValue) {
this.fieldValue.set(fieldValue);
}
public String getFieldName() {
return fieldName.get();
}
public String getFieldValue() {
return fieldValue.get();
}
}
The controller class has:
private static ObservableList<MesssageField> obserListMsgsField;
#FXML
private TableView<MesssageField> msgTableView;
#FXML
private TableColumn<MesssageField, String> fieldNameColumn;
#FXML
private TableColumn<MesssageField, String> fieldValueColumn;
#Override
public void initialize(URL arg0, ResourceBundle arg1)
{
// create List Of fields
obserListMsgsField = FXCollections.observableArrayList();
fieldValueColumn.setCellFactory(TextFieldTableCell.forTableColumn());
// Initialize the person table with the two columns.
fieldNameColumn.setCellValueFactory(cellData -> cellData.getValue().getFieldNameProperty());
fieldValueColumn.setCellValueFactory(cellData -> cellData.getValue().getFieldValueProperty());
}
the onEditFieldValueCommit method attached to "on edit commit"
#FXML
public void onEditFieldValueCommit() {
MesssageField messageField = msgTableView.getSelectionModel().getSelectedItem();
// get field name
String fieldName = messageField.getFieldName();
// get field value
String valueString = messageField.getFieldValue();
// debug print
System.out.print("\n[DEBUG] Field Name = " + fieldName + " = " + valueString);
}
But the output is always the default value and not the changed value.
Thanks
In your onEditFieldValueCommit method you are not using the parameter required, and then you are not updating the list obserListMsgsField.
First, you need to add a parameter of the type TableColum.CellEditEvent, which is the event that is fired when a user performs an edit on the table cell.
Then you just get the new value or the row affected, updating the list:
#FXML
public void onEditFieldValueCommit(TableColumn.CellEditEvent<MesssageField, String> t) {
t.getRowValue().setFieldValue(t.getNewValue());
System.out.print("\n[DEBUG] Field Name = " + t.getRowValue().getFieldName() +
" = " + t.getRowValue().getFieldValue());
}

JavaFX 2.0 Choice Box Issue. How to update a choiceBox, which represents a list of objects, when an object is updated?

I have a choiceBox which represents a list objects. When the name representing one of those objects is changed by another bit of code the name in the drop down list for the choice box does not change. For example if I have a choice box which is made up of list Test objects. The Code for Test is shown below:
class Test {
String name;
public Test(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
#Override
public String toString() {
return name;
}
}
Then have a choice Box as follows:
ChoiceBox<Test> chi = new ChoiceBox<>();
ObservableList<Test> items = FXCollections.observableArrayList();
chi.setItems(items);
items.addAll(new Test("ITEM1"),new Test("ITEM2"),new Test("ITEM3"));
The ChoiceBox will show the list ITEM1, ITEM2 and ITEM3
If I then change the name of one of the items via the following code:
items.get(1).setName("CHANGED");
The ChoiceBox will still show the list ITEM1, ITEM2 and ITEM3. How can I make it so the choiceBox will update and show the list ITEM1, CHANGED and ITEM3?
Just for completeness - in fx2 you are probably stuck with the replace approach as outlined in the other answer. Since fx8, there's a mechanism to tell the list to listen to changes of its contained items (precondition being, of course, that your item has properties and notifies listeners on change):
/** changed item to
* - use property and notify on change
* - not override toString (for visuals, use converter instead)
*/
class Test {
StringProperty name;
public Test(String name) {
setName(name);
}
public StringProperty nameProperty() {
if (name == null) name = new SimpleStringProperty(this, "name");
return name;
}
public void setName(String name) {
nameProperty().set(name);
}
public String getName() {
return nameProperty().get();
}
}
// use in collection with extractor
ObservableList<Test> items = FXCollections.observableList(
e -> new Observable[] {e.nameProperty()} );
items.addAll(...);
choiceBox = new ChoiceBox<>(items);
// tell the choice how to represent the item
StringConverter<Test> converter = new StringConverter<Test>() {
#Override
public String toString(Test album) {
return album != null ? album.getName() : null;
}
#Override
public Test fromString(String string) {
return null;
}
};
choiceBox.setConverter(converter);
I had the same issue in JavaFX 8 (with ComboBox too). I was able to get the same functionality by removing the item then adding a new one at the same location.
Example:
This gets selected item, creates a new item, then calls the replace method:
Channel selected = channelChoiceBox.getSelectionModel().getSelectedItem();
Channel newChan = new Channel("Example", "Channel");
replaceChannel(newChan, selected);
This replaces the selected channel with the new one, effectively editing it:
private void replaceChannel(Channel newChan, Channel oldChan) {
int i = channelChoiceBox.getItems().indexOf(oldChan);
channelChoiceBox.getItems().remove(oldChan);
channelChoiceBox.getItems().add(i, newChan);
channelChoiceBox.setValue(newChan);
}
It's not ideal, but does the job.
Disclaimer: I'm new to Java and programming in general.
Yes. This seems to be issue with JavaFx. I too had faced that. Use ComboBox instead of ChoiceBox that will work.

Categories

Resources