如何在 ScalaFX 中更新 TableView?
How can I update TableView in ScalaFX?
我有一个 table 观点。当我更新一行的属性时,我看不到修改?例如:
implicit class PersonView(p:Person) {
val fname = new ObjectProperty(this, "fname",p.name)
}
在我的 table 视图中
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
class 通过提供一个 fname
属性 来装饰一个 Person
实例,它被初始化为 name
字段关联 Person
。在 "Name" 列中创建每个单元格时,您创建这样一个 属性 并将其与单元格的值相关联。此后,每当 属性 的值发生变化时,单元格将自动更改其 item
字段以显示 属性 的新值。 (顺便说一句,onChange
属性 是多余的和不必要的——它提供了在 item
属性 时执行一些其他操作的机会——也就是说,绑定 fname
属性—更改,因此单元格在执行时已经更新。)
那么,如果您现在更改 Person
实例的名称,"Name" 列中 Person
的单元格会发生什么变化?没有。
为什么?
首先,正如@James_D 所指出的,您尚未在 Person
实例的 name
与 ObjectProperty
实例的值之间建立关系最初与它相关联。也就是说,您所做的只是更改 String
值。要更新 GUI,ObjectProperty
的值也需要更改。
添加到您的问题的事实是 Person
与其关联的 PersonView
没有任何关系。因此,当 Person
name
字段更改时,Person
的人无法通知其 PersonView
。更糟糕的是,通过将 PersonView
设为 implicit
class,您暗示 PersonView
实例本身不重要且短暂,临时存在只是为了装饰某些 Person
实例使用一组额外的方法 and/or 属性。
那么,我们怎样才能改变事情,使它们按您预期的那样工作?有两种基本方法,您的选择将取决于您可以对 Person
class 施加多少控制。在这两种情况下,关键是确保包含 Person
名称的 StringProperty
(顺便说一下,比 ObjectProperty
更好的选择)只要 name
发生变化 Person
已更改...
首先,最简单的方法是完全取消PersonView
class。显然,您需要能够编辑 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
}
在这种情况下,您的 table 初始化现在看起来像这样:
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 中相应单元格的值 table 现在都将具有相同的值。
如果您无法修改 Person
类型的定义,则需要第二种方法。在这里,我们使用 PersonView
来更改 Person
个实例的名称,并希望没有人在我们的控制范围之外更改 Person
名称。 (也就是说,如果某些其他代码修改了 Person
实例的名称而没有经过 PersonView
,那么我们将一无所知,并且 GUI 也不会相应地更新。)
PersonView
,在这种情况下,不能是implicit
class。我们想保留一个 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中相应单元格的值table(一旦someoneView
被添加到 tableLines
observable),现在都将具有相同的值。
但是,下一行只是更改了 Person
实例的名称。 PersonView
和 GUI 不 得到更新:
someone.name = "Eric"
我有一个 table 观点。当我更新一行的属性时,我看不到修改?例如:
implicit class PersonView(p:Person) {
val fname = new ObjectProperty(this, "fname",p.name)
}
在我的 table 视图中
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
class 通过提供一个 fname
属性 来装饰一个 Person
实例,它被初始化为 name
字段关联 Person
。在 "Name" 列中创建每个单元格时,您创建这样一个 属性 并将其与单元格的值相关联。此后,每当 属性 的值发生变化时,单元格将自动更改其 item
字段以显示 属性 的新值。 (顺便说一句,onChange
属性 是多余的和不必要的——它提供了在 item
属性 时执行一些其他操作的机会——也就是说,绑定 fname
属性—更改,因此单元格在执行时已经更新。)
那么,如果您现在更改 Person
实例的名称,"Name" 列中 Person
的单元格会发生什么变化?没有。
为什么?
首先,正如@James_D 所指出的,您尚未在 Person
实例的 name
与 ObjectProperty
实例的值之间建立关系最初与它相关联。也就是说,您所做的只是更改 String
值。要更新 GUI,ObjectProperty
的值也需要更改。
添加到您的问题的事实是 Person
与其关联的 PersonView
没有任何关系。因此,当 Person
name
字段更改时,Person
的人无法通知其 PersonView
。更糟糕的是,通过将 PersonView
设为 implicit
class,您暗示 PersonView
实例本身不重要且短暂,临时存在只是为了装饰某些 Person
实例使用一组额外的方法 and/or 属性。
那么,我们怎样才能改变事情,使它们按您预期的那样工作?有两种基本方法,您的选择将取决于您可以对 Person
class 施加多少控制。在这两种情况下,关键是确保包含 Person
名称的 StringProperty
(顺便说一下,比 ObjectProperty
更好的选择)只要 name
发生变化 Person
已更改...
首先,最简单的方法是完全取消PersonView
class。显然,您需要能够编辑 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
}
在这种情况下,您的 table 初始化现在看起来像这样:
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 中相应单元格的值 table 现在都将具有相同的值。
如果您无法修改 Person
类型的定义,则需要第二种方法。在这里,我们使用 PersonView
来更改 Person
个实例的名称,并希望没有人在我们的控制范围之外更改 Person
名称。 (也就是说,如果某些其他代码修改了 Person
实例的名称而没有经过 PersonView
,那么我们将一无所知,并且 GUI 也不会相应地更新。)
PersonView
,在这种情况下,不能是implicit
class。我们想保留一个 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中相应单元格的值table(一旦someoneView
被添加到 tableLines
observable),现在都将具有相同的值。
但是,下一行只是更改了 Person
实例的名称。 PersonView
和 GUI 不 得到更新:
someone.name = "Eric"