限制JavaFX TextField的字符数会导致撤消时出现IndexOutOfBounds

限制JavaFX TextField的字符数会导致撤消时出现IndexOutOfBounds,java,text,javafx-2,javafx-8,indexoutofboundsexception,Java,Text,Javafx 2,Javafx 8,Indexoutofboundsexception,我需要限制用户可以输入到TextFieldJavaFX控件中的字符数。我像这样扩展了TextField public class LengthLimitedTextField extends TextField { /** * @param maxCharacters The max allowed characters that can be entered into this {@link TextField}. */ public LengthLimit

我需要限制用户可以输入到
TextField
JavaFX控件中的字符数。我像这样扩展了
TextField

public class LengthLimitedTextField extends TextField {
    /**
     * @param maxCharacters The max allowed characters that can be entered into this {@link TextField}.
     */
    public LengthLimitedTextField(final int maxCharacters) {
        final TextField thisField = this;
        this.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable,
                                String oldValue, String newValue) {
                // Force correct length by deleting the last entered character if text is longer than maxCharacters
                if (newValue.length() > maxCharacters) {
                    thisField.deleteNextChar();
                }
            }
        });
    }
}
我不知道如何解决这个问题。一个可能的(但不是理想的)解决方案是完全禁用撤消/重做,但如果不完全覆盖上下文菜单(根据这一点,这并不容易)和默认的键盘快捷键,这似乎是不可能的

因此,归根结底,我的问题有两个:

是否有其他方法可以限制
TextField
的字符数,而不在撤消时引发异常?还是有一种干净的方法可以完全禁用应用程序中的撤消

编辑:我做了一些进一步的研究,根据研究结果,这似乎是一个bug。看见那么也许这是不可能实现的呢?我将把这个问题留待讨论,希望有人能找到解决办法

以下是我的做法: 我将使用一个普通的文本字段,并添加一个事件过滤器

设置:

事件处理程序:

public EventHandler maxLength(最终整数i){
返回新的EventHandler(){
@凌驾
公共无效句柄(KeyEvent arg0){
TextField tx=(TextField)arg0.getSource();
if(tx.getText().length()>=i){
arg0.consume();
}
}
};
}

在Magcus代码中添加一点香料

@FXML
private TextField txt_Numeric;
@FXML
private TextField txt_Letters;

@Override
public void initialize(URL url, ResourceBundle rb) {
    /* add Event Filter to your TextFields **************************************************/
    txt_Numeric.addEventFilter(KeyEvent.KEY_TYPED , numeric_Validation(10));
    txt_Letters.addEventFilter(KeyEvent.KEY_TYPED , letter_Validation(10));
}

/* Numeric Validation Limit the  characters to maxLengh AND to ONLY DigitS *************************************/
public EventHandler<KeyEvent> numeric_Validation(final Integer max_Lengh) {
    return new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent e) {
            TextField txt_TextField = (TextField) e.getSource();                
            if (txt_TextField.getText().length() >= max_Lengh) {                    
                e.consume();
            }
            if(e.getCharacter().matches("[0-9.]")){ 
                if(txt_TextField.getText().contains(".") && e.getCharacter().matches("[.]")){
                    e.consume();
                }else if(txt_TextField.getText().length() == 0 && e.getCharacter().matches("[.]")){
                    e.consume(); 
                }
            }else{
                e.consume();
            }
        }
    };
}    
/*****************************************************************************************/

 /* Letters Validation Limit the  characters to maxLengh AND to ONLY Letters *************************************/
