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,通过注入数据模型.我们首先创建一个数据模型,可以保存我们的项目列表:
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