Notmy Realname
Notmy Realname

Reputation: 21

binding a function with conditional logic in scala

I am a scala newbie who came from JavaFX 1.3 and this is my first post in stackoverflow

In JavaFX 1.3 I can do something like this

property : bind if (condition) value1 else value2

In Scala, I attempted doing something like this:

property <== function1

def function1() = {
  if (condition)
    value1
  else
    value2
}

However, it does not seem to be dynamically working. The expression in the condition of the function evaluates only once when the stage appears. I was kind of expecting the values in that expression are evaluated in realtime.

Specifically, I want to resize something to a certain limit and I am using binding to achieve it. So I want the bound function to keep evaluating the expression and give me the appropriate width of something as I resize other things.

Anyway, I will paste the actual codes below:

var stageWidth = DoubleProperty(0)
var stageHeight = DoubleProperty(0)

stageWidth <== stage.scene.width
stageHeight <== stage.scene.height

var origStageWidth = DoubleProperty(stage.scene.width.toDouble)
val origStageHeight = DoubleProperty(stage.scene.height.toDouble)

val origTextClipperWidth = DoubleProperty(textClipper.width.toDouble)
val origTextClipperHeight = DoubleProperty(textClipper.height.toDouble)
val minWidth = DoubleProperty(100)

val origButtonWidth = DoubleProperty(button.prefWidth.toDouble)

textClipper.width <== resize

def resize() ={
    var boolResult = (stageWidth - origStageWidth) + origTextClipperWidth > minWidth
    if (boolResult.value) {
        (stageWidth - origStageWidth) + origTextClipperWidth
    } else {
        minWidth
    }
}

textClipper.height <== (stageHeight - origStageHeight) + origTextClipperHeight

Thanks in advance for your help.

Upvotes: 2

Views: 1604

Answers (2)

Jarek
Jarek

Reputation: 1533

JavaFX2+ and ScalaFX allow you to dynamically switch two bound properties based on a BooleanProperty or BooleanBinding. This can be achieved using when construct. Your coode

property : bind if (condition) value1 else value2

can be expressed in ScalaFX as:

property <== when (condition) choose value1 otherwise value2

In your specific example assume that we create a width property that contains dynamically computed width based on the condition boolResult

val width = DoubleProperty(0)

That width property is bound to conditional expression using when construct

width <== when(boolResult) choose ((stageWidth - origStageWidth) + origTextClipperWidth) otherwise minWidth

When boolResult value is true the first part (after choose) is assigned to width when it is false the part after otherwise is assigned. Here is a complete example that shows this in action:

import scalafx.Includes._
import scalafx.beans.property.DoubleProperty

object ConditionalBindigDemo extends App {

  val stageWidth = DoubleProperty(200)
  val origStageWidth = DoubleProperty(100)

  val origTextClipperWidth = DoubleProperty(20)
  val minWidth = DoubleProperty(50)

  val boolResult = (stageWidth - origStageWidth) + origTextClipperWidth > minWidth
  val width = DoubleProperty(0)
  width <== when(boolResult) choose ((stageWidth - origStageWidth) + origTextClipperWidth) otherwise minWidth

  // Simple test
  printState()
  stageWidth() = 150
  printState()
  stageWidth() = 100
  printState()
  stageWidth() = 50
  printState()

  def printState() {
    println("stageWidth: " + stageWidth() + ", origStageWidth: " + origStageWidth() + ", width: " + width())
  }
}

When you run it you will get output:

stageWidth: 200.0, origStageWidth: 100.0, resizeWidth: 120.0
stageWidth: 150.0, origStageWidth: 100.0, resizeWidth: 70.0
stageWidth: 100.0, origStageWidth: 100.0, resizeWidth: 50.0
stageWidth: 50.0, origStageWidth: 100.0, resizeWidth: 50.0

Upvotes: 1

pagoda_5b
pagoda_5b

Reputation: 7373

A standard function/method is not an scalafx.beans.Observable so it doesn't have the needed "hooks" to invalidate its binding.

A while ago I made some methods to simplify binding creation, just for this purpose.

The following code is used to make functions binding to a string value

import scalafx.Includes._
import scalafx.beans.binding.StringBinding
import scalafx.beans.Observable
import scalafx.collections._
import javafx.collections.ObservableList
import javafx.beans.{ binding => jfxbb }
import jfxbb.ListBinding

def createStringBinding(dependency: Observable*)(computeFunction: => String): StringBinding = 
  new jfxbb.StringBinding {
  //invalidated when the passed dependency becomes invalid
  dependency.foreach(this.bind(_))
  //use the function to compute the new value
  override def computeValue: String = computeFunction
}

In your case you should make a Double binding

//THIS CODE IS NOT TESTED, MAYBE IT NEEDS A LITTLE TWEAKING

def createDoubleBinding(dependency: Observable*)(computeFunction: => Double): DoubleBinding = 
  new jfxbb.DoubleBinding {
  //invalidated when the passed dependency becomes invalid
  dependency.foreach(this.bind(_))
  //use the function to compute the new value
  override def computeValue: Double = computeFunction
}

//and use it like
val resize = createDoubleBinding(
  stageWidth,
  stageHeight,
  origStageWidth,
  origStageHeight,
  minWidth,
  origButtonWidth) {
    var boolResult = (stageWidth - origStageWidth) + origTextClipperWidth > minWidth
    if (boolResult.value) {
      (stageWidth - origStageWidth) + origTextClipperWidth
    } else {
      minWidth
    }
}

textClipper.width <== resize

I suppose it's possible to generalize the createXXXBinding with type parameters adapting to available classes of the javafx.beans.binding package, but I'm not sure it would be an easy task, since the class hierarchy doesn't help...

Upvotes: 1

Related Questions