JavaFX: Run ChangeListener - javafx

Ho can I run the change method inside ChangeListener in the initialization.
Since it only run when changes happen. For example I have a TextField that I want to set it's value to 0 when it's empty, and I do it like this:
textField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if (!newValue.matches("\\d*")) {
textField.setText(newValue.replaceAll("[^\\d]", ""));
}
if (newValue.isEmpty())
textField.setText("0");
}
});
But at the start of the application the changed method is not called. So how to work around this issue?

There's no way to access a listener added to a property. Obviously you could just keep a reference to it:
ChangeListener<String> textFieldListener = (observable, oldValue, newValue) -> {
if (!newValue.matches("\\d*")) {
textField.setText(newValue.replaceAll("[^\\d]", ""));
}
if (newValue.isEmpty())
textField.setText("0");
};
textField.textProperty().addListener(textFieldListener);
textFieldListener.changed(null, null, textField.getText());
or, perhaps more naturally, just move the actual functionality to a different method:
textField.textProperty().addListener((observable, oldValue, newValue) -> vetoTextFieldChanges());
vetoTextFieldChanges();
// ...
private void vetoTextFieldChanges() {
String newText = textField.getText();
if (!newText.matches("\\d*")) {
textField.setText(newText.replaceAll("[^\\d]", ""));
}
if (newText.isEmpty())
textField.setText("0");
}
Note that the whole approach of watching for changes and then modifying them if they are inconsistent with your business logic is not very satisfactory. For example, if you have other listeners registered with the property, they may see the intermediate (invalid) values of the text. The supported way to do this is to use a TextFormatter. The TextFormatter both allows you to veto changes that are requested to the text, before the textProperty() is updated, and to convert the text to an appropriate value. So in your case you could do:
UnaryOperator<TextFormatter.Change> filter = change -> {
// remove any non-digit characters from inserted text:
if (! change.getText().matches("\\d*")) {
change.setText(change.getText().replaceAll("[^\\d]", ""));
}
// if new text is empty, replace all text with "0":
if (change.getControlNewText().isEmpty()) {
change.setRange(0, change.getControlText().length());
change.setText("0");
}
return change ;
};
TextFormatter<Integer> formatter = new TextFormatter<Integer>(new IntegerStringConverter(), 0, filter);
textField.setTextFormatter(formatter);
Now you can use the formatter to get the (numeric) values directly via the formatter's value property, and bind them to your model, etc:
// assume model is a data model and valueProperty() returns a Property<Integer>:
formatter.valueProperty().bindBidirectional(model.valueProperty());
Note that a binding like this will both update the formatter (and consequently the text field) when the model changes, and will also initialize it depending on your model value (thus providing a solution for your original question).

Related

JavaFX - using setRowFactory to highlight new rows

