Tornadofx tableview sincroniza dos tablas

Pregunta básica para principiantes:

Quiero sincronizar / enlazar dos tablas.
Para mantener el ejemplo simple, he usado dos vistas de tabla separadas. Esto debe hacerse usando fragments y scope, lo que pensé que complicaría la pregunta ya que estoy atascado en un problema básico.
Comportamiento: Al hacer clic en el button de synchronization de la tabla 1, deseo que la tabla 1 select datos para anular los datos de la tabla 2 correspondiente . y viceversa

Modelo de persona:

class Person(firstName: String = "", lastName: String = "") { val firstNameProperty = SimpleStringProperty(firstName) var firstName by firstNameProperty val lastNameProperty = SimpleStringProperty(lastName) var lastName by lastNameProperty } class PersonModel : ItemViewModel<Person>() { val firstName = bind { item?.firstNameProperty } val lastName = bind { item?.lastNameProperty } } 

Controlador de persona (datos ficticios):

 class PersonController : Controller(){ val persons = FXCollections.observableArrayList<Person>() val newPersons = FXCollections.observableArrayList<Person>() init { persons += Person("Dead", "Stark") persons += Person("Tyrion", "Lannister") persons += Person("Arya", "Stark") persons += Person("Daenerys", "Targaryen") newPersons += Person("Ned", "Stark") newPersons += Person("Tyrion", "Janitor") newPersons += Person("Arya", "Stark") newPersons += Person("Taenerys", "Dargaryen") } } 

