I have the requirement to limit the number of characters a user can input into a TextField
JavaFX control. I have extended TextField
like so
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();
}
}
});
}
}
This does work as intended. However, lets say that for a particular LengthLimitedTextField
the maxCharacters
is set to 3. If the user enters 4 or more characters, and attempts to Undo (either via CTRL+Z or mouse context menu), I receive the following Exception
and the text is left unchanged.
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)
I'm not sure how I can resolve this. A possible (but not ideal) solution was to disable Undo/Redo completely, but that doesn't appear possible without completely override the context menu (which according to this SO answer, isn't easy) and the default keyboard shortcuts.
So ultimately, my question is twofold:
Is there possibly another way I can limit the number of characters of a TextField
without throwing an exception on Undo? Or is there a clean way to completely disable Undo in the application?
Edit: I did some more research and according to https://javafx-jira.kenai.com/browse/RT-30881 this appears to be a bug. See this comment. So perhaps this isn't possible to achieve then? I'm going to leave the question open in hopes someone does have a possible workaround.
This method let TextField to finish all processing (copy/paste/undo safe). Do not requares to make extending class. And allow you to deside what to do with new text after every change (to push it to logic, or turn back to previous value, or even to modify it).
For your case just add this logic inside. Works perfectly.
Here how I would do it: I would use a normal textfield, and would add an event filter.
The set up:
The event handler:
adding a little spices to Magcus code
best of luck. ^^
Here is another solution using Lambda Expressions on JavaFX 8
If textField length is more than 5 it doesn't insert more text.