public EventHandler<KeyEvent> letter_Validation(final Integer max_Lengh) {
    return new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent e) {
            TextField txt_TextField = (TextField) e.getSource();                
            if (txt_TextField.getText().length() >= max_Lengh) {                    
                e.consume();
            }
            if(e.getCharacter().matches("[A-Za-z]")){ 
            }else{
                e.consume();
            }
        }
    };
}    
/*****************************************************************************************/
@FXML
私有文本字段txt_数字;
@FXML
私有文本字段txt_字母;
@凌驾
公共void初始化(URL、ResourceBundle rb){
/*将事件筛选器添加到文本字段**************************************************/
txt_Numeric.addEventFilter(KeyEvent.KEY_类型,数字_验证(10));
txt_Letters.addEventFilter(键入KeyEvent.KEY,字母验证(10));
}
/*数字验证将字符限制为最大长度,且仅限数字*************************************/
公共事件处理程序数字验证(最终整数最大长度){
返回新的EventHandler(){
@凌驾
公共无效句柄(KeyEvent e){
TextField txt_TextField=(TextField)e.getSource();
如果(txt_TextField.getText().length()>=max_Lengh){
e、 消费();
}
如果(e.getCharacter()匹配(“[0-9.]”){
如果(txt_TextField.getText()包含(“.”)和&e.getCharacter()匹配(“[.””){
e、 消费();
}如果(txt_TextField.getText().length()==0&&e.getCharacter().matches(“[.]”),则为else{
e、 消费();
}
}否则{
e、 消费();
}
}
};
}    
/*****************************************************************************************/
/*字母验证将字符限制为maxLengh且仅限于字母*************************************/
公共事件处理程序字母验证(最终整数最大长度){
返回新的EventHandler(){
@凌驾
公共无效句柄(KeyEvent e){
TextField txt_TextField=(TextField)e.getSource();
如果(txt_TextField.getText().length()>=max_Lengh){
e、 消费();
}
如果(e.getCharacter()匹配(“[A-Za-z]”){
}否则{
e、 消费();
}
}
};
}    
/*****************************************************************************************/

祝你好运^^

此方法允许TextField完成所有处理(复制/粘贴/撤消安全)。 不要求进行扩展类。 并允许您在每次更改后决定如何处理新文本 (将其推送到逻辑,或返回到上一个值,甚至修改它)

对于您的情况,只需在内部添加此逻辑即可。工作完美

    // For example 10 characters     
  if (newValue.length() >= 10) ((StringProperty)observable).setValue(oldValue);

下面是另一个在JavaFX8上使用Lambda表达式的解决方案

textField.textProperty().addListener(
        (observable,oldValue,newValue)-> {
            if(newValue.length() > 5) cp.setText(oldValue);
        }
);

如果textField长度大于5,则不会插入更多文本。

当我键入第一个字符时,我获得NullPointerException'因为tx.getText()为null。可能是tx.getText()!=如果有必要,则第一次签入时为null。此解决方案不会阻止复制和粘贴比我不适用的长度更长的字符串。仍然获得IndexOutOfBoundsException。@MichaelHaefele,从8u40开始,您可能会尝试。是否想出解决方案?通常,在听取属性更改的同时修改属性的状态是个坏主意-您可能会侥幸逃脱(fx属性会处理循环),但仍然可能会有令人讨厌的副作用(撤销错误可能是或不是这样的副作用,没有挖掘)如果您转到jdk8u40及更高版本,则
@FXML
private TextField txt_Numeric;
@FXML
private TextField txt_Letters;

@Override
public void initialize(URL url, ResourceBundle rb) {
    /* add Event Filter to your TextFields **************************************************/
    txt_Numeric.addEventFilter(KeyEvent.KEY_TYPED , numeric_Validation(10));
    txt_Letters.addEventFilter(KeyEvent.KEY_TYPED , letter_Validation(10));
}

/* Numeric Validation Limit the  characters to maxLengh AND to ONLY DigitS *************************************/
public EventHandler<KeyEvent> numeric_Validation(final Integer max_Lengh) {
    return new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent e) {
            TextField txt_TextField = (TextField) e.getSource();                
            if (txt_TextField.getText().length() >= max_Lengh) {                    
                e.consume();
            }
            if(e.getCharacter().matches("[0-9.]")){ 
                if(txt_TextField.getText().contains(".") && e.getCharacter().matches("[.]")){
                    e.consume();
                }else if(txt_TextField.getText().length() == 0 && e.getCharacter().matches("[.]")){
                    e.consume(); 
                }
            }else{
                e.consume();
            }
        }
    };
}    
/*****************************************************************************************/

 /* Letters Validation Limit the  characters to maxLengh AND to ONLY Letters *************************************/
public EventHandler<KeyEvent> letter_Validation(final Integer max_Lengh) {
    return new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent e) {
            TextField txt_TextField = (TextField) e.getSource();                
            if (txt_TextField.getText().length() >= max_Lengh) {                    
                e.consume();
            }
            if(e.getCharacter().matches("[A-Za-z]")){ 
            }else{
                e.consume();
            }
        }
    };
}    
/*****************************************************************************************/
  // fired by every text property change
textField.textProperty().addListener(
  (observable, oldValue, newValue) -> {
    // Your validation rules, anything you like
      // (! note 1 !) make sure that empty string (newValue.equals("")) 
      //   or initial text is always valid
      //   to prevent inifinity cycle
    // do whatever you want with newValue

    // If newValue is not valid for your rules
    ((StringProperty)observable).setValue(oldValue);
      // (! note 2 !) do not bind textProperty (textProperty().bind(someProperty))
      //   to anything in your code.  TextProperty implementation
      //   of StringProperty in TextFieldControl
      //   will throw RuntimeException in this case on setValue(string) call.
      //   Or catch and handle this exception.

    // If you want to change something in text
      // When it is valid for you with some changes that can be automated.
      // For example change it to upper case
    ((StringProperty)observable).setValue(newValue.toUpperCase());
  }
);
    // For example 10 characters     
  if (newValue.length() >= 10) ((StringProperty)observable).setValue(oldValue);
textField.textProperty().addListener(
        (observable,oldValue,newValue)-> {
            if(newValue.length() > 5) cp.setText(oldValue);
        }
);