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

我要求限制用户可以输入到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() { @Override public void changed(ObservableValue 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(); } } }); } } 

这确实按预期工作。 但是,假设对于特定的LengthLimitedTextFieldmaxCharacters设置为3.如果用户输入4个或更多字符,并尝试撤消(通过CTRL + Z或鼠标上下文菜单),我收到以下Exception ,文本为保持不变。

 java.lang.IndexOutOfBoundsException at javafx.scene.control.TextInputControl.replaceText(TextInputControl.java:368) at com.sun.javafx.scene.control.skin.TextFieldSkin.replaceText(TextFieldSkin.java:572) at com.sun.javafx.scene.control.behavior.TextFieldBehavior.replaceText(TextFieldBehavior.java:159) at com.sun.javafx.scene.control.behavior.TextInputControlBehavior$UndoManager.undo(TextInputControlBehavior.java:442) at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callAction(TextInputControlBehavior.java:137) at com.sun.javafx.scene.control.skin.TextInputControlSkin$ContextMenuItem$1.handle(TextInputControlSkin.java:595) at com.sun.javafx.scene.control.skin.TextInputControlSkin$ContextMenuItem$1.handle(TextInputControlSkin.java:593) at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:69) at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:217) at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:170) at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:37) at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92) at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:53) at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:28) at javafx.event.Event.fireEvent(Event.java:171) at javafx.scene.control.MenuItem.fire(MenuItem.java:456) at com.sun.javafx.scene.control.skin.ContextMenuContent$MenuItemContainer.doSelect(ContextMenuContent.java:1197) at com.sun.javafx.scene.control.skin.ContextMenuContent$MenuItemContainer$6.handle(ContextMenuContent.java:1148) at com.sun.javafx.scene.control.skin.ContextMenuContent$MenuItemContainer$6.handle(ContextMenuContent.java:1146) at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:69) at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:217) at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:170) at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:38) at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:37) at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92) at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35) at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92) at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35) at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92) at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:53) at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:33) at javafx.event.Event.fireEvent(Event.java:171) at javafx.scene.Scene$MouseHandler.process(Scene.java:3328) at javafx.scene.Scene$MouseHandler.process(Scene.java:3168) at javafx.scene.Scene$MouseHandler.access$1900(Scene.java:3123) at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1563) at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2265) at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:250) at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:173) at java.security.AccessController.doPrivileged(Native Method) at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:292) at com.sun.glass.ui.View.handleMouseEvent(View.java:528) at com.sun.glass.ui.View.notifyMouse(View.java:922) at com.sun.glass.ui.win.WinApplication._runLoop(Native Method) at com.sun.glass.ui.win.WinApplication.access$100(WinApplication.java:29) at com.sun.glass.ui.win.WinApplication$3$1.run(WinApplication.java:73) at java.lang.Thread.run(Thread.java:724) 

我不知道如何解决这个问题。 一个可能的(但不是理想的)解决方案是完全禁用Undo / Redo,但如果没有完全覆盖上下文菜单(根据这个SO答案 ,并不容易)和默认的键盘快捷键,这似乎是不可能的。

所以最终,我的问题是双重的:

有没有其他方法可以限制TextField的字符数而不会在撤消时抛出exception? 或者有一种干净的方法来完全禁用应用程序中的撤消?

编辑 :我做了一些更多的研究,根据https://javafx-jira.kenai.com/browse/RT-30881这似乎是一个错误。 看到这个评论 。 那么也许这是不可能实现的呢? 我将打开这个问题,希望有人确实有可能的解决方法。

在这里我将如何做:我会使用普通的文本字段,并添加一个事件filter。

设置:

 TextField tx = new TextField(); tx.addEventFilter(KeyEvent.KEY_TYPED, maxLength(3)); 

事件处理程序:

 public EventHandler maxLength(final Integer i) { return new EventHandler() { @Override public void handle(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 numeric_Validation(final Integer max_Lengh) { return new EventHandler() { @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 letter_Validation(final Integer max_Lengh) { return new EventHandler() { @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(); } } }; } /*****************************************************************************************/ 

祝你好运。 ^^

这是在JavaFX 8上使用Lambda Expressions的另一种解决方案

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

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

此方法允许TextField完成所有处理(复制/粘贴/撤消安全)。 不要重新制作扩展课程。 并允许您在每次更改后决定如何处理新文本(将其推送到逻辑,或者转回以前的值,甚至修改它)。

  // 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);