TreeView 的异步加载

Async Loading of a TreeView

嘿,我是 tornadofx 的新手,正在努力为树视图异步加载数据。我正在从休息端点加载类别,我想在其中显示。

似乎没有直接绑定到子项的数据。

当使用 'bindChildren' 时,我可以提供可观察列表,但我必须将它们转换为 Node 的。这将使填充块有点过时。

推荐的方法是什么?我找不到任何相关信息。

// Category
interface Category<T : Category<T>> {
  val id: String
  val name: String
  val subcategories: List<T>?
}


//default category:
class DefaultCategory(override val name: String) : Category<DefaultCategory> {
  override val id: String = "default"
  override val subcategories: List<DefaultCategory>? = null
}
//ViewModel
class CategoryViewModel : ViewModel() {
  val sourceProperty = SimpleListProperty<Category<*>>()
  fun loadData() {
    // load items for treeview into 'newItems'
    sourceProperty.value = newItems
  }
}

// TreeViewFactoryMethod
private fun createTreeView(
  listProperty: SimpleListProperty<Category<*>>
): TreeView<Category<*>> {
  return treeview {
    root = TreeItem(DefaultCategory("Categories"))
    isShowRoot = false
    root.isExpanded = true
    root.children.forEach { it.isExpanded = true }
    cellFormat { text = it.name }
    populate { parent ->
      when (parent) {
        root -> listProperty.value
        else -> parent.value.subcategories
      }
    }
  }
}

假设在单击按钮时我调用 viewmodel.loadData(),我希望 TreeView 在有一些新数据时立即更新。 (如果我能找到绑定方法的话)

我以前从来没有为 TornadoFX 使用过 bindChildren,你对异步的使用与我认为你的主要问题不是很相关。所以,不可否认,这个问题起初让我有些困惑,但我猜你只是想知道为什么列表没有出现在你的 TreeView 中?我做了一个测试示例,并进行了更改以使其正常工作。

// Category
interface Category<T : Category<T>> {
    val id: String
    val name: String
    val subcategories: List<T>?
}

//default category:
class DefaultCategory(override val name: String) : Category<DefaultCategory> {
    override val id: String = "default"
    override val subcategories: List<DefaultCategory>? = null
}
//Just a dummy category
class ChildCategory(override val name: String) : Category<ChildCategory> {
    override val id = name
    override val subcategories: List<ChildCategory>? = null
}

//ViewModel
class CategoryViewModel : ViewModel() {
    //filled with dummy data
    val sourceProperty = SimpleListProperty<Category<*>>(listOf(
            ChildCategory("Categorya"),
            ChildCategory("Categoryb"),
            ChildCategory("Categoryc"),
            ChildCategory("Categoryd")
    ).asObservable())

    fun loadData() {
        sourceProperty.asyncItems {
            //items grabbed somehow
            listOf(
                    ChildCategory("Category1"),
                    ChildCategory("Category2"),
                    ChildCategory("Category3"),
                    ChildCategory("Category4")
            ).asObservable()
        }
    }
}


class TestView : View() {
    val model: CategoryViewModel by inject()

    override val root = vbox(10) {
        button("Refresh Items").action {
            model.loadData()
        }
        add(createTreeView(model.sourceProperty))
    }

    // TreeViewFactoryMethod
    private fun createTreeView(
            listProperty: SimpleListProperty<Category<*>>
    ): TreeView<Category<*>> {
        return treeview {
            root = TreeItem(DefaultCategory("Categories"))
            isShowRoot = false
            root.isExpanded = true
            root.children.forEach { it.isExpanded = true }
            cellFormat { text = it.name }
            populate { parent ->
                when (parent) {
                    root -> listProperty
                    else -> parent.value.subcategories
                }
            }
        }
    }
}

有两个重要的区别。

1. 更相关的区别是在填充块内部,使用 root -> listProperty 而不是 root.listProperty.value。这将使您的列表出现。原因是 SimpleListProperty 不是列表,它 包含 列表。所以,是的,传入一个普通列表是完全有效的(就像你传入列表 属性 的 value 的方式一样)。但现在这意味着树视图不听你的 属性,只听你传入的列表。考虑到这一点,我会体谅类别的子类别列表也已实现。

2.其次,注意ViewModelasyncItems的使用。这将异步执行任何任务,然后将项目设置为成功列出。您甚至可以向其中添加 failcancel 块。我建议使用它,因为 long/intensive 操作不应在 UI 线程上执行。