I am writing a JavaFX app where a series of messages appear in a TableView. When a new message appears, its row in the table should be highlighted, meaning its background color should be orange or something. Once the user clicks it, the background color should clear, acknowledging the message was read. Should be simple.
I've done enough research to realize that I need to use a rowFactory to set or clear a row's background. But I'm struggling with the mechanics of setRowFactory(). The documentation on Oracle is over my head, and every example I pull up online seems radically different than the last one.
Here's what I have:
public class Message {
private boolean readOnce;
private int date;
private String msg;
public Message(int date, String msg, String msg2){
this.readOnce = false;
this.date = date;
this.msg = msg;
}
public boolean isReadOnce() {
return readOnce;
}
public void setReadOnce(){
readOnce = true;
}
// ...and more standard getters & setters here...
}
The TableView is set up in the main controller:
#FXML TableView<Message> messageTable;
#FXML TableColumn<Message, Integer> Col1;
#FXML TableColumn<Message, String> Col2;
ObservableList<Message> tableItems;
// ...
// Setting up the Table:
PropertyValueFactory<Message, Integer> dateProperty = new PropertyValueFactory<Message, Integer>("date");
PropertyValueFactory<Message, String> msgProperty = new PropertyValueFactory<Message, String>("msg");
Col1.setCellValueFactory( dateProperty );
Col2.setCellValueFactory( msgProperty );
messageTable.setItems( tableItems );
// If we click an item in the table: messageTable.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
if (newSelection != null) {
System.out.println("Troubleshoot: You clicked: "+newSelection.getMsg());
newSelection.setReadOnce(true);
}
});
And if I want to add a new message to the table, I just add it into the observable list:
public void addMsg(int num, String msg){
tableItems.add(new Message(num, msg));
}
So far, pretty easy. But I'm all thumbs when it comes to the rowFactory:
messageTable.setRowFactory(messageTable -> {
TableRow<Message> row = new TableRow<>();
ObjectProperty<Message> opMsg = row.itemProperty();
Message tmpMsg = opMsg.get();
if(!tmpMsg.isReadOnce()){
row.getStyleClass().add("highlight-message"); // defined in CSS
} else {
row.getStyleClass().add("clear-message"); // defined in CSS
}
return row;
});
To be very honest, I have no idea what I'm doing here. I understand that the rowFactory takes in the entire table and regenerates each row one-by-one. What I don't understand is how does the RowFactory code examine each Message in the table and how can I access them? Originally I thought these line might allow me to see the Message within the row:
TableRow<Message> row = new TableRow<>();
ObjectProperty<Message> opMsg = row.itemProperty();
Message tmpMsg = opMsg.get();
But when I debug the code, tmpMsg == NULL. So that's a big fat dead end.
Anyone see what I'm doing wrong? I've been researching this for about a week, getting absolutely no-where. Any help anyone can offer is wildly appreciated.
Many thanks,
-RAO
TableRows are created by TableView to fill it's viewport and contain TableCells. At the time they are created the item property still contains the default value null. You could register a listener to that property but usually I prefer overriding the updateItem method of a cell.
Also using PseudoClass is simpler than using style classes. New items can be assigned to a row; this could result in the same style class being added multiple times and even both style classes could be added to the same cell. PseudoClasses however can be switched on/of without the need to take care of removing other classes.
final PseudoClass highlightMessage = PseudoClass.getPseudoClass("highlight-message");
messageTable.setRowFactory(messageTable -> new TableRow<Message>() {
{
selectedProperty().addListener((o, oldVal, newVal) -> {
if (newVal) {
Message item = getItem();
if (item != null) {
item.setReadOnce();
pseudoClassStateChanged(highlightMessage, false);
}
}
});
}
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
pseudoClassStateChanged(highlightMessage, item != null && !item.isReadOnce());
}
});
In a CSS stylesheet you could use rules like this:
.table-row-cell:filled {
/* style for non-highlighted rows */
}
.table-row-cell:filled:highlight-message {
/* style for highlighted rows */
}
Note that this does not allow you to programmatically alter the read state. It updates the state on selecting a cell. You could add a BooleanProperty to Message or use a ObservableSet to store the highlighted messages and update the state of cells from a listener if you need to programmatically update the readOnce property. In the latter case you do not need to store a readOnce property in the Message itself...

Restrict user to give input up to 3 characters in javafx

