3

基本的な初心者の質問:

2 つのテーブルを同期/バインドしたい。
例を単純にするために、2 つの別個のテーブル ビューを使用しました。これは、フラグメントとスコープを使用して行う必要があります。基本的な問題で立ち往生しているため、質問が複雑になると思いました。
動作: テーブル 1 の同期ボタンをクリックすると、テーブル 1 の選択されたデータが対応するテーブル 2 のデータを上書きします。およびその逆

人物モデル:

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

個人コントローラー (ダミーデータ):

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

個人リスト ビュー:

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

他の人のリスト ビュー:

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

2 つのテーブルを同期する

4

2 に答える 2

2

まず、Person を識別できるようにする必要があるため、Person オブジェクトに equals/hashCode を含めます。

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
    }

}

[同期] ボタンをクリックしたときにイベントを発生させたいので、選択した人物と行インデックスの両方を含むことができるイベントを定義します。

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

同じ PersonModel インスタンスを注入しbindSelectedて両方のビューで使用することはできません。これは、お互いをオーバーライドするためです。また、自分自身bindSelectedを呼び出すときではなく、選択が変更されるたびに反応するbindSelectedため、ボタン ハンドラーには属しません。ビューごとに個別のモデルを使用し、選択範囲にバインドします。これにより、ボタン ハンドラーが実行されたときにどの人物が選択されているかを簡単に知ることができ、TableView のインスタンスを保持する必要がなくなります。また、新しいルート ビルダー構文を使用してすべてをクリーンアップします。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)))
                    }
                }

            }
        }
    }
}

ビューは、2 か所の代わりにAnotherPersonListへの参照を除いて同じです。(同じフラグメントを使用してリストをパラメーターとして送信することもできるため、このコードをすべて複製する必要はありません)。ctrl.newPersonsctrl.persons

ボタンのクリック時に人が選択されている場合、同期ボタンはイベントを発生させます。

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

TableView 内で、以下をサブスクライブしますSyncPersonEvent

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

イベントが発生すると、同期イベントが通知されます。テーブルビューのアイテムにこの人物が含まれているかどうかを最初に確認し、含まれていない場合は正しいインデックスに追加します。実際のアプリケーションでは、インデックスが項目リストの境界内にあることを確認する必要があります。

次に、この人物がすでに選択されているかどうかを確認し、選択されていない場合は、選択を行い、このテーブルへのフォーカスを要求します。ソース テーブルがフォーカスを要求したり、(冗長な) 選択を実行したりしないように、このチェックは重要です。

前述のように、適切な最適化は、PersonList コードを複製する必要がないように、項目リストをパラメーターとして送信することです。

また、新しいビルダー構文の使用にも注意してください。

override val root = vbox {
}

VBox()これは、最初にルート ノードを として宣言し、残りの UI をinitブロック内に構築する場合よりもはるかに優れています。

これがあなたが探しているものであることを願っています:)

重要: このソリューションには、TornadoFX 1.5.9 が必要です。それは今日リリースされます :) 必要に応じて、その間に 1.5.9-SNAPSHOT に対してビルドできます。

于 2016-12-23T11:46:08.283 に答える
0

別のオプションは RxJavaFX/RxKotlinFX です。TornadoFX と同じように、これらのライブラリのコンパニオン ガイドを書いています。

複雑なイベント ストリームを処理し、UI コンポーネントの同期を維持する必要がある場合は、リアクティブ プログラミングが効果的です。

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")
    }
}
于 2016-12-23T15:12:53.340 に答える