Tornadofx - 如何在每个实例上将参数传递给 Fragment
Tornadofx - How to pass parameter to Fragment on every instance
我是 javafx、kotlin 和显然是 tornadofx 的新手。
问题:
如何在每个实例上将参数传递给 Fragment?
假设我有一个 table 视图布局作为我的片段。现在这个片段在多个地方使用,但使用不同的数据集。
例如。
添加一个片段:
class SomeView : View() {
...
root += SomeViewFragment::class
}
class SomeAnotherView : View() {
...
root += SomeViewFragment::class
}
正在声明片段:
class SomeViewFragment : Fragment() {
...
tableview(someDataSetFromRestApiCall) {
...
}
}
如何从 SomeView 和 SomeAnotherView 传递不同的 someDataSetFromRestApiCall ?
让我们从将数据传递到 Fragments 的最明确的方式开始。对于这个 TableView 示例,您可以在 Fragment 中公开一个可观察列表,并将您的 TableView 绑定到这个列表。然后您可以从片段外部更新该列表,并将您的更改反映在片段中。例如,我创建了一个简单的数据对象,其中包含一个名为 SomeItem
:
的可观察 属性
class SomeItem(name: String) {
val nameProperty = SimpleStringProperty(name)
var name by nameProperty
}
现在我们可以使用绑定到 TableView 的项目 属性 来定义 SomeViewFragment
:
class SomeViewFragment : Fragment() {
val items = FXCollections.observableArrayList<SomeItem>()
override val root = tableview(items) {
column("Name", SomeItem::nameProperty)
}
}
如果您稍后更新项目内容,更改将反映在 table:
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,即注入数据模型。我们首先创建一个可以保存我们的项目列表的数据模型:
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)
}
}
传递参数
编辑:作为这个问题的结果,我们决定包含对使用 find
和 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)
}
}
请注意,这涉及 Fragment 内未经检查的强制转换。
其他选项
您还可以通过 EventBus 传递参数和数据,这也将在即将发布的 TornadoFX 1.5.9 中提供。 EventBus 还支持范围,可以轻松定位事件。
进一步阅读
您可以在指南中阅读有关 Scopes、EventBus 和 ViewModel 的更多信息:
我是 javafx、kotlin 和显然是 tornadofx 的新手。
问题:
如何在每个实例上将参数传递给 Fragment?
假设我有一个 table 视图布局作为我的片段。现在这个片段在多个地方使用,但使用不同的数据集。
例如。 添加一个片段:
class SomeView : View() {
...
root += SomeViewFragment::class
}
class SomeAnotherView : View() {
...
root += SomeViewFragment::class
}
正在声明片段:
class SomeViewFragment : Fragment() {
...
tableview(someDataSetFromRestApiCall) {
...
}
}
如何从 SomeView 和 SomeAnotherView 传递不同的 someDataSetFromRestApiCall ?
让我们从将数据传递到 Fragments 的最明确的方式开始。对于这个 TableView 示例,您可以在 Fragment 中公开一个可观察列表,并将您的 TableView 绑定到这个列表。然后您可以从片段外部更新该列表,并将您的更改反映在片段中。例如,我创建了一个简单的数据对象,其中包含一个名为 SomeItem
:
class SomeItem(name: String) {
val nameProperty = SimpleStringProperty(name)
var name by nameProperty
}
现在我们可以使用绑定到 TableView 的项目 属性 来定义 SomeViewFragment
:
class SomeViewFragment : Fragment() {
val items = FXCollections.observableArrayList<SomeItem>()
override val root = tableview(items) {
column("Name", SomeItem::nameProperty)
}
}
如果您稍后更新项目内容,更改将反映在 table:
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,即注入数据模型。我们首先创建一个可以保存我们的项目列表的数据模型:
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)
}
}
传递参数
编辑:作为这个问题的结果,我们决定包含对使用 find
和 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)
}
}
请注意,这涉及 Fragment 内未经检查的强制转换。
其他选项
您还可以通过 EventBus 传递参数和数据,这也将在即将发布的 TornadoFX 1.5.9 中提供。 EventBus 还支持范围,可以轻松定位事件。
进一步阅读
您可以在指南中阅读有关 Scopes、EventBus 和 ViewModel 的更多信息: