如何在 ScalaFX 中更新 TableView?



我有一个表格视图。当我更新一行的属性时,我看不到修改?例如:

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

在我的表格视图中

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

它工作正常,但是当我更新名称时,我在 GUI 中看不到它。

首先,我将尝试总结我所看到的,以及我认为您如何使其工作:

PersonView类通过提供fname属性来修饰Person实例,该属性初始化为关联Personname字段。在"名称"列中创建每个单元格时,您可以创建此类属性并将其与单元格的值相关联。此后,每当该属性的值更改时,单元格将自动更改其item字段以显示该属性的新值。(顺便说一句,onChange属性是冗余且不必要的 — 当item属性(即绑定的fname属性)更改时,它提供了执行一些其他操作的机会,因此单元格在执行时已经更新。

因此,如果您现在更改Person实例的名称,那么"名称"列中该Person的单元格会发生什么情况?无。

为什么?

首先,正如@James_D指出的那样,您尚未在Person实例的name与最初与之关联的ObjectProperty实例的值之间建立关系。也就是说,您所做的只是更改String值。要更新 GUI,该ObjectProperty的值也需要更改。

增加您的问题的事实是,从Person到其相关PersonView没有关系。因此,当Personname字段更改时,Personto person无法通知其PersonView。更糟糕的是,通过使PersonView成为implicit类,您暗示PersonView实例本身是不重要和暂时的,只是为了使用一组额外的方法和/或属性来装饰某些Person实例。

那么,我们如何才能改变事物,使它们像您期望的那样工作呢?有两种基本方法,您的选择将取决于您可以对Person类施加多少控制。这两种情况下的关键是确保包含Person名称的StringProperty(顺便说一下,比ObjectProperty更好的选择)在更改Personname时更改......

首先,最简单的方法是完全取消PersonView类。显然,您需要能够编辑Person才能执行此操作;如果不能,则必须尝试第二种方法。 应修改Person以添加fname属性字段,name转换为报告当前值的函数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
}

在这种情况下,表初始化现在如下所示:

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

现在,您可以像这样更改Person的名称:

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

一切都很好。fname属性、name字段GUI 表中相应单元格的值现在都将具有相同的值。

如果无法修改Person类型的定义,则需要第二种方法。在这里,我们使用PersonView来更改Person实例的名称,并希望没有人更改Person超出我们控制范围的名称。(也就是说,如果其他一些代码修改了Person实例的名称而不经过PersonView,那么我们将对此一无所知,并且GUI将不会相应地更新。

在这种情况下,PersonView不能implicit类。我们希望保留一个PersonView实例,并使用它来与关联的Person实例进行交互。PersonView现在看起来像这样:

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

现在,假设您有一个Person实例的列表,您需要将它们映射到PersonView实例,然后使用后一个实例。

您的 GUI 代码现在如下所示:

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

更改人员姓名现在有点复杂,因为我们需要能够找到正确的PersonView实例,但它看起来像这样:

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

一切又好了。PersonView.fname属性、Person.name字段GUI 表中相应单元格的值(一旦someoneView添加到可观察tableLines)现在都将具有相同的值。

但是,以下行只是更改Person实例的名称。PersonView和 GUI 不会更新:

someone.name = "Eric"

最新更新