Vista de list de personas:

 class PersonList : View() { val ctrl: PersonController by inject() val model : PersonModel by inject() var personTable : TableView<Person> by singleAssign() override val root = VBox() init { with(root) { tableview(ctrl.persons) { personTable = this column("First Name", Person::firstNameProperty) column("Last Name", Person::lastNameProperty) columnResizePolicy = SmartResize.POLICY } hbox { button("Sync") { setOnAction { personTable.bindSelected(model) //model.itemProperty.bind(personTable.selectionModel.selectedItemProperty()) } } } } } 

Otra vista de list de personas:

 class AnotherPersonList : View() { val model : PersonModel by inject() val ctrl: PersonController by inject() override val root = VBox() var newPersonTable : TableView<Person> by singleAssign() init { with(root) { tableview(ctrl.newPersons) { newPersonTable = this column("First Name", Person::firstNameProperty) column("Last Name", Person::lastNameProperty) columnResizePolicy = SmartResize.POLICY } hbox { button("Sync") { setOnAction { newPersonTable.bindSelected(model) } } } } } } 

Sincronizar dos tablas

Primero, necesitamos poder identificar a una Persona, así que incluya equals / hashCode en el object Persona:

 class Person(firstName: String = "", lastName: String = "") { val firstNameProperty = SimpleStringProperty(firstName) var firstName by firstNameProperty val lastNameProperty = SimpleStringProperty(lastName) var lastName by lastNameProperty override fun equals(other: Any?): Boolean { if (this === other) return true if (other?.javaClass != javaClass) return false other as Person if (firstName != other.firstName) return false if (lastName != other.lastName) return false return true } override fun hashCode(): Int { var result = firstName.hashCode() result = 31 * result + lastName.hashCode() return result } } 

Queremos activar un evento cuando hace clic en el button Sincronizar, por lo que definimos un evento que puede contener tanto la persona seleccionada como el índice de fila:

 class SyncPersonEvent(val person: Person, val index: Int) : FXEvent() 

No puede inyectar la misma instancia de PersonModel y usar bindSelected en ambas vistas, ya que eso se anulará entre sí. Además, bindSelected reactjsrá cada vez que la selección cambie, no cuando llame a bindSelected , por lo que no pertenece al controller de botones. Usaremos un model separado para cada vista y nos uniremos a la selección. Entonces podemos saber fácilmente qué persona se selecciona cuando se ejecuta el manejador de botones, y no necesitamos aferrarnos a una instancia de TableView. También usaremos la nueva syntax del generador de raíz para limpiar todo. Aquí está la vista de PersonList:

 class PersonList : View() { val ctrl: PersonController by inject() val selectedPerson = PersonModel() override val root = vbox { tableview(ctrl.persons) { column("First Name", Person::firstNameProperty) column("Last Name", Person::lastNameProperty) columnResizePolicy = SmartResize.POLICY bindSelected(selectedPerson) subscribe<SyncPersonEvent> { event -> if (!items.contains(event.person)) { items.add(event.index, event.person) } if (selectedItem != event.person) { requestFocus() selectionModel.select(event.person) } } } hbox { button("Sync") { setOnAction { selectedPerson.item?.apply { fire(SyncPersonEvent(this, ctrl.persons.indexOf(this))) } } } } } } 

La vista AnotherPersonList es idéntica, excepto por la reference a ctrl.newPersons lugar de ctrl.persons en dos lugares. (Puede usar el mismo fragment y enviarlo como un parámetro para que no necesite duplicar todo este código).

El button de synchronization ahora activa nuestro evento, siempre que se select una persona en el momento de hacer clic en el button:

 selectedPerson.item?.apply { fire(SyncPersonEvent(this, ctrl.persons.indexOf(this))) } 

Dentro de TableView ahora nos suscribimos al SyncPersonEvent :

 subscribe<SyncPersonEvent> { event -> if (!items.contains(event.person)) { items.add(event.index, event.person) } if (selectedItem != event.person) { requestFocus() selectionModel.select(event.person) } } 

El evento de synchronization se notifica cuando se activa el evento. Primero comtesting si los elementos de para la vista de tabla contienen esta persona, o la agrega al índice correcto si no es así. Una aplicación real debería verificar que el índice esté dentro de los límites de la list de elementos.

Luego verifica si esta persona ya está seleccionada y, de no ser así, hará la selección y también solicitará el enfoque en esta tabla. La comprobación es importante para que la tabla de origen no solicite el foco ni realice la selección (networkingundante).

Como se señaló, una buena optimization sería enviar la list de elementos como un parámetro para que no tenga que duplicar el código de PersonList.

También observe el uso de la nueva syntax del generador:

 override val root = vbox { } 

Esto es mucho más claro que declarar primero el nodo raíz como un VBox() y al build el rest de la UI en el bloque de init .

Espero que esto es lo que estás buscando 🙂

Importante: esta solución requiere TornadoFX 1.5.9. Se lanzará hoy 🙂 Puedes build contra 1.5.9-SNAPSHOT mientras tanto, si quieres.

Otra opción que tiene es RxJavaFX / RxKotlinFX. He estado escribiendo una guía complementaria para estas bibliotecas al igual que la de TornadoFX .

Cuando tiene que tratar con secuencias de events complejos y mantener sincronizados los componentes de la interfaz de usuario, la progtwigción reactiva es efectiva para estas situaciones.

 package org.nield.demo.app import javafx.beans.property.SimpleStringProperty import javafx.collections.FXCollections import javafx.collections.ObservableList import rx.javafx.kt.actionEvents import rx.javafx.kt.addTo import rx.javafx.kt.onChangedObservable import rx.javafx.sources.CompositeObservable import rx.lang.kotlin.toObservable import tornadofx.* class MyApp: App(MainView::class) class MainView : View() { val personList: PersonList by inject() val anotherPersonList: AnotherPersonList by inject() override val root = hbox { this += personList this += anotherPersonList } } class PersonList : View() { val ctrl: PersonController by inject() override val root = vbox { val table = tableview(ctrl.persons) { column("First Name", Person::firstNameProperty) column("Last Name", Person::lastNameProperty) //broadcast selections selectionModel.selectedIndices.onChangedObservable() .addTo(ctrl.selectedLeft) columnResizePolicy = SmartResize.POLICY } button("SYNC").actionEvents() .flatMap { ctrl.selectedRight.toObservable() .take(1) .flatMap { it.toObservable() } }.subscribe { table.selectionModel.select(it) } } } class AnotherPersonList : View() { val ctrl: PersonController by inject() override val root = vbox { val table = tableview(ctrl.newPersons) { column("First Name", Person::firstNameProperty) column("Last Name", Person::lastNameProperty) //broadcast selections selectionModel.selectedIndices.onChangedObservable() .addTo(ctrl.selectedRight) columnResizePolicy = SmartResize.POLICY } button("SYNC").actionEvents() .flatMap { ctrl.selectedLeft.toObservable() .take(1) .flatMap { it.toObservable() } }.subscribe { table.selectionModel.select(it) } } } class Person(firstName: String = "", lastName: String = "") { val firstNameProperty = SimpleStringProperty(firstName) var firstName by firstNameProperty val lastNameProperty = SimpleStringProperty(lastName) var lastName by lastNameProperty } class PersonController : Controller(){ val selectedLeft = CompositeObservable<ObservableList<Int>> { it.replay(1).autoConnect().apply { subscribe() } } val selectedRight = CompositeObservable<ObservableList<Int>> { it.replay(1).autoConnect().apply { subscribe() } } val persons = FXCollections.observableArrayList<Person>() val newPersons = FXCollections.observableArrayList<Person>() init { persons += Person("Dead", "Stark") persons += Person("Tyrion", "Lannister") persons += Person("Arya", "Stark") persons += Person("Daenerys", "Targaryen") newPersons += Person("Ned", "Stark") newPersons += Person("Tyrion", "Janitor") newPersons += Person("Arya", "Stark") newPersons += Person("Taenerys", "Dargaryen") } } 
  • TornadoFX: permite copyr un elemento de una vista de list al portapapeles
  • Context de reinicio de JavaFX
  • CellCache se representa inesperadamente en TableView con tornadoFX
  • ¿Cómo puedo crear un encabezado de columna nested / dividido usando TornadoFx?
  • Cómo inyectar ItemViewModel en tornadoFx
  • java.lang.NoSuchMethodException cuando intenta ejecutar la aplicación TornadoFX
  • Animación consecutiva en TornadoFX?
  • TornadoFX con TestFX cierra la vista después de cada TestCase
  • Enlazando una vista a una propiedad de controller cambiante
  • TornadoFX reemplaza el layoutChildren en la región
  • ¿Hay alguna manera de vincular una propiedad a appConfig en tornadofx?