Hey all, just getting started with TornadoFX and I...
# tornadofx
t
Hey all, just getting started with TornadoFX and I found the documentation a little sparse on how to use an
ObservableMap
to populate a
TreeView
and dynamically re-render when the underlying data changes. Any advice?
👍 1
c
Not familiar with treeview specifically, but most other View DSLs functions take an observable as an argument for binding. on the entry point. Could you do
treeview(files) {
instead of
treeview {
?
b
Data driven TreeView If the child list you return from populate is an ObservableList, any changes to that list will automatically be reflected in the TreeView. The populate function will be called for any new children that appears, and removed items will result in removed TreeItems as well.
t
@Bogdan I read this already. It refers to Lists, not Maps, and it doesn't actually seem to update with a list anyway.
b
why do you need map?
you need to write the list processing logic you need in populate
t
Because I need to have multiple subgroups of items abd be able to group the items in arbitrary ways
Anyway it should be supported. The
bindChildren()
function has a form that accepts an
ObservableMap
and a converter of type
(Map) -> Node
That's the part I'm not really getting though. There are no examples
b
bindChildren(observableMap) { file -> TreeItem(file) }
?
t
That's the form I want to use, yes. But my folders object is an
ObjectBinding<Map<File, <List<File!>>!>
not an
ObservableMap<File, <List<File>>
. I though I should be able to use
folders.value
to access the map, but I can't
Also, the linter complains that
TreeItem(file)
is not a
Node
??
b
bindChildren
- defined on
EventTarget
, and knows nothing about
treeview
t
OK
b
I'll try to make an example now
t
Thanks! I'm just not getting how to both bind the data from my ObjectBinding and populate it with my conditions (e.g.,
if (file.isDirectory) ... else ...
)
b
Copy code
class Test : View("My View") {
    val rootFile = File("/home/funtik")
    val files = observableListOf(rootFile)

    override val root = vbox {

        treeview(TreeItem(rootFile)) {

            cellFormat { text = it.name }

            populate {
                when  {
                     it.value.isFile -> null
                     it.value.isDirectory -> it.value.listFiles()?.toList()
                     else -> null
                }
            }
        }
    }
}
t
So I really should only be using this with a List and not a Map, then?
I'll give this a try. I think the key is to create a wrapper class around
java.io.File
that I can store my grouping data in. I want to be able to synthetically group Files into "different directories" than the ones they exist in on the Filesystem.
Tagging, if you will
b
in general - yes, but in this case I do not know if you need to view or some other manipulation
t
Cool. Thanks for your help
b
then perhaps you need to create additional structures to provide information about the filter if there is no data in the FIle itself
I may still work on the code, I will write if something works out
t
Appreciate it!
One other thing...
I don't really want to use the observableList as my root TreeItem. The user is able to drag/drop multiple directories into the app, and I want them to appear as siblings in the TreeView, even if they aren't siblings on the actual FS. That's why I went with the Map to begin with.
I was basically creating an empty root TreeItem, adding each of my Map keys (the directories) as its children, and each of THOSE would show their files as children
Per the image I attached on the main channel
b
and how do you want to group?
all child File have one parent
Copy code
class Test2 : View("My View") {
    val rootFile = File("/home/funtik")
    val files = observableListOf(rootFile)
    val directories = observableMapOf<File, ObservableList<File>>()

    init {
        files += File("/home/funtik/Data")
        files += File("/home/funtik/Downloads")
    }
    
    fun grouping() {
        files
            .groupBy { File(it.parent) }
            .forEach { (k, v) ->
                println(v)
                val list = directories.getOrPut(k) { observableListOf() }
                list.clear()
                list += v
            }
    }

    override val root = vbox(10) {
        val treeview = treeview(TreeItem(rootFile)) {
            isShowRoot = false
            cellFormat { text = it.name }

            populate {
                when {
                    it.value == rootFile -> it.value.listFiles()?.toList()
                    it.value.isFile -> null
                    it.value.isDirectory -> directories.getOrPut(it.value) { observableListOf() }
                    else -> null
                }
            }
        }
    }
}
Copy code
class Test3 : View("My View") {
    val rootFile = File("/home/funtik")
    val files = rootFile.listFiles()?.toList()?.toObservable() ?: observableListOf()
    val directories = observableMapOf<File, ObservableList<File>>()

    init {
//        files += File("/home/funtik/Data")
//        files += File("/home/funtik/Downloads")
    }

    override val root = vbox(10) {
        val treeview = treeview(TreeItem(rootFile)) {
            isShowRoot = false
            cellFormat { text = it.name }

            populate {
                when {
                    it.value == rootFile -> files
                    it.value.isFile -> null
                    it.value.isDirectory -> directories.getOrPut(it.value) { observableListOf() }
                    else -> null
                }
            }
        }

        buttonbar {
            button("g").action {
                val file = treeview.selectedValue?.takeIf { it.isDirectory } ?: return@action
                val files = file.listFiles() ?: return@action
                directories.getOrPut(file) { observableListOf() }.let {
                    it.clear()
                    it += files
                }
            }
        }
    }
}
This is not the best code, but it shows how you can use it. In the code below, it is not necessary to use getOrPut, since at the populate stage, all values from files were inserted
Copy code
directories.getOrPut(file) { observableListOf() }.let {
    it.clear()
    it += files
                
}
Also, to better understand, I advise you to take a look at the populateTree method. from the docks: Add children to the given item by invoking the supplied childFactory function, which converts a TreeItem<T> to a List<T>?. If the childFactory returns a non-empty list, each entry in the list is converted to a TreeItem<T> via the supplied itemProcessor function. The default itemProcessor from TreeTableView.populate and TreeTable.populate simply wraps the given T in a TreeItem, but you can override it to add icons etc. Lastly, the populateTree function is called for each of the generated child items.