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"