What's the best way to keep the value of a javafx Property within specific bounds?
(Or - is this bad practice, existing any reason to never filter values wrapped by javafx properties?)
Example1: avoid negative values in an IntegerProperty
Example2: keep the value of an IntegerProperty within the bounds of a List
First idea: - override IntegerPropertyBase.set(int). It's safe? Actually setValue(int) only calls set(int), but - if this implementation one day changes - the control over the values set goes lost.
Second idea: - override IntegerPropertyBase.invalidate(). But at this point the value already was set.
Will it fit better to javafx properties throw an IllegalArgumentException (or an ArrayIndexOutOfBoundsException, if the wrapped value is the index of an array), or better refuse the value out of bounds, setting back the last value in bounds?
Maybe like this:
class BoundedIntegerProperty extends IntegerPropertyBase {
(...)
int oldValue = defaultValueInBounds;
boolean settingOldValue = false;
public void invalidated() {
if(!settingOldValue){
if(outOfBounds(get())){
settingOldValue = true;
set(oldValue);
} else {
oldValue = get();
}
} else
settingOldValue = false;
}
}
Only throw an Exception in invalidated() for values out of bounds may keep the value of the property out of bounds.
Have I overlooked anything in javafx properties provided to filter values?
(If necessary, please help me improving the possibly bad english of this text...)
In both your examples, there seemed to be a logical default value (eg. if it's required to be positive, negative numbers turn into 0). Assuming you document that well (what the defaults are if the value is invalid), I think your first approach seems like it's on the right path.
I'd recommend starting with a concrete class like SimpleIntegerProperty as the class you're extending (unless there's some reason you chose IntegerPropertyBase instead.
I would then overwrite both the set(int) method and the setValue(Number) method, wrapping the parent in your logic:
/**
* Explanation that values under 0 are set to 0
*/
#Override
public void set(int value){
super.set(value > 0 ? value : 0);
}
/**
* Explanation that values under 0 are set to 0
*/
#Override
public void setValue(Number value){
super.setValue(value.intValue() > 0 ? value : 0);
}
There may be a case where there isn't logical default values (or you just want to reject invalid values). That case makes it a bit harder - you'd actually want to use a method signature of like this so the caller knows if the value changed:
public boolean set(int value)
In order to do that, you'll have to go back quite a few classes - all the way back to ReadOnlyIntegerProperty and implement the setting / invalidating structure yourself.
I would hesitate to use Exceptions to handle the invalid input. It is a legitimate use of exceptions, but my fear is that the Exception would be relied on for validation. Exceptions are very resource intensive, and should only be hit if there's something that needs to be fixed. So it's really about your intentions and how much you trust people using your class to do the right thing (and validate before sending to you).
I believe I understand what you're shooting for better now. You're looking to perform user input validation.
When you're doing your user validation, there's really two ways to approach it:
Validate immediately after any change takes place and provide
feedback
Validate when the focus leaves the input area
With both, you'll be using property listeners - it's just a matter of what property listener you're dealing with.
In the first case you'll listen directly to the property you're validating:
TextField field = new TextField();
field.textProperty().addListener(new ChangeListener<String>(){
#Override
public void changed(ObservableValue<? extends String> value,
String oldValue, String newValue) {
//Do your validation or revert the value
}});
In the second case, you'll listen to the focused property, and validate when focus is lost (you can maintain the last validated value in this listener to help revert the value if necessary):
TextField field = new TextField();
field.focusedProperty().addListener(new ChangeListener<Boolean>(){
String lastValidatedValue = "";
#Override
public void changed(ObservableValue<? extends Boolean> value,
Boolean oldValue, Boolean newValue) {
if(newValue == false && oldValue == true){
//Do your validation and set `lastValidatedValue` if valid
}
}});
Note:
I was assuming you just wanted to put in a fail safe for system code updating the UI. I'll leave my previous answer as I believe it provides useful information as well.
Related
I have an I18N implementation that binds JavaFX UI elements through properties, for e.g.:
def translateLabel(l: Label, key: String, args: Any*): Unit =
l.textProperty().bind(createStringBinding(key, args))
Having a property binding is easy and works well. However I struggle with ComboBox as it takes an ObservableList (of Strings in my case) and I have no idea how to bind my translator functions to that. I am conflicted about the difference between ObservableValue, ObservableList and Property interfaces as they all sound the same.
It has itemsProperty() and valueProperty() however the documentation for these is lacking and vague so I am not sure where they can be used.
What I want to do is have a ComboBox where all elements (or at least the selected / visible one) changes the language dynamically (I18N) as if it was bound, just like a property.
EDIT:
Just to make it easier understand, my current implementation is:
private def setAggregatorComboBox(a: Any): Unit = {
val items: ObservableList[String] = FXCollections.observableArrayList(
noneOptionText.getValue,
"COUNT()",
"AVG()",
"SUM()"
)
measureAggregatorComboBox.getItems.clear()
measureAggregatorComboBox.getItems.addAll(items)
}
Where noneOptionText is a StringProperty that's already bound to a StringBinding that's translated upon class instantiation in this manner:
def translateString(sp: StringProperty, key: String, args: Any*): Unit =
sp.bind(createStringBinding(key, args))
The itemsProperty() is the list of items to show in the combo box popup; it's value is an ObservableList.
The valueProperty() is the selected item (or the value input by the user if the combo box is editable).
What I'd recommend is to have the data in the combo box be the list of keys, and use custom cells to bind the text in each cell to the translation of those keys. I don't speak scala, but in Java it looks like:
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().setAll(getAllKeys());
class TranslationCell extends ListCell<String> {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
textProperty().unbind();
if (empty || item == null) {
setText("");
} else {
textProperty().bind(createStringBinding(item));
}
}
}
comboBox.setCellFactory(lv -> new TranslationCell());
comboBox.setButtonCell(new TranslationCell());
Note now that the valueProperty() contains the key for the selected value.
If you really want to bind the items to an ObservableValue<ObservableList<String>> you can do something like:
comboBox.itemsProperty().bind(Bindings.createObjectBinding(() ->
FXCollections.observableArrayList(...),
...));
where the first ... is a varargs of String values, and the second ... is an observable value, changes in which would prompt the list to be recomputed. (So in your case, I'm guessing you have an ObservableValue<Locale> somewhere representing the current locale; you would use that for the second argument.)
In your specific use case (where only the first element of the list is internationalizable), it might be easier simply to use a listener:
comboBox.getItems().setAll(
noneOptionTest.getValue(),
"COUNT()",
"AVG()",
"SUM");
noneOptionTest.addListener((obs, oldVal, newVal) ->
comboBox.getItems().set(0, newVal));
though I agree this is slightly less elegant.
For completeness:
I am conflicted about the difference between ObservableValue,
ObservableList and Property interfaces as they all sound the same.
ObservableValue<T>: represents a single value of type T which can be observed (meaning that code can be executed when it changes).
Property<T>: represents a writable ObservableValue<T>; the intention is that implementations would have an actual variable representing the value. It defines additional functionality allowing its value to be bound to other ObservableValue<T>.
So, for example:
DoubleProperty x = new SimpleDoubleProperty(6);
DoubleProperty y = new SimpleDoubleProperty(9);
ObservableValue<Number> product = x.multiply(y);
x and y are both Property<Number>; the implementation of SimpleDoubleProperty has an actual double variable representing this value, and you can do things like y.set(7); to change the value.
On the other hand, product is not a Property<Number>; you can't change its value (because doing so would violate the binding: the declared invariant that product.getValue() == x.getValue() * y.getValue()); however it is observable, so you can bind to it:
BooleanProperty answerCorrect = new SimpleBooleanProperty();
answerCorrect.bind(product.isEqualTo(42));
etc.
An ObservableList is somewhat different: it is a java.util.List (a collection of elements), and you can observe it to respond to operations on the list. I.e. if you add a listener to an ObservableList, the listener can determine if elements were added or removed, etc.
I'm new to JavaFX and was wondering if the Bindings API allowed an easier way to achieve the following. Consider a model that contains a database that may be null (because the database loads asynchronously) and a view that displays a label status reflecting the state of the database. If it is null it should say something like "Loading..." and if it isn't it should display how many items are in the database. It also would be great if the status could reflect the size of the database as it grows or shrinks.
So far, I understand that I could bind an integer property (size of the database) to the text property of the label by using a converter. This is fine, but I want the label to display more than the number. A localized string like "Loaded {0} items" precisely. And let's not forget that the database may still be null.
This is the solution I have in place
#Override
public void initialize(URL url, ResourceBundle bundle) {
// Initialize label with default value
status();
model.databaseProperty().addListener((obs, old, neu) -> {
// Update label when database is no longer null
status();
// Update label when size of database changes
neu.sizeProperty().addListener(x -> status());
});
}
public void status() {
if (model.database() == null) {
status.setText(bundle.getString("status.loading"));
} else {
String text = bundle.getString("status.ready");
int size = model.database().size();
text = new MessageFormat(text).format(size);
status.setText(text);
}
}
It works, but is there a way to do it with a chain of bindings, or at least part of it? I've seen how powerful (and lenghty) boolean bindings can be but I'm not sure something as flexible is possible with string bindings.
You can use Bindings.when, which is essentially a dynamic if/then binding:*
status.textProperty().bind(
Bindings.when(model.databaseProperty().isNull())
.then(bundle.getString("status.loading"))
.otherwise(
Bindings.selectInteger(model.databaseProperty(), "size").asString(
bundle.getString("status.ready")))
);
However, the above assumes bundle.getString("status.ready") returns a java.util.Formatter string, not a MessageFormat string. In other words, it would need to be "Loaded %,d items" rather than "Loaded {0,number,integer} items".
Bindings doesn’t have built-in support for MessageFormat, but if you really want to stick with MessageFormat (which is a legitimate requirement, as there are things MessageFormat can do which Formatter cannot), you can create a custom binding with Bindings.createStringBinding:
MessageFormat statusFormat = new MessageFormat(bundle.getString("status.ready"));
status.textProperty().bind(
Bindings.when(model.databaseProperty().isNull())
.then(bundle.getString("status.loading"))
.otherwise(
Bindings.createStringBinding(
() -> statusFormat.format(new Object[] { model.getDatabase().getSize() }),
model.databaseProperty(),
Bindings.selectInteger(model.databaseProperty(), "size")))
);
* Actually, it’s more like the ternary ?…: operator.
Is there a way to build listener that detect if date are still transmitted to variable and if yes do one thing and when not do other?
For example
Until “int counter1” increasing set boolean (true) or print or change another int for 1
int counter (not increasing or decreasing anymore) set Boolean (false) print different thing change another int for 2.
Basically variable changing plus or minus do one thing stop changing do other thing start changing again go back to doing first thing etc etc.
Is there a way to do this?
Without obvious whole if statements compering way.
Handmade
Most simple way is to access that variable through getters and setters. You can put preferred logic into your setter and track all mutations from there.
public class Main {
static int observable = 0;
static void setObservable(int newValue) {
if (observable != newValue) {
System.out.printf("Observable int has been changed from %d to %d.%n", observable, newValue);
observable = newValue;
}
}
public static void main(String[] args) {
observable = 1; // Nothing notified us that value has been changed
setObservable(2); // Console output 'Observable int changed from 1 to 2.'
}
}
Built-in solutions
There are plenty other ways to implement the same functionality: create actual java bean with getters and setters, implement observable and observer interfaces on your own or use ready built-in solutions, for example IntegerProperty:
IntegerProperty intProperty = new SimpleIntegerProperty();
intProperty.addListener((observable, oldValue, newValue) -> {
if (!oldValue.equals(newValue) ) {
System.out.printf("Value has been changed from %d to %d!%n", oldValue.intValue(), newValue.intValue());
}
});
intProperty.setValue(1); // Output: Value has been changed from 0 to 1!
intProperty.setValue(2); // Output: Value has been changed from 1 to 2!
intProperty.setValue(2); // No output
System.out.println(intProperty.intValue()); // Output: 2
stopped changing
As for "stopped changing" listener, it's a little bit more complex issue. Depending on exact situation, there are several possible solutions I can think of:
1) if your loop is predictable and determined by you, just code the logic manually as it's required
/* listening for changes up there */
System.out.println("I'll go get some coffee");
Thread.sleep(60000); // stopped changing, eh?
/* do your stuff */
/* Continue listening for changes below */
2) if your loop is unpredictable but designed by you, you can try make it a little bit more predictable, design set of rules and protocols to follow, for example if new value is exactly zero, system will pause and switch to another task
3) you can also run background task which will periodically check last updated time, to determine if system is idle
There a lot of possible solutions to suggest, but I can't come up with something more specific without knowing more details
I have an JFormattedTextField created by
JFormattedTextField(NumberFormat.getInstance);
I would like to augment its behaviour, so that if the user
enters a zero and the field loses focus, it reacts exactly as if the user had entered e.g. "foo".
Using an InputVerifier somehow wrecked the reverting behaviour, and using a custom subclass of DecimalFormat did not revert when zero was entered, but instead cleared the field.
(By zero I mean, anything that parses to BigDecimal.ZERO.)
The code I used:
new DecimalFormat(){
{
setParseBigDecimal(true);
}
public Number parse(String txt, ParsePosition pos){
BigDecimal num = (BigDecimal) super.parse(txt, pos);
if(num == null || num.compareTo(BigDecimal.ZERO) == 0)
return null;
else return num;
}
Zero was not accepted then, but the field only reverted on letters entered.
You can add a FocusListener to do a similar check to what is done internally:
JFormattedTextField ftf = new JFormattedTextField(NumberFormat.getInstance());
ftf.addFocusListener(new FocusAdapter() {
#Override
public void focusLost(FocusEvent e) {
Object lastValid = ftf.getValue();
try {
ftf.commitEdit();
} catch (ParseException e1) {
ftf.setValue(lastValid);
}
Object newValue = ftf.getValue();
if (newValue instanceof Long)
if ((Long) newValue == 0l)
ftf.setValue(lastValid);
}
});
Notes on this approach:
A JFormattedTextField has a focusLostBehavior which instructs what to do on focus lost. I assumed it will always be COMMIT_OR_REVERT (the default).
Be careful if you registered a PropertyChangeListener to the text field, as I did not handle firing its events carefully. While inputs which can't be parsed will "immediately" be reverted (and not fire a PropertyChangeEvent event), a value which is parsed to 0 will first be committed (and fire a PropertyChangeEvent) and only then reverted (firing a PropertyChangeEvent again).
Notes on other approaches:
While I think this is the most direct approach, there are almost certainly other ways to accomplish this which involve extending and overriding some methods. The hierarchy from the text field itself to the format chosen for it can be a bit complicated and modification can be done in some of the steps, though care should be taken to not break anything.
Input verification can also work, but it behaves differently - it holds the focus until the input is verified, instead of allowing its loss and reverting. It is ultimately the programmer's choice.
Putting something similar to the code by #user1803551 into a PropertyChangeListener worked even better - before, there was an issue with hitting "return" when there was a default button in the form - it would receive "action performed" without the user having to change the input value. Plus, we get by without an additional variable.
DecimalFormat format = new DecimalFormat();
format.setParseBigDecimal(true);
JFormattedTextField ftf = new JFormattedTextField(format):
ftf.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("value"))
if ( ((BigDecimal) evt.getNewValue()).compareTo(BigDecimal.ZERO) ==0 )
ftf.setValue(evt.getOldValue());
}
});
I want to have a JSpinner that displays an non-patterened sequence of numbers (say, a sequence of prime numbers). This pattern is too complicated for a SpinnerNumberModel, so I decided to subclass SpinnerListModel. The constructor looks something like this:
public CustomSpinnerListModel() {
Vector<Integer> values = new Vector<Integer>();
values.add(1);
values.add(3);
values.add(5);
values.add(7);
this.setList(values);
}
This generates the model just fine and I can move through the values using the buttons on the JSpinner. However, typing a value in doesn't work. For instance, if the spinner is set to 3 and I type in 7, it remains at 3 (presumably because it doesn't think that 7 is a valid value). This works with the SpinnerNumberModel, so I'm not sure what's going on.
EDIT: I found out that if I save the numbers as string values, typing works. However, SpinnerNumberModel saves everything as Integers and that works too. So I'm not sure why my integers don't work, but SpinnerNumberModel's do.
I think the following solution is better than the suggestion to implement a Formatter, as it is not a formatting issue, but an issue of restricting the possible values, which should be the responsibility of the model. I had a similar problem and stumbling upon this threads solution, lead to a very ugly implementation. So hopefully what I came up with will keep you out of trouble.
This generates the model just fine and I can move through the values using the buttons on the JSpinner. However, typing a value in doesn't work. For instance, if the spinner is set to 3 and I type in 7, it remains at 3 (presumably because it doesn't think that 7 is a valid value). This works with the SpinnerNumberModel, so I'm not sure what's going on.
The Problem here is that setting a new model with setModel has the undocumented side effect of changing the JTextFieldEditor attribute depending on the type of the Model:
http://fuseyism.com/classpath/doc/javax/swing/JSpinner-source.html
By default, JSpinner uses a model of class SpinnerNumberModel with an editor of class DefaultNumberEditor. When you set the model to SpinnerListModel, it will instead use a ListEditor. In your case this is a bad choice, since it requires you to enter every prime number into a list to give it to the SpinnerListModel for input verification. Otherwise, as you pointed out, your input is ignored.
So the simple solution here is to subclass SpinnerNumberModel, which allows any number, instead of a specific list of values:
class PrimeNumberModel extends SpinnerNumberModel {
Object currentValue;
#Override
public Object getNextValue() {
return findNextPrimeFrom(currentValue);
}
#Override
public Object getPreviousValue() {
return findPreviousPrimeFrom(currentValue);
}
#Override
public void setValue(Object o) {
throwOnNonePrime(o); //Verify Input
super.setValue(o);
}
private void throwOnNonePrime(Object o) {
try {
int num = Integer.valueOf(o.toString());
if(!isPrime(num))
throw new IllegalArgumentException(o.toString());
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException(o.toString());
}
}
}
I think you could do it with strings and then use a method to get the number.
like this:
Spinner1(){
String[] values={"1","3","5","7"};
SpinnerModel model=new SpinnerListModel(values);
JSpinner spinner=new JSpinner(model);
}
int getValue(Object obj){
int out=0;
return out=Integer.parseInt((String)obj);
}