Im trying to prevent backspace from deleting characters in a javafx textarea. KeyEvent.consume should do this but it doesn't prevent the key event from happening. It does set the property of the key event and KeyEvent.isConsumed() gives back true after consuming the event. The backspace still gets executed. I tried other keys and there is the same effect.
This is my current fxcontroller class:
public class Terminal {
#FXML
private TextArea ta_console;
#FXML
private AnchorPane ap_main;
#FXML
protected void initialize(){
ta_console.setText(">");
ta_console.positionCaret(1);
}
public void keyStroke(KeyEvent keyEvent) {
if (keyEvent.getCode() == KeyCode.BACK_SPACE || keyEvent.getCode() == KeyCode.DELETE) {
keyEvent.consume();
}
}
}
The keyStroke method gets executed by the textarea on key pressed.
Am i missing something or is consume bugged or somehow not functional the way the documentation says? Is there a way i can still get the desired outcome?
If you want to prevent the user deleting text, that should be done with a TextFormatter instead of trying to guess which key events the control is handling internally, and in what order all those events are handled, etc. etc., for its default behavior.
It's not clear exactly what you want to achieve, but here is an example, which prevents any deletion (or replacement) of text:
public class Terminal {
#FXML
private TextArea taConsole;
#FXML
protected void initialize(){
taConsole.setText(">");
taConsole.positionCaret(1);
taConsole.setTextFormatter(new TextFormatter(change -> {
if (change.getRangeStart() == change.getRangeEnd()) { // nothing deleted
return change ;
}
// change represents a deletion of some text, veto it:
return null ;
});
}
}
See the Javadocs for TextFormatter.Change for details.
Related
How can I enable my CheckBox when I type into the TextField a value other than zero?
By default, I've set my CheckBox to disable in the Scene Builder.
In my MainController class:
#FXML
private CheckBox cb1;
#FXML
private TextField txt1;
public void enableCB() {
if (txt1.getText() == "0") {
cb1.setDisable(true);
} else {
cb1.setDisable(false);
}
}
In Scene Builder I've set the "enableCb Method" to the On Action and On Key Typed, but still it did not provide the right condition and output.
This can be accomplished with a fairly simple, one-statement binding:
cb1.disableProperty().bind(
txt1.textProperty().isEmpty()
.or(txt1.textProperty().isEqualTo("0")));
This will enable the CheckBox only if a value other than "0" has been entered into the TextField. So it will be disabled if the text is either empty or "0".
I suggest writing a ChangeListener for the text property of the TextField.
txt1.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable,
String oldValue,
String newValue) {
if ("0".equals(newValue)) {
cb1.setDisable(true);
}
else {
cb1.setDisable(false);
}
}
});
Every time the text in the TextField is changed, the above code will execute, regardless of how the text is changed.
Also note that the way to compare different strings in Java is with method equals and not with the equals operator, i.e. ==.
I'm playing around with JavaFX and created a login form. So I want to test the inevitable, insert usernmae and password to check if correct. The problem is that it doesn't run as expected. It will always go to the else body no matter what. Code below:
public class Controller {
#FXML PasswordField pass;
#FXML TextField name;
#FXML javafx.scene.control.Button login;
#FXML Hyperlink signup;
#FXML Label errormessage;
#FXML private void buttonPressed() {
checkUser();
}
#FXML private void ifEnterIsPressed(KeyEvent k) {
if (k.getCode() == KeyCode.ENTER)
checkUser();
}
#FXML private void checkUser() {
System.out.println(name.getCharacters());
System.out.println(pass.getCharacters());
if (name.getCharacters().equals("Marios") && pass.getCharacters().equals("19981998")) {
errormessage.setVisible(false);
System.out.println("Access granted!");
}
else {
errormessage.setText("Wrong username or password");
System.out.println("Access denied");
}
}
}
I don't know if I'm missing something completely obvious here or what...
Thanks in advance!
The issue is your use of the TextField.getCharacters() method. This isn't the same thing as calling TextField.getText(). Currently, for Java 10, the method TextField.getCharacters() returns a StringBuilder (which is a CharSequence) rather than a String. This is can be expected because the Javadoc states:
Returns the character sequence backing the text field's content.
By "content" it most likely means the backing TextInputControl.Content and the source code of TextField seems to confirm this. What you want to compare, however, is the String contained in the textProperty() of the TextField.
Apparently, and I didn't know this (though its understandable), a StringBuilder does not equals a String even if they contain the same characters. Or, to put it in code:
StringBuilder sb = new StringBuilder("Hello, World!");
String s = "Hello, World!";
sb.equals(s); // FALSE
sb.toString().equals(s); // TRUE
So, to answer directly, change name.getCharacters() and pass.getCharacters() to name.getText() and pass.getText(), respectively.
I have an EventHandler that I set as an event filter on TextFields. When I write the class, I get the source TextField by calling getSource() on the event and casting it to a TextField.
The code for the EventHandler:
public class NumberFilter implements EventHandler<KeyEvent> {
public final int maxLength;
public NumberFilter(int maxLength) {
this.maxLength = maxLength;
}
#Override
public void handle(KeyEvent event) {
TextField textField = (TextField) event.getSource(); //<-- is this cast okay?
//consume event if there is too much text or the input isn't a number.
if (textField.getText().length() >= maxLength || !event.getCharacter().matches("[0-9]")) {
event.consume();
}
}
}
Is this cast okay by standard java conventions? How can I write the class so that it can't be used anywhere except as an event filter for a TextField?
Andy Turner's answer provides a robust general approach to allowing event handlers to be added to only one type of Node. However, for the specific case of vetoing changes to the text in a TextField (or other text input control), the approach of using key event handlers is not a good one for the following reasons:
The user can bring up a context menu with the mouse and paste text in. This doesn't involve any key presses at all, so your handler won't be invoked.
You have no control over which type of key events the text field uses internally. Are you registering this filter with KEY_PRESSED, KEY_RELEASED, or KEY_TYPED events? Are you sure the events used internally by the text field will remain the same from one JavaFX release to the next?
You will likely inadvertently veto keyboard shortcuts such as Ctrl-C (for copy) or Ctrl-V (for paste), and similar. (If you don't veto shortcuts for "paste", you allow another loophole for the user to paste invalid text...). Again, it's possible a future release of JavaFX may introduce additional shortcuts, which it's virtually impossible to proof your functionality against.
For completeness, the preferred approach for this particular use case is as follows:
Use a TextFormatter, which is the supported mechanism for vetoing or modifying text entry to a text input control (as well as providing mechanisms to format or parse text in the control). You can make this reusable by implementing the filter in a standalone class:
public class NumberFilter implements UnaryOperator<TextFormatter.Change> {
private final Pattern pattern ;
public NumberFilter(int maxLength) {
pattern = Pattern.compile("[0-9]{0,"+maxLength+"}");
}
#Override
public TextFormatter.Change apply(TextFormatter.Change c) {
String newText = c.getControlNewText() ;
if (pattern.matcher(newText).matches()) {
return c ;
} else {
return null ;
}
}
}
And now you can do
TextField textField = new TextField();
textField.setTextFormatter(new TextFormatter<String>(new NumberFilter(5)));
Just to expand on my comment on #MaxPower's answer:
Don't use inheritance to do something which you can more cleanly do with composition.
I think that #James_D's approach is better in this case; but if in general you want an EventHandler which can only be added to a certain type of field, enforce this through your API:
public class NumberFilter implements EventHandler<KeyEvent> {
public static void addTo(int maxLength, TextField textField) {
textField.addEventHandler(new NumberFilter(maxLength));
}
private NumberFilter(int maxLength) {
// Private ctor means that you can't just create one of these
// however you like: you have to create it via the addTo method.
}
// Now casting in the handle() method is safe.
}
In this way, the only means of creating the NumberFilter is via the addTo method; and that requires that you're adding it to a TextField.
Casts are a way of you telling the compiler that you know more then it does.
If you know that every time this piece of code gets called it will be from a TextField than it is okay. Otherwise, I would do
try {
TextField textField = (TextField) event.getSource();
//Do Stuff
}
catch(ClassCastException e) {
//handle the error
}
or if you want a little more type safety
if(event.getSource() instanceof TextField) {
TextField textField = (TextField) event.getSource();
}
Or better yet
public class MyTextField extends TextField implements EventHandler<KeyEvent> {
}
then place use this instead of TextField and add your method, then it's type safe.
i am trying to add multiple text fields in to a single focus listener to be executed, currently i have added a text field but i cant figure out how to add multiple text fields to it,
i think its way better than creating a focus listener for each textfield.
Usually tha anser is: "it depends..."
but in your case Id say: each textfield should have its own listener (instance).
The reason is that you change the textfields content.
When you do this in only one Listener instance then you either change all the textfields any time the event occurs regarless of which textfield was affected, or you end up with a if/else cascade needing a new entry if you add another textfield.
I suggest that you create a named inner class for the Listener giving it a Textfield as parameter:
class MyTextFiledFocusListener implements FocusListener {
private final JTextFiled textField;
MyTextFiledFocusListener(JTextFiled textField){
this.textField = textField;
textField.addFocusListener(this);
}
public void focusGained(FocusEvent e)
{
textField.setText("");
}
public void focusLost(FocusEvent e)
{
// nothing
}
}
and the bit you posted changes to:
new MyTextFiledFocusListener(JT_id);
Just for the records:
quite a lot Swing related interfaces with more than one method have default implementations with empty methods (the pre java8 solution for default methods) so does FocusListener. These default implementation are usually called *Adapter. Therefore you could shorten your code when you extend FocusAdapter insted of implementing FocusListener:
class MyTextFiledFocusListener extends FocusAdapter {
private final JTextFiled textField;
MyTextFiledFocusListener(JTextFiled textField){
this.textField = textField;
textField.addFocusListener(this);
}
public void focusGained(FocusEvent e)
{
textField.setText("");
}
}
What about something like this?
FocusListener clearFields = new FocusListener() {
public void focusGained(FocusEvent e)
{
JT_id.setText("");
JT_name.setText("");
JT_add.setText("");
JT_cno.setText("");
JT_email.setText("");
}
public void focusLost(FocusEvent e)
{
// nothing
}
});
JT_id.addFocusListener(clearFields);
JT_name.addFocusListener(clearFields);
JT_add.addFocusListener(clearFields);
JT_cno.addFocusListener(clearFields);
JT_email.addFocusListener(clearFields)
You will still need to add a focus listener to all of the text-fields to avoid the situation where clicking/focusing on the first one (JT_id) will clear all of them while clicking/focusing on the others would do nothing.
Or, you know, the alternative - assign each textfield a focuslistener that only clears that field.
I am trying to get a TextArea to autoscroll to the bottom with new text which is put in via an event handler. Each new entry is just one long string of text with each entry separated by a line break. I have tried a change handler which sets setscrolltop to Double.MIN_VALUE but to no avail. Any ideas of how this could be done?
You have to add a listener to the TextArea element to scroll to the bottom when it's value is changed:
#FXML private TextArea txa;
...
txa.textProperty().addListener(new ChangeListener<Object>() {
#Override
public void changed(ObservableValue<?> observable, Object oldValue,
Object newValue) {
txa.setScrollTop(Double.MAX_VALUE); //this will scroll to the bottom
//use Double.MIN_VALUE to scroll to the top
}
});
But this listener is not triggered when you use the setText(text) method, so if you want to trigger it after a setText(text) use the appendText(text) right after it:
txa.setText("Text into the textArea"); //does not trigger the listener
txa.appendText(""); //this will trigger the listener and will scroll the
//TextArea to the bottom
This sounds more like a bug, once the setText() should trigger the changed listener, however it doesn't. This is the workaround I use myself and hope it helps you.
txa.appendText("") will scroll to the bottom without a listener. This becomes an issue if you want to scroll back and the text is being constantly updated. txa.setText("") puts the scroll bar back at the top and same issue applies.
My solution was to extend the TextArea class, ammend the FXML tag from textArea to LogTextArea. Where this works, it clearly causes problems in scene builder as it does not know what this component is
import javafx.scene.control.TextArea;
import javafx.scene.text.Font;
public class LogTextArea extends TextArea {
private boolean pausedScroll = false;
private double scrollPosition = 0;
public LogTextArea() {
super();
}
public void setMessage(String data) {
if (pausedScroll) {
scrollPosition = this.getScrollTop();
this.setText(data);
this.setScrollTop(scrollPosition);
} else {
this.setText(data);
this.setScrollTop(Double.MAX_VALUE);
}
}
public void pauseScroll(Boolean pause) {
pausedScroll = pause;
}
}
I don't have enough reputation to comment, but wanted to give some insight for future readers as to why setText doesn't appear to trigger the listener, but appendText does, as in Math's answer.
I Just found this answer while encountering similar issues myself, and looked into the code. This is currently the top result for 'javafx textarea settext scroll' in a google search.
setText does indeed trigger the listener.
According to the javadoc on the doSet method in TextInputControl (TextArea's superclass):
* doSet is called whenever the setText() method was called directly
* on the TextInputControl, or when the text property was bound,
* unbound, or reacted to a binding invalidation. It is *not* called
* when modifications to the content happened indirectly, such as
* through the replaceText / replaceSelection methods.
Inside the doSet method, a call is made to updateText(), which TextArea overrides:
#Override final void textUpdated() {
setScrollTop(0);
setScrollLeft(0);
}
So, when you set the scroll amount in the listener as in Math's answer, the following happens:
The TextProperty is updated
Your listener is called, and the scroll is set
doSet is called
textUpdated is called
The scroll is set back to the top-left
When you then append "",
The TextProperty is updated
Your listener is called, and the scroll is set
The javadoc is above is clear why this is the case - doSet is only called when using setText.
In fact, appendText calls insertText which calls replaceText - and the javadoc further states that replaceText does NOT trigger a call to doSet.
The behaviour is rather irritating, especially since these are all final methods, and not obvious at first glance - but is not a bug.
Alternative to that strange setText bug without using appendText
textArea.selectPositionCaret(textArea.getLength());
textArea.deselect(); //removes the highlighting
One addendum I would add to jamesarbrown's response would be to this would be to use a boolean property instead so you can access it from within FXML.
Something like this.
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.TextArea;
public class LogTextArea extends TextArea {
private final BooleanProperty pausedScrollProperty = new SimpleBooleanProperty(false);
private double scrollPosition = 0;
public LogTextArea() {
super();
}
public void setMessage(String data) {
if (isPausedScroll()) {
scrollPosition = this.getScrollTop();
this.setText(data);
this.setScrollTop(scrollPosition);
} else {
this.setText(data);
this.setScrollTop(Double.MAX_VALUE);
}
}
public final BooleanProperty pausedScrollProperty() { return pausedScrollProperty; }
public final boolean isPausedScroll() { return pausedScrollProperty.getValue(); }
public final void setPausedScroll(boolean value) { pausedScrollProperty.setValue(value); }
}
However, the problem with this answer is that if you get flooded with an unreasonably large amount of input (as can happen when retrieving a log from an IO Stream) the javaFX thread will lock up because the TextArea gets too much data.
As Matthew has posted the setText call is the problem. A easy workaround is to call clear, appendText and then setScrollTop. The other suggestions above did not work well for me, with enough delay it worked but was unreliable behaviour.
textAreaListener = (observable, oldValue, newValue) -> {
textArea.clear();
textArea.appendText(newValue);
textArea.setScrollTop(Double.MAX_VALUE);
};