简体   繁体   中英

How can I update TableView in ScalaFX?

I have a table view. When I update the properties of one row, I can not see the modifications? For example:

implicit class PersonView(p:Person) {
  val fname = new ObjectProperty(this, "fname",p.name)
}

and in my table view

lazy val tableLines = ObservableBuffer(persView)
val personTable = new TableView[PersonView](tableLines) {
  columns ++= List(
    new TableColumn[PersonView, String] {
      text = "Name"
      cellValueFactory = _.value.fname
      cellFactory = { _ =>
        new TableCell[PersonView, String] {
          item.onChange { (_, _, newValue) => text = newValue }
        }
      }
    }
  )
}

It works fine, but when I update the name, I can not see that in GUI.

Firstly, I'll attempt to summarize what I'm seeing, and how I think you might get this to work:

The PersonView class decorates a Person instance by providing an fname property, that is initialized to the name field of the associated Person . When creating each cell in the "Name" column, you create such a property and associate it with the value of the cell. Henceforth, whenever the value of that property changes, the cell will automatically change its item field to show the new value of that property. (BTW, the onChange property is redundant and unnecessary—it provides an opportunity to perform some other actions when the item property—that is, the bound fname property—changes, so the cell will have already been updated when it executes.)

So, if you now change the name of a Person instance, what happens to the cell for that Person in the "Name" column? Nothing.

Why?

Firstly, as @James_D points out, you have not established a relationship between the name of a Person instance, and the value of the ObjectProperty instance originally associated with it. That is, all you've done is change a String value. For the GUI to be updated, the value of that ObjectProperty needs to change too.

Adding to your problem is the fact that there is no relationship from the Person to its associated PersonView . So, when the Person name field is changed, there's no way for the Person to person to notify its PersonView . Worse, by making PersonView an implicit class, you're suggesting that PersonView instances themselves are unimportant and transient, existing temporarily solely to decorate some Person instance with an additional set of methods and/or properties.

So, how can we change things so that they work as you might expect? There are two basic approaches, and your choice will depend upon how much control you can exert on the Person class. The key in both cases is to ensure that the StringProperty (a better option than an ObjectProperty , incidentally) containing the name of the Person changes whenever the name of the Person is changed...

Firstly, the simplest method is to do away with PersonView class altogether. Clearly, you'll need to be able to edit Person to do this; if you cannot, you'll have to try the second approach. Person should be modified to add an fname property field, with name being converted to a function that reports the current value of fname :

// initName is the initial name of the Person, and may be changed later...
class Person(initName: String, /*Whatever other arguments you require*/) {

  // String property storing this Person's name. Name is initialized to initName.
  val fname = new StringProperty(this, "fname", initName)

  // Report the current name of this Person.
  def name = fname.value

  // This function is not necessary, since we could change the value through fname directly
  // but it does look better...
  def name_=(newName: String): Unit = fname.value = newName
}

In this case, your table initialization now looks like this:

val tableLines = ObservableBuffer(persView) // Of Person, not PersonView!
val personTable = new TableView[Person](tableLines) {
  columns ++= List(
    new TableColumn[Person, String] {
      text = "Name"
      cellValueFactory = _.value.fname
      // No need for a cellFactory - default works fine.
    }
  )
}

Now, you can change the name of a Person like this:

val someone = new Person("Bob"/*, etc...*/)
someone.name = "Fred"

And all is good. The fname property, the name field and the value of the corresponding cell in the GUI table, will now all have the same value.

The second approach is required if you cannot modify the definition of the Person type. Here, we use PersonView to change the names of Person instances, and hope that no-one changes Person names outside of our control. (That is, if some other code modifies the name of a Person instance without going through PersonView , then we'll know nothing about it, and the GUI will not be updated accordingly.)

PersonView , in this case, must not be an implicit class. We want to retain a PersonView instance and use it to interact with an associated Person instance. PersonView now looks like this:

class PersonView(p: Person) {

  // String property initialized to the name of the associated person.
  val fname = new StringProperty(this, "fname", p.name)

  // Change the name of the person. Note that we MUST also change the name of the
  // associated person instance.
  def name_=(newName: String): Unit = {

    // Change the name of the Person instance. Verify it has the value we think it has.
    assert(p.name == fname.value)
    p.name = newName // Might be p.setName(newName), etc. in your case

    // Change the name of our property.
    fname.value = newName
  }
}

Now, say you have a list of Person instances, you'll need to map them to PersonView instances, and use those latter instances subsequently.

Your GUI code now looks like this:

val tableLines = ObservableBuffer(persView)
val personTable = new TableView[PersonView](tableLines) {
  columns ++= List(
    new TableColumn[PersonView, String] {
      text = "Name"
      cellValueFactory = _.value.fname
      // No need for a cellFactory - default works fine.
    }
  )
}

Changing the names of people is now a little more complex, because we need to be able to find the right PersonView instance, but it would look like this:

val someone = new Person("Bob"/*, etc...*/)
val someoneView = new PersonView(someone)
someoneView.name = "Fred"

And all is good once again. The PersonView.fname property, the Person.name field and the value of the corresponding cell in the GUI table (once someoneView is added to the tableLines observable), will now all have the same value.

However, the following line just changes the name of a Person instance. The PersonView and GUI do not get updated:

someone.name = "Eric"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM