Views
9 months ago

tornadofx-guide

Workspaces class MyApp:

Workspaces class MyApp: App(MyWorkspace::class) { override fun onBeforeShow(view: UIComponent) { workspace.dock() } } class MyWorkspace: Workspace() { override fun onRefresh() { customerTable.asyncItems { customerController.listCustomers() } } } Same goes for the Delete button. We will revisit the Save button and introduce a neat trick to only activate it when there are dirty changes later in this chapter. Tabbed Views You may at one point dock a View containing a TabPane inside of a Workspace , and then add tabs which represents further UIComponents. You can quite easily proxy the savable, refreshable and deletable state and actions from the Workspace onto the View represented by the currently active Tab. Consider a Customer Editor which has tabs for editing customer data, and one for editing contacts for that customer. Whenever the user selects one of the tabs, the buttons in the Workspace should interact with the state and actions from the selected tab view. class CustomerEditor : View("Customer Editor") { override val root = tabpane { tab(CustomerBasicDataEditor::class) tab(ContactListEditor::class) connectWorkspaceActions() } } That single call to connectWorkspaceActions() takes care of everything for us. The actual implementation of the two sub views are omitted for brevity, but you can imagine that they share a CustomerViewModel injected into the scope they share for example. The actual implementation of connectWorkspaceActions is quite simple, and reveals what's going on under the cover: 228

Workspaces fun TabPane.connectWorkspaceActions() { savableWhen { savable } whenSaved { onSave() } deletableWhen { deletable } whenDeleted { onDelete() } } refreshableWhen { refreshable } whenRefreshed { onRefresh() } This function is declared inside UIComponent , so the savableWhen , deletableWhen , and refreshableWhen are performed on the UIComponent. Those state are then bound to the savable , deletable and refreshable state of the TabPane . But wait... a TabPane does not have those functions?! Yes, in TornadoFX it does :) You can probably guess that the implementation is again another proxy into the currently selected Tab in the TabPane , and a lookup of the UIComponent represented by the content property of that Tab . Whenever the Tab changes (or when the content of the tab changes), the underlying UIComponent is looked up, and the pertinent states are bound to the Workspace . It would also be possible to bind these states and connect the actions more explicitly. You will never or seldom need to do that, but the following example might help your understanding of the proxy mechanism. class TooExplicitCustomerEditor : View() { override val root = tab { ... } override val savable = root.savable override val refreshable = root.refreshable override val deletable = root.deletable override fun onSave() { } root.onSave() override fun onDelete() { } root.onDelete() } override fun onRefresh() { } root.onRefresh() 229

GUIDE
GUIDE
Guide
GUIDE
Guide
GUIDE
GUIDE
GUIDE
Guide
GUIDE
Guide
GUIDE
Guide
GUIDE
GUIDE
Guide