I am new to javaFx so please ignore my silly question here I want to validate (restrict) user to give 3 characters input (if user put more input, it should not allowed or after 3 characters, no latter should be visible) I found many solutions for validation but it does not restricting up to 3 characters as well as it little confusing to understand Here is my code.
public class editController {
#FXML
private TextField countrycode;
public void add(ActionEvent event) {
String ADD=countrycode.getText();
try {
if(ADD.isEmpty()){
Alert alert=new Alert(Alert.AlertType.ERROR);
alert.setHeaderText(null);
alert.setContentText("Please Fill All DATA");
alert.showAndWait();
return;
}
FXMLLoader loader =new FXMLLoader();
loader.load(getClass().getResource("/region/newCountry.fxml").openStream());
Validate using textProperty Listener like:
countrycode.textProperty().addListener(new ChangeListener<String>() {
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if (newValue.length() > 3) {
countrycode.setText(oldValue);
}
}
});
Or get a String length and check that like
int length = ADD.length();
if (length > 3) {
System.out.println("Please Enter Lessthen 3 character");
}
you have to #Override the replaceText(int,int,String) method of your textField this way:
#Override
public void replaceText(int start, int end, String text) {
if ("".equals(text)) { // handles delete
super.replaceText(start, end, text);
return;
}
if (getText().length() < 3) { // handles add and checks length.
super.replaceText(start, end, text);
}
}
This won't let you to insert more than 3 characters, but you can define any regexp and use instead of the second if condition.
The solution of #KeyurBhanderi is good enough until you are typing char by char. But when you try to paste from the clipboard a long text you will not see any changes in the text field. Surprise!
As alternative to avoid confusing set at least truncated string:
field.setText(newValue.substring(0, maxLimit));
In this case the end user will see a part of text and will quickly understand what is going on. Even better to show a popup window with a warning.

Spinner inside TableCell not updating value

I've created a simple TableView that is fed with data from a database, and what I want is just to be able to easily change the value of a numeric column of that table with JavaFx.
But... since I have some mental issue or something, I can't make it work.
Below it's the "SpinnerCell" component, and the issue I've been having is that even after the commitEdit is fired, when I get the items from the TableView, no values were altered. What am I missing from this update lifecycle?
import javafx.scene.control.Spinner;
import javafx.scene.control.TableCell;
public class SpinnerTableCell<S, T extends Number> extends TableCell<S, T> {
private final Spinner<T> spinner;
public SpinnerTableCell() {
this(1);
}
public SpinnerTableCell(int step) {
this.spinner = new Spinner<>(0, 100, step);
this.spinner.valueProperty().addListener((observable, oldValue, newValue) -> commitEdit(newValue));
}
#Override
protected void updateItem(T c, boolean empty) {
super.updateItem(c, empty);
if (empty || c == null) {
setText(null);
setGraphic(null);
return;
}
this.spinner.getValueFactory().setValue(c);
setGraphic(spinner);
}
}
Because your table cell is always showing the editing control (the Spinner), you bypass the usual table cell mechanism for beginning an edit. For example, in the TextFieldTableCell, if the cell is not in an editing state, then a label is shown. When the user double-clicks the cell, it enters an editing state: the cell's editingProperty() is set to true, and the enclosing TableView's editingCellProperty() is set to the position of the current cell, etc.
In your case, since this never happens, isEditing() is always false for the cell, and as a consequence, commitEdit() becomes a no-op.
Note that the CheckBoxTableCell is implemented similarly: its documentation highlights this fact. (The check box table cell implements its own direct update of properties via the selectedStateCallback.)
So there are two options here: one would be to enter an editing state when the spinner gains focus. You can do this by adding the following to the cell's constructor:
this.spinner.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
getTableView().edit(getIndex(), getTableColumn());
}
});
Another option would be to provide a callback for "direct updates". So you could do something like:
public SpinnerTableCell(BiConsumer<S,T> update, int step) {
this.spinner = new Spinner<>(0, 100, step);
this.spinner.valueProperty().addListener((observable, oldValue, newValue) ->
update.accept(getTableView().getItems().get(getIndex()), newValue));
}
and then given a model class for the table, say
public class Item {
private int value ;
public int getValue() { return value ;}
public void setValue(int value) { this.value = value ;}
// ...
}
You could do
TableView<Item> table = ... ;
TableColumn<Item, Integer> valueCol = new TableColumn<>("Value");
valueCol.setCellValueFactory(cellData -> new SimpleIntegerProperty(cellData.getValue().getValue()).asObject());
valueCol.setCellFactory(tc -> new SpinnerTableCell<>(Item::setValue, 1));

How do I automatically trigger enter key in Javafx

