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());
}
});
Related
I am trying to attach an UndoableEditListener to a JTextPane or JTextArea that queues up edits into an UndoManager.
textPane.getDocument().addUndoableEditListener(new UndoableEditListener() {
#Override
public void undoableEditHappened(UndoableEditEvent event) {
undoQueue.addEdit(event.getEdit());
}
});
But undoableEditHappened is never called when I type "aaa" in the text window.
Thinking it's Java's fault, not mine, I crack AbstractDocument.class open with Eclipse debugger to watch the event trigger. It has a private listeners array. AbstractDocument stores all its listeners in odd indices in the listeners array, with the listeners' type Class<>'s in the even indices.
protected void fireUndoableEditUpdate(UndoableEditEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == UndoableEditListener.class) {
// Lazily create the event:
// if (e == null)
// e = new ListSelectionEvent(this, firstIndex, lastIndex);
((UndoableEditListener) listeners[i + 1]).undoableEditHappened(e);
}
}
}
See the line if (listeners[i] == UndoableEditListener.class)? When I add the undo change listener, the debugger shows listeners containing my listener, along with UndoableEditListener.class in the index before it. But, when the debugger comes to that if-statement, all the even indices in the array listeners show as DocumentListener.class in the debugger. Consequently, the if-statement is always false and the listener never called.
What the heck? Is this a Java 8 bug? Or am I missing a step the examples forgot to mention?
The problem was in the JTextPane. I was overriding its setText method to force it to call read, the alternative to setText that normalizes all kinds of newline while remembering them. But JTextPane.read appears to not trigger an UndoableEditEvent on the document.
If I leave setText alone, then UndoManager.undo works.
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.
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);
}
I have a jFormattedTextField in my program and I need to update a jLabel's text when jFormattedTextField value has been changed validly.
Actually jFormattedTextField gets a number and jLabel displays diffrence between this number and another number.
I currently do this by listenning to "FocusLost" event of jFormatted text.
How can i do this?
register a PropertyChangeListener for the property "value" to the formattedField
PropertyChangeListener l = new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
String text = evt.getNewValue() != null ? evt.getNewValue().toString() : "";
label.setText(evt.getNewValue());
}
};
formattedTextField.addPropertyChangeListener("value", l);
Do not use DocumentListener nor FocusListener: the former is notified too often (on every keytyped, before parsing happened) the latter is too brittle.
Probably the easiest way to do this is to use a javax.swing.event.DocumentListener that you attache to the text field. Then, as the user types, the label can be updated.
I don't remember the exact sequence, but the listener's insertUpdate() may be called before the formatted text field is validated. So, you may also need to check for valid numbers in your listener too.
Is it possible to know , from inside a ChangeListener receiving a ChangeEvent from a JSpinner,
which button (increment/decrement) has been pressed?
Short answer : No there's no way to know which button was pressed
Long answer : depending on your model and your change listener, if you do a comparison between the new value and the previous value, it is possible to know if the user went forward or backward.
You can inspect the object firing the event. Perhaps save the value prior to the event and determine whether it went up or down during the event.
Compare the actual value to the previous one. Here is how:
ChangeEvent ce = ...
((JSpinner)ce.getSource()).getPreviousValue();
You can check the new value against the old value by storing the old value:
int currentValue = spinner.getValue();
spinner.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent e) {
int value = spinner.getValue();
if(value > currentValue) {
// up was pressed
} else if(value < currentValue) {
// down was pressed
}
currentValue = value;
}
});
JSpinner is a composite component, it's possible to add mouseListeners to the components it contains. You'd have to experiment a bit to work out how to distinguish the buttons from one another and from the text field. One quick and dirty way would be to check their coordinates.
I'm not sure if you want to iterate over the components contained by the JSpinner itself, or those contained by the container returned by JSpinner.getEditor(), so try both.