I am a newbie to javafx, kotlin and obviously tornadofx.
Issue:
How to pass parameters to Fragment on every instance?

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

如.

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

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

Declaring Fragment:

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

How can I pass different someDataSetFromRestApiCall from SomeView and SomeAnotherView ?

推荐答案

Let's start with the most explicit way to pass data to Fragments. For this TableView example you could expose an observable list inside the Fragment and tie your TableView to this list. Then you can update that list from outside the Fragment and have your changes reflected in the fragment. For the example I created a simple data object with an observable property called SomeItem:

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

现在,我们可以使用绑定到TableView的Item属性定义SomeViewFragment:

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

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

If you later update the items content, the changes will be reflected in the table:

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

You can then do the same for SomeOtherView but with other data:

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

虽然这很容易理解,而且非常明确,但它在组件之间创建了非常强的耦合.您可能需要考虑使用作用域来代替此.我们现在有两个 Select :

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

在范围内使用注入

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

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

现在,我们将这个ItemsModel注入到片段中,并从该模型中提取项目:

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

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

Lastly, we need to define a separate scope for the fragments in each view and prepare the data for that scope:

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)
    }
}

Please not that the setInScope function used above will be available in TornadoFX 1.5.9. In the mean time you can use:

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

让作用域包含数据

另一种 Select 是将数据直接放入作用域.让我们改为创建一个ItemsScope:

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

Now our fragment will expect to get an instance of SomeItemScope so we cast it and extract the data:

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)
    }
}

Passing parameters

EDIT: As a result of this question, we decided to include support for passing parameters with find and inject. From TornadoFX 1.5.9 you can therefore send the items list as a parameter like this:

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

The SomeViewFragment can now pick up these parameters and use them directly:

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

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

Please not that this involves an unchecked cast inside the Fragment.

其他 Select

您还可以通过EventBus传递参数和数据,EventBus也将在即将发布的TornadoFX 1.5.9中发布.EventBus还支持作用域,这使您可以轻松地确定事件的目标.

Further reading

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

Scopes

EventBus

ViewModel and Validation

Kotlin相关问答推荐

Compose:LaunchedEffect在密钥更改后不会重新启动

在Kotlin中,有没有一种函数方法将一个列表(N个元素)映射到一个相邻元素之和列表(N—1个元素)?

如何确保Kotlin子类已完成初始化?

使函数同时挂起和取消挂起

Kotlin 获取继承类的默认 hashCode 实现

mutableStateOf 和 mutableStateListOf 有什么区别?

判断 Kotlin 变量是否为函数

如何使用函数类型或 lambdas 作为 Kotlin 上下文接收器的类型?

如果不在可组合函数中,如何获取 stringResource

Kotlin 中列表或数组的乘积

AIDL 中的 Parcelize 注释:Incompatible types: Object cannot be converted to MyCustomObject

在 SplashActivity 中显示的 Firebase 应用内消息.如何在 MainActivity 中显示它?

Spring webflux bean验证不起作用

下拉通知面板时是否可以暂停Android中的任何视频(媒体播放器)应用程序?

使用 Paging 3 时保存并保留 LazyColumn 滚动位置

Kotlin:如何修改成对的值?

在 suspendCoroutine 块中调用挂起函数的适当方法是什么?

如何使用mockk库模拟android上下文

Kotlin for assertThat(foo, instanceOf(Bar.class))

如何在 Kotlin 中生成 MD5 哈希?