2013-10-04 3 views
5

У меня есть требование ограничить количество символов, которые пользователь может ввести в управление JavaFX TextField. Я продлил TextField такОграничение количества символов JavaFX TextField вызывает IndexOutOfBounds при Undo

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(); 
       } 
      } 
     }); 
    } 
} 

Это работает по назначению. Однако скажем, что для определенного LengthLimitedTextField значение maxCharacters равно 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 answer не просто) и сочетания клавиш по умолчанию.

Так в конечном счете, мой вопрос имеет два аспекта:

Есть, возможно, еще один способ, которым я могу ограничить количество символов в TextField, не бросая исключение на Undo? Или есть ли чистый способ полностью отключить Undo в приложении?

Редактировать: Я сделал еще несколько исследований и в соответствии с https://javafx-jira.kenai.com/browse/RT-30881 это, кажется, ошибка. См. this comment. Так, возможно, этого невозможно достичь? Я собираюсь оставить вопрос открытым в надежде, что у кого-то есть возможное обходное решение.

+0

Всегда придумал решение для этого? – Tommo

+1

Как правило, это плохая идея изменить состояние свойства во время прослушивания его изменения - вы можете уйти от него (свойства fx заботятся о циклах), но все же могут быть неприятные побочные эффекты (ошибка отменить может или не быть таким побочным эффектом, не копать). Если вы перешли к jdk8u40 и выше, [TextFormatter может прийти на помощь] (http://stackoverflow.com/a/33218556/203657) – kleopatra

ответ

4

Вот как бы я это сделал: Я бы использовал обычное текстовое поле и добавил фильтр событий.

установочном:

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

Обработчик:

public EventHandler<KeyEvent> maxLength(final Integer i) { 
     return new EventHandler<KeyEvent>() { 

      @Override 
      public void handle(KeyEvent arg0) { 

       TextField tx = (TextField) arg0.getSource(); 
       if (tx.getText().length() >= i) { 
        arg0.consume(); 
       } 

      } 

     }; 

    } 
+1

Когда я набираю первый символ Я получаю NullPointerException 'причина tx.getText() имеет значение null. Возможно, tx.getText()! = Null, поскольку первая проверка в if необходима. – Giorgio

+2

Это решение не мешает копированию и вставке строки дольше, чем i – Giorgio

3

добавив немного специй в 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(); 
      } 
     } 
    }; 
}  
/*****************************************************************************************/ 

удачи. ^^

0

Этот метод позволяет 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); 
+0

Это не сработало для меня. Все еще получает исключение IndexOutOfBoundsException. –

+0

@MichaelHaefele с 8u40 вы можете попробовать [используя TextFormatter] (http://stackoverflow.com/a/33218556/203657). – kleopatra

2

Вот еще одно решение с использованием лямбда-выражений на JavaFX 8

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

Если длина TextField более чем 5 не вставить больше текста.

Смежные вопросы