如何在 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 实例的 nameObjectProperty 实例的值之间建立关系最初与它相关联。也就是说,您所做的只是更改 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,在这种情况下,不能implicitclass。我们想保留一个 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"