12

我是 javafx、kotlin 和显然是 tornadofx 的新手。
问题
如何在每个实例上将参数传递给 Fragment?

假设我有一个表格视图布局作为我的片段。现在这个片段在多个地方使用,但具有不同的数据集。

例如。在以下位置添加片段:

class SomeView : View() {
... 
root += SomeViewFragment::class
}

class SomeAnotherView : View() {
... 
root += SomeViewFragment::class
}

声明片段:

class SomeViewFragment : Fragment() {
...
    tableview(someDataSetFromRestApiCall) {
    ...
    }
}

如何从 SomeView 和 SomeAnotherView 传递不同的 someDataSetFromRestApiCall ?

4

1 回答 1

26

让我们从将数据传递给 Fragments 的最明确的方式开始。对于这个 TableView 示例,您可以在 Fragment 中公开一个可观察列表,并将您的 TableView 绑定到该列表。然后,您可以从 Fragment 外部更新该列表,并将您的更改反映在 Fragment 中。对于这个例子,我创建了一个简单的数据对象,它有一个名为 observable 的属性SomeItem

class SomeItem(name: String) {
    val nameProperty = SimpleStringProperty(name)
    var name by nameProperty
}

现在我们可以定义SomeViewFragment一个绑定到 TableView 的 item 属性:

class SomeViewFragment : Fragment() {
    val items = FXCollections.observableArrayList<SomeItem>()

    override val root = tableview(items) {
        column("Name", SomeItem::nameProperty)
    }
}

如果您稍后更新项目内容,更改将反映在表中:

class SomeView : View() {
    override val root = stackpane {
        this += find<SomeViewFragment>().apply {
            items.setAll(SomeItem("Item A"), SomeItem("Item B"))
        }
    }
}

然后,您可以对SomeOtherView其他数据执行相同的操作:

class SomeOtherView : View() {
    override val root = stackpane {
        this += find<SomeViewFragment>().apply {
            items.setAll(SomeItem("Item B"), SomeItem("Item C"))
        }
    }
}

虽然这很容易理解并且非常明确,但它在您的组件之间创建了非常强的耦合。您可能要考虑为此使用范围。我们现在有两个选择:

  1. 在范围内使用注入
  2. 让范围包含数据

在范围内使用注入

我们将首先使用选项 1,通过注入数据模型。我们首先创建一个可以保存我们的项目列表的数据模型:

class ItemsModel(val items: ObservableList<SomeItem>) : ViewModel()

现在我们将这个 ItemsModel 注入我们的 Fragment 并从该模型中提取项目:

class SomeViewFragment : Fragment() {
    val model: ItemsModel by inject()

    override val root = tableview(model.items) {
        column("Name", SomeItem::nameProperty)
    }
}

最后,我们需要为每个视图中的片段定义一个单独的范围,并为该范围准备数据:

class SomeView : View() {

    override val root = stackpane {
        // Create the model and fill it with data
        val model= ItemsModel(listOf(SomeItem("Item A"), SomeItem("Item B")).observable())

        // Define a new scope and put the model into the scope
        val fragmentScope = Scope()
        setInScope(model, fragmentScope)

        // Add the fragment for our created scope
        this += find<SomeViewFragment>(fragmentScope)
    }
}

请注意,setInScope上面使用的功能将在 TornadoFX 1.5.9 中可用。同时,您可以使用:

FX.getComponents(fragmentScope).put(ItemsModel::class, model)

让范围包含数据

另一种选择是将数据直接放入范围。让我们创建一个ItemsScope

class ItemsScope(val items: ObservableList<SomeItem>) : Scope()

现在我们的片段将期望获得一个实例,SomeItemScope因此我们对其进行转换并提取数据:

class SomeViewFragment : Fragment() {
    override val scope = super.scope as ItemsScope

    override val root = tableview(scope.items) {
        column("Name", SomeItem::nameProperty)
    }
}

由于我们不需要模型,视图现在需要做的工作更少:

class SomeView : View() {

    override val root = stackpane {
        // Create the scope and fill it with data
        val itemsScope= ItemsScope(listOf(SomeItem("Item A"), SomeItem("Item B")).observable())

        // Add the fragment for our created scope
        this += find<SomeViewFragment>(itemsScope)
    }
}

传递参数

编辑:作为这个问题的结果,我们决定支持使用findand传递参数inject。因此,在 TornadoFX 1.5.9 中,您可以将项目列表作为参数发送,如下所示:

class SomeView : View() {
    override val root = stackpane {
        val params = "items" to listOf(SomeItem("Item A"), SomeItem("Item B")).observable()
        this += find<SomeViewFragment>(params)
    }
}

现在SomeViewFragment可以获取这些参数并直接使用它们:

class SomeViewFragment : Fragment() {
    val items: ObservableList<SomeItem> by param()

    override val root = tableview(items) {
        column("Name", SomeItem::nameProperty)
    }
}

请注意,这涉及片段内部未经检查的演员表。

其他选项

您还可以通过 EventBus 传递参数和数据,这也将在即将发布的 TornadoFX 1.5.9 中。EventBus 还支持范围,可以轻松定位您的事件。

进一步阅读

您可以在指南中阅读有关 Scopes、EventBus 和 ViewModel 的更多信息:

范围

事件总线

ViewModel 和验证

于 2016-12-17T13:54:46.007 回答