title: JavaFX: custom TextArea for hex encoded content
body: <p>I am currently trying to create a custom TextArea in JavaFX that makes it easy to input hex-encoded content. The main constraints are:</p>
  
  <ol>
  <li>The text is linked to a <code>Property&lt;byte[]&gt;</code> that contains the binary equivalent of the string (e.g. <code>"ff"</code> is stored as <code>{0xff}</code>.</li>
  <li>The only valid characters are hex digits (<code>0123456789abcdef</code>). Only for presentation reasons, upper case digits are turned to lower case (e.g. <code>1Bd5</code> is displayed as <code>1bd5</code>).</li>
  <li><strong>The area displays the hex string in blocks of two characters.</strong></li>
  </ol>
  
  <p>The third requirement is where I have found most of the problems. What I have done so far is to add a <code>changeListener</code> to the area's <code>textProperty</code>, so that every time the text on the field is changed, the format is preserved. The code looks like this:</p>
  
  <pre><code>textProperty().addListener(new ChangeListener&lt;String&gt;() {
      @Override
      public void changed(ObservableValue&lt;? extends String&gt; observable, String oldValue, String newValue) {
          String correctedNewValue = validated(newValue);
          if (!correctedNewValue .equals(newValue)) 
              textProperty().setValue(correctedNewValue);
      }
  });
  </code></pre>
  
  <p>Where <code>validated(String)</code> returns a <code>String</code> that fulfills the format requirements of the component. This works like a charm when a new character is inserted: the rules are enforced and the format is preserved without the user having to type the spaces manually. However, when content is deleted and I try to delete the spaces automatically, a nasty exception is produced:
      </p>
  
  <pre><code>Exception in thread "JavaFX Application Thread" java.lang.IndexOutOfBoundsException
      at javafx.scene.control.TextArea$TextAreaContent.insert(TextArea.java:136)
      at javafx.scene.control.TextInputControl.replaceText(TextInputControl.java:1204)
      at javafx.scene.control.TextInputControl.updateContent(TextInputControl.java:556)
      at javafx.scene.control.TextInputControl.replaceText(TextInputControl.java:548)   
      at javafx.scene.control.TextInputControl.deleteText(TextInputControl.java:496)
      at javafx.scene.control.TextInputControl.deletePreviousChar(TextInputControl.java:899)
      at com.sun.javafx.scene.control.skin.TextAreaSkin.deleteChar(TextAreaSkin.java:1351)
      at com.sun.javafx.scene.control.behavior.TextAreaBehavior.deleteChar(TextAreaBehavior.java:274)
      at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.deletePreviousChar(TextInputControlBehavior.java:311)
      at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callAction(TextInputControlBehavior.java:143)
      at com.sun.javafx.scene.control.behavior.TextAreaBehavior.callAction(TextAreaBehavior.java:259)
      at com.sun.javafx.scene.control.behavior.BehaviorBase.callActionForEvent(BehaviorBase.java:218)
      at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callActionForEvent(TextInputControlBehavior.java:127)
      at com.sun.javafx.scene.control.behavior.BehaviorBase.lambda$new$74(BehaviorBase.java:135)
      at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
      at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
      at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
      at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
      at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
      at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
      at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
      at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
      at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
      at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
      at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
      at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
      at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
      at javafx.event.Event.fireEvent(Event.java:198)
      at javafx.scene.Scene$KeyHandler.process(Scene.java:3964)
      at javafx.scene.Scene$KeyHandler.access$1800(Scene.java:3910)
      at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040)
      at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501)
      at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:217)
      at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:149)
      at java.security.AccessController.doPrivileged(Native Method)
      at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$353(GlassViewEventHandler.java:248)
      at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
      at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:247)
      at com.sun.glass.ui.View.handleKeyEvent(View.java:546)
      at com.sun.glass.ui.View.notifyKey(View.java:966)
      at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
      at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
      at java.lang.Thread.run(Thread.java:745)
  </code></pre>
  
  <p>What I have seen so far is that a property should not be changed within the listener that detects changes on the same property, and some sources suggest to use <code>Platform.runLater</code>. However, this is very erratic (the caret changes position -usually to the beginning of the field-, and sometimes the focus gets lost).</p>
  
  <p>My question is: what is the best way to go about this hex field? How can I update the textProperty correctly? (I would be willing to use an existing component as well, but since I did not find it, I figured I could try to build it myself).</p>