ScalaFX 8u40 – TextFormatter – part 2

JavaFX 8.0_u40 brings several enhancements, the new ScalaFX release 8.0.40 supports them the. The new Spinner and the basic use of TextFormatter were described in previous blogs. ScalaFX use of Dialogs and Alerts will be described in following posts.

TextFormatter restricts and formats the text that can be displayed and typed in a TextInputControl. TextFormatter has one or both:

  • Filter – intercepts and can modify or reject text changes,
  • ValueConverter – a StringConverter that converts between the values and text. There are several predefined converters in scalafx.util.converter package.

In the first part we presented a simple scenario where we specify a string converter to display TextField content as currency. Here we will look at an example that uses a filter and a converter together.

Imagine, for the sake of this example, that we have a TextField that should contain one part of the text that is fixed and the rest that can be freely edited. User types messages in that text field and presses Enter to send. The restriction is that that text field always contains a prompt: > in front of the message:

011 - input

User should be able to freely edit the message, but should not be able to delete the prompt or move caret on or in front of the prompt. As in the previous example a converter is used to ensure that there is a prompt in front of the message when content of the text field is set. However the converter is not activated while user is editing content of a text input field, only when the value is set. TextFormatter’s filter gives you ability to intercept and modify any change done to the text content. It is called as changes happen before they are applied or displayed on the screen. The filter is a function that takes as an input change event; it can modify that it before it returns it back:

def filter(change:Change) : Change

In our case we will look for changes that would remove prompt or misplace the caret.

val prompt = "> "
...  
val filter: (Change) => Change = { change: Change =>
  // Restore prompt if part was deleted
  if (change.controlNewText.length <= prompt.length) {
    change.text = prompt.substring(change.controlNewText.length)
  }
  // Restore caret position if it moved over the prompt
  if (change.anchor < prompt.length) { 
    change.anchor = prompt.length
  }
  if (change.caretPosition < prompt.length) {
    change.caretPosition = prompt.length
  }
  change
}

As in the previous example we will use a custom text converter that will add prompt when text is set:

case class Message(text: String) {
  override def toString = s""$text""
}

val converter = new StringConverter[Message] {
  override def fromString(s: String): Message = {
    val r = if (s.startsWith(prompt)) s.substring(prompt.length)
            else s
      Message(r)
  }
  override def toString(v: Message): String = prompt + v.text
}

Now we use our converter and filter to create a text formatter, with some default content:

val formatter = new TextFormatter[Message](converter, Message("hello"), filter)

And the text field with our text formatter and action that sends the messages when Enter is pressed.

val textField = new TextField {
  text = prompt
  textFormatter = formatter
  onAction = (a: ActionEvent) => {
    val str = text()
    val message = converter.fromString(str) + "\n"
    outputTextArea.text = message + outputTextArea.text()
    text() = ""
  }
}

Here is the complete example:

011 - input-output

import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.event.ActionEvent
import scalafx.geometry.Insets
import scalafx.scene.Scene
import scalafx.scene.control.TextFormatter.Change
import scalafx.scene.control.{Label, TextArea, TextField, TextFormatter}
import scalafx.scene.layout.{BorderPane, VBox}
import scalafx.util.StringConverter

object TextFormatterWithChangeFilterDemo extends JFXApp {

  case class Message(text: String) {
    override def toString = '"' + text + '"'
  }

  val prompt = "> "

  val converter = new StringConverter[Message] {
    override def fromString(s: String): Message = {
      val r =
        if (s.startsWith(prompt)) s.substring(prompt.length)
        else s
      Message(r)
    }
    override def toString(v: Message): String = {
      prompt + v.text
    }
  }

  // Filter the change restoring prompt if it was removed and 
  // correcting caret position
  val filter: (Change) => Change = { change: Change =>
    // Restore prompt if part was deleted
    if (change.controlNewText.length <= prompt.length) {
      change.text = prompt.substring(change.controlNewText.length)
    }
    // Restore caret position if it moved over the prompt
    if (change.anchor < prompt.length) change.anchor = prompt.length
    if (change.caretPosition < prompt.length) change.caretPosition = prompt.length
    change
  }
  val formatter = new TextFormatter[Message](converter, Message("hello"), filter)

  val outputTextArea = new TextArea {
    editable = false
    focusTraversable = false
  }

  val textField = new TextField {
    text = prompt
    textFormatter = formatter
    onAction = (a: ActionEvent) => {
      val str = text()
      val message =
        if (outputTextArea.text().length == 0)
          converter.fromString(str)
        else
          converter.fromString(str) + "n"
      outputTextArea.text = message + outputTextArea.text()
      text() = ""
    }
  }

  stage = new PrimaryStage {
    scene = new Scene(300, 200) {
      title = "TextFormatter Demo"
      root = new VBox {
        spacing = 6
        padding = Insets(10)
        children = Seq(
          new BorderPane {
            top = textField
            center = outputTextArea
          }
        )
      }
    }
  }
}

The TextFormatter support is available in latest SNAPSHOT releases of ScalaFX. To use it add following to your SBT build file:

libraryDependencies += "org.scalafx" %% "scalafx" % “8.0.40-SNAPSHOT”
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: