edvin / tornadofx

Lightweight JavaFX Framework for Kotlin
Apache License 2.0
3.67k stars 270 forks source link

How to make a tableview refresh and make cellformat working ? #355

Closed toplinuxsir closed 7 years ago

toplinuxsir commented 7 years ago

Example: I have a tableview :

button("refresh table") {
 action {
   // here I want to make table view refresh How ? 

   }
}
tableview<KingdeeOrderEntry> {
                                column("col1", KingdeeOrderEntry::mTypeCodeProperty).cellFormat {
                                    text = it.toString()
                                    textFill = Color.BLUE
                                }
                                refreshableWhen { refreshable }

                                column("col2", KingdeeOrderEntry::accountNOProperty)
                                column("col3", KingdeeOrderEntry::batchFixProperty).cellFormat {
                                    if (rowItem.authId == null) {
                                        style {
                                            backgroundColor += Color.RED
                                        }
                                    } else {
                                        style {
                                            backgroundColor += Color.GREEN
                                        }
                                    }

                                    text = it.toString()
                                    textFill = Color.LIGHTCYAN

                                }
                                column("colx", KingdeeOrderEntry::supplierFixProperty).cellFormat {
                                    textFill = Color.LIGHTCYAN
                                    if (rowItem.authId == null) {
                                        style {
                                            backgroundColor += Color.RED
                                        }
                                    } else {
                                        style {
                                            backgroundColor += Color.GREEN
                                        }
                                    }
                                    text = it.toString()
                                }
}

I found a memeber function "refreshableWhen " for tableview , Can I via " refreshableWhen " to refresh the tableview for loose coupling? Thanks !

edvin commented 7 years ago

In JavaFX, you don't refresh the table, you refresh the data that your table is bound to. Worst case you can also reassign the items property of the TableView, but under must circumstances you would simply call myData.setAll(newData) to change the data and consequently update the TableView. You omitted the items assignment in your example, but consider something like this:

val myData = FXCollections.observableArrayList<KingdeeOrderEntry>()

You can now bind this data to your TableView:

tableview(myData) {
...
}

Notice that you no longer need to specify the type for the TableView, since it can be inferred from the data.

Lastly, to update that data:

button("Refresh").action {
    myData.setAll(newDataHere)
}

If you are getting your data from a remoting call for example, you should look into asyncItems:

myData.asyncItems { someCallThatReturnsItems() }

The refreshable statement is for use with the Workspace, so unless your app is Workspace based you shouldn't use that.

Another optimization hint: You can get away with a single style block in your cellFormat functions if you change the logic around a little:

style {
   backgroundColor += if (rowItem.authId == null) Color.RED else Color.GREEN
}
toplinuxsir commented 7 years ago

Thanks 1

Y2Kot commented 6 years ago

Hello, how can I add items one by one? I have a loop(getting lines from console app) and want to add info to tableview after each iteration.

bekwam commented 6 years ago

We usually bind the TableView to a backing ObservableList. Then, your loop can update the ObservableList using add() and changes will be reflected in the TableView (because of the binding).

The class MyView in the link below has a studentList variable bound to the table. You would call studentList.add(item) to add additional students.

https://courses.bekwam.net/~walkerro/bkcourse_tornadofx_style_tableview.html

Good luck!

Y2Kot commented 6 years ago
fun startScanner() {
    val foundFoo = FXCollections.observableArrayList<MyObject>()
    val process = Runtime.getRuntime().exec(SCANNER_PATH)
    while (process.isAlive) {
        val source = process.inputStream.bufferedReader().readLine()
        if (source != null ) {
            val line = source.split(" ")
            ScanView().myObservableList.add(MyObject(line[0], line[1], line[2], line[3], line[4], line[5], line[6]))
            /*foundFoo.add(MyObject(line[0], line[1], line[2], line[3], line[4], line[5], line[6]))
            println(findFoo.size)
            ScanView().myObservableList.setAll(foundFoo)*/
        }
    }
}

I tried some ways, but it didn't refresh (I save all tryings in comments)

bekwam commented 6 years ago

It looks like you have two ScanView() instances and you're not updating the one visible on the screen. You're updating a newly-created ScanView which is never displayed. With TornadoFX dependency injection, you'll need to call find() to get THE singleton instance.

Once you get your data on the screen, you'll want to use Tasks to keep the application lively during a long scan operation. Check out this article which also adds a cancel example.

https://courses.bekwam.net/public_tutorials/bkcourse_tornadofx_cancelapp.html

If you need more help like this, I recommend joining the TornadoFX channel on the Kotlin Slack at kotlinlang.slack.com.

Y2Kot commented 6 years ago

Thanks! find method made magic

lisovskey commented 5 years ago

@edvin It's not working now for some reason

class AdminView : View() {
    val controller : AdminController by inject()
    var users = mutableListOf<UserModel>().observable()

    override val root = vbox {
        tableview(users) {
            column("Id", UserModel::id.getter)
            column("Login", UserModel::login.getter)
            column("Password", UserModel::password.getter)
            column("Admin", UserModel::admin.getter)
        }
        button("refresh").action {
            users.asyncItems { controller.getUsers() }
        }
    }

    override fun onDock() {
        super.onDock()
        users.asyncItems { controller.getUsers() }
    }
}

class UserModel : ViewModel {
    var id: SimpleIntegerProperty
    var login: SimpleStringProperty
    var password: SimpleStringProperty
    var admin: SimpleBooleanProperty

    constructor() : super() {
        this.id = bind { SimpleIntegerProperty() }
        this.login = bind { SimpleStringProperty() }
        this.password = bind { SimpleStringProperty() }
        this.admin = bind { SimpleBooleanProperty() }
    }

    constructor(user: User) : super() {
        this.id = bind { SimpleIntegerProperty(user.id) }
        this.login = bind { SimpleStringProperty(user.login) }
        this.password = bind { SimpleStringProperty(user.password) }
        this.admin = bind { SimpleBooleanProperty(user.admin) }
    }
}

Table updates only after second docking and refresh button does not affect anything

edvin commented 5 years ago

You should not use a ViewModel in the table like this, and you should absolutely not reference the getters, but rather the observable properties. Your use class should look like:

class User {
    val idProperty = SimpleIntegerProperty()
    var id by idProperty

    // Same for the rest of the properties
}

Then you should bind against the property in the tableview, like this:

column("Id", User::idProperty)

Define the user list like this:

val users = SortedFilteredList<User>()

Create an onRefresh callback and put your refresh code there, and only there:

override fun onRefresh() {
    users.asyncItems { controller.getUsers() }
}

Then call this function from onDock and your Button action.

Lastly, don't highjack other threads, create a new if you have an issue, and post a complete runnable sample :)

Edvin