Hi all, I have a simple application that is for wa...
# tornadofx
m
Hi all, I have a simple application that is for walking down a list of Cards. Each card has an image, and I use the "n" shortcut to move to the next card in the view by invoking a method on the controller. The main view only shows the card's main graphic, there's no lists, or other components. I have a controller loading all the cards, and holding a model for the card. When I initialise the controller, i set the model to the first card in the list, and the view is bound to the model. This all works, and I see the image for the first card when it loads. When I press "n" I can see the logging for the controller's function invocation, and it updating the model as follows:
Copy code
class CardAlignController : Controller() {
    val cards = FXCollections.observableArrayList<CardData>()
    val model = CardDataModel()
    var currentIndex = 0

    init {
        cards.addAll(...) // some loader code here
        model.item = cards[0]
    }

    fun nextCard() {
        model.item = cards[++currentIndex]
        println("Next invoked, index: $currentIndex, currentCard: ${model.card.value.name}")
    }
but the main view doesn't update. How can I tie moving through the list to the main view? I don't have anything to do a bindSelected() on, so I'm not sure how to proceed. Thanks!
c
It would be helpful to see CardDataModel and your view.
I see one possible issue already, but it may be a red herring depending on how you set those two up.
m
Copy code
package aligner.model

import tesl.model.Card
import tornadofx.ItemViewModel
import tornadofx.getProperty
import tornadofx.property

class CardData(
    card: Card,
    xOffset: Double,
    yOffset: Double,
    scale: Double,
    index: Int
) {
    var card by property(card)
    fun cardProperty() = getProperty(CardData::card)

    var xOffset by property(xOffset)
    fun xOffsetProperty() = getProperty(CardData::xOffset)

    var yOffset by property(yOffset)
    fun yOffsetProperty() = getProperty(CardData::yOffset)

    var scale by property(scale)
    fun scaleProperty() = getProperty(CardData::scale)

    var index by property(index)
    fun indexProperty() = getProperty(CardData::index)
}

class CardDataModel: ItemViewModel<CardData>() {
    val card = bind { item?.cardProperty() }
    val xOffset = bind { item?.xOffsetProperty() }
    val yOffset = bind { item?.yOffsetProperty() }
    val scale = bind { item?.scaleProperty() }
    val index = bind { item?.indexProperty() }
}
and the view holds it like this:
Copy code
class CardAlignMainView: View("Card Align Main View") {

    val cardAlignController: CardAlignController by inject()
    val model: CardDataModel by inject()
Then the gui elements access it as:
Copy code
override val root = gridpane {
        row {
            stackpane {
                group {
                    val name = cardAlignController.model.card.value.name
clearly wrong, but using
model.card.name
gives nothing
c
In the controller and the view, these two lines are at odds with each other. The View will try to obtain the CardDataModel from the DI scope. Meanwhile, the Controller has its own instance which isn't in the DI scope.
I suggest switching the Controller to obtain it with
by inject()
That way they'll share the same model instance
m
how do I initialise it then? same as I'm doing with
model.item = cards[0]
?
c
You can leave your
init
block alone
m
thanks. ok, it's still not updating the image though in the view
should
nextCard()
be directly setting the
model.item
as I'm doing?
c
Oh, I see. It looks like the next issue is that you're assigning values to the UI node, instead of binding them to observable properties
m
is there an example of how to do that in the docs somewhere?
particularly as I don't have any lists or other things to bind to. do i need a dummy?
c
Perhaps that was a little unclear. The
.value.name
is causing your view take the current backing value
CardData
from your
CardDataModel
. Use properties from
CardDataModel
directly in that case
textfield(model.name)
or similar
m
when the code runs, the
model.card.name
value is empty when the GUI is being displayed. which is why i reached into the controller. The value for
model.card.value.name
is set though
c
cards.addAll(...) // some loader code here
-- is that in a runAsync, or a coroutine, or is it happening synchronously in the
init
for the controller?
m
synchronously
might be easier if I upload a cut down version of this to github
c
Oh,
card: Card
You can use a
select
to obtain a selection binding that follows the current
card
, selecting the
nameProperty
from the
Card
m
ah, so I have to make Card property aware too?
c
It's nice to flatten that out in the context of the Model
If possible, that'll make it a lot easier to wire up the UI
m
there's only a few fields I need from it, though I'm interested in your selection binding method too, so I don't have to keep making classes to represent the data. Though right now, I just want to get it working. Thanks for the suggestions, I'll have a play and come back if I have issues, or else thank you more.
on it you can make
bindSelect
m
i have no selection though, other than the user clicking the "n" button, I don't represent the list in the UI. I'm manually tracking the current card.
m
aha, that's new information. thanks Carlton
I ended up simplifying the model into strings, and using
imageview(model.cardUrl) {
... etc which now updates the view when I move through the list. thanks for the help all.
👍 1