Nowadays I am working on raspberry pi and I write some programs in java , javafx platforms.I just would like to inform you that I am simply beginner on javafx.
According to that I just would like to trigger ENTER key after changing my textfield.Working principle of my program is like this;
1)I have created one masterform fxml and it is directing all other pages with one textfield.
2)I created main method that let me to use keyboard to enter some specific String values to assign them to textfield for page alteration.
3)I have a bridge java page, it includes global variables to use everywhere in project.So Firstly I set value from keyboard to these global variables.These global variables are created as stringproperty for adding actionlistener for any change.
4)Then I set these global variables to textfield.
5)Textfield indicates relevant values from keyboard.But Unfortunately I can not forward the pages without pressing to enter key.In this case ı would like to trigger this textfield.But unfortunately ı have no idea how to trigger texfield without pressing enter key.Therefore I decided to make auto trigger to enter key for this textfield.
I simply used robot method;
Robot robot = new Robot();
robot.keyPress(KeyEvent.VK_ENTER);
But it didn't work.Because After I set the global variable to textfield for first time.It does not define the value of the textfield is changed.It determines after pressing the enter key.
So how can I trigger this textfield after getting value of my global variables.I would like to pass how to set pages, I will show you how my program works.
Example of my code is;
Main method
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
for (String strBarcode = scanner.nextLine(); !strBarcode.isEmpty();
strBarcode = scanner.nextLine()) {
if (strBarcode.equals("distribution")){
Global.G_MOD.set("distribution");
System.out.println(Global.G_MOD.get());
}
}}
GlobalVariables.java(bridge page)
public class Global{
public static StringProperty G_MOD = new SimpleStringProperty("");
}
My MasterController Page for javafx
public class masterformController implements Initializable {
#FXML
public TextField tbxBarcode;
#FXML
void onchangetbxBarcode(ActionEvent event) {
if(Global.G_MOD.get().equals("distribution")){
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/puttolightfx/fxml/page1.fxml"));
Parent rootpage1 = (Parent)loader.load();
pnPages.getChildren().clear();
pnPages.getChildren().add(rootpage1);
} catch (IOException ex) {
Logger.getLogger(masterformController.class.getName()).log(Level.SEVERE, null, ex);
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
Global.G_MOD.addListener(new ChangeListener(){
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
String Newvalue = (String)newValue;
tbxBarcode.setText(Global.G_MOD.get());}
});
}
}
So Everything is working, just I have to trigger textfield when the global value : Global.G_MOD is indicated on texfield.Then it will pass to another page according to global value of Global.G_MOD : "distribution".
SOLUTION(SOLVED):
I solved my problem using thread on listener of the textfield.I gave up to trigger enter key automatically and focused on textfield change.
I simply decided to use thread to change .fxml pages in textfield listener.
Platform.runLater(new Runnable() {
#Override
public void run() {
//if you change the UI, do it here !
}
});
EDITED CODE :
tbxBarcode.textProperty().addListener((ObservableValue<? extends String> observable, String oldValue, String newValue) -> {
String Newvalue=(String)newValue;
System.out.println(tbxBarcode.getText());
Platform.runLater(new Runnable() {
#Override
public void run() {
if(Global.G_MOD.get().equals("distribution")){
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/puttolightfx/fxml/page1.fxml"));
Parent rootpage1 = (Parent)loader.load();
pnPages.getChildren().clear();
pnPages.getChildren().add(rootpage1);
} catch (IOException ex) {
Logger.getLogger(masterformController.class.getName()).log(Level.SEVERE, null, ex);
}
}
// }
}
});
});
Try using
textField.fireEvent(new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.ENTER, true, true, true, true));
According to the docs
public KeyEvent(EventType<KeyEvent> eventType,
String character,
String text,
KeyCode code,
boolean shiftDown,
boolean controlDown,
boolean altDown,
boolean metaDown)
Constructs new KeyEvent event with null source and target and KeyCode object directly specified.
Parameters:
eventType - The type of the event.
character - The character or sequence of characters associated with the event
text - A String describing the key code
code - The integer key code
shiftDown - true if shift modifier was pressed.
controlDown - true if control modifier was pressed.
altDown - true if alt modifier was pressed.
metaDown - true if meta modifier was pressed.
Since:
JavaFX 8.0
You can refer https://docs.oracle.com/javase/8/javafx/api/javafx/scene/input/KeyEvent.html
Edit 1
You need to identify the moment when Enter key event must be triggered.
For example:
If your textfield allows a limited number of characters, then you can add the above mentioned code in the following way:
txtField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if (newValue.length()>30) {
txtField.setText(oldValue);
txtField.fireEvent(new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.ENTER, true, true, true, true));
}
}
});
This is just an example. It can fire your event multiple times, so you need to write the code to fire the event just once.

Update field on valuechange

I have two fields binded with a fieldgroup. I need to make the second field change when the first field loses focus.
What I have so far:
class MyBean {
private String first;
private String second;
//getters, setters
}
class MasterData extends CustomComponent{
private TextField first;
private TextField second;
//add to layout, other tasks
}
//the calling code
FieldGroup fieldgroup = new FieldGroup(new MyBean());
fieldgroup.bindMemberFields(new MasterData());
((AbstractComponent)fg.getField("first")).setImmediate(true);
fg.getField("first").addValueChangeListener(new ValueChangeListener() {
#Override
public void valueChange(ValueChangeEvent event) {
MyBean bean = fg.getItemDataSource().getBean();
bean.setSecond((String) event.getProperty().getValue());
try {
fg.commit();
} catch (CommitException e) { }
}
});
The value change event is called but the second field never gets updated on the screen. How canI force the fieldgroup to repaint its field?
You might want to take a look at BlurListener.
Also, I think you need to update the value of the TextField "manually". Changing it in the bean might no update the TextField. When you call commit() on the FieldGroup it commits the values in the field to the bean, not the other way around. So in the listener's implementation, it might look something like this:
second.setValue(event.getProperty().getValue());
try {
fg.commit();
} catch (CommitException e) { }

Resources