I have a simple UI window with just one Text node, witch shows current time and binded to the model:
import model.Clock
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.beans.property.StringProperty
import scalafx.geometry.Insets
import scalafx.scene.Scene
import scalafx.scene.layout.HBox
import scalafx.scene.text.Text
import scalafx.Includes._
object Main extends JFXApp {
val clock = new Text()
clock.textProperty().bind( new StringProperty(Clock.curTime) )
stage = new PrimaryStage {
onShowing = handle { Clock.startClock }
title = "ScalaFX clock"
scene = new Scene {
content = new HBox {
padding = Insets(50, 80, 50, 80)
children = clock
}
}
}
}
And a model:
import java.util.Date
import java.text.SimpleDateFormat
object Clock {
var curTime = ""
private lazy val dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss")
def startClock = {
/*A code, witch runs in another Thread and
changes curTime every 1 second skipped here.
Only one change of curTime for simplicity.
*/
curTime = dateFormat format new Date
}
}
My problem is: the text node in UI doesn't change, when curTime variable change.
I guess it renderes once on stratup. How to make text node show new value of curTime every time, curTime changed?
Sorry for my English :(
The problem is that Clock.curTime
is just a variable whose value gets updated periodically - it's not an observed property, so nothing happens when it is changed. In particular, there's no link between that variable and your Text
element's contents
What you have done is to initialize a StringProperty
with that value - but since the property itself is never updated, neither is the label. I think what you wanted to do was to make curTime
a StringProperty
instead of just a String
. Now, whenever you change that property's value, the Text
element's value will change accordingly.
You should note that interactions with JavaFX/ScalaFX can only happen on the JavaFX Application Thread, so if you try updating curTime
from a another thread, you're going to run into problems. One way to achieve that would be to pass the code that actually updates curTime
to Platform.runLater
.
However, a simpler approach is to use a timed ScalaFX event to update curTime
periodically from within JavaFX/ScalaFX, as follows:
import model.Clock
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.geometry.Insets
import scalafx.scene.Scene
import scalafx.scene.layout.HBox
import scalafx.scene.text.Text
import scalafx.Includes._
object Main
extends JFXApp {
val clock = new Text()
// Clock.curTime is now a StringProperty, which we bind to clock's text. Since clock is
// created lazily, it starts the timer event, so we do not need startClock either.
clock.text <== Clock.curTime
// Create the stage & scene.
stage = new PrimaryStage {
title = "ScalaFX clock"
scene = new Scene {
content = new HBox {
padding = Insets(50, 80, 50, 80)
children = clock
}
}
}
}
In Clock.scala
:
import java.util.Date
import java.text.SimpleDateFormat
import scalafx.animation.PauseTransition
import scalafx.application.Platform
import scalafx.beans.property.StringProperty
import scalafx.util.Duration
import scalafx.Includes._
// Object should be constructed lazily on the JFX App Thread. Verify that...
object Clock {
assert(Platform.isFxApplicationThread)
private val dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss")
val curTime = new StringProperty(getDate())
// Update the curTime property every second, using a timer.
// Note: 1,000 ms = 1 sec
val timer = new PauseTransition(Duration(1000))
timer.onFinished = {_ =>
curTime.value = getDate()
timer.playFromStart() // Wait another second, or you can opt to finish instead.
}
// Start the timer.
timer.play()
private def getDate() = dateFormat.format(new Date())
}