【问题标题】:Bind an observable property to a collection function?将可观察属性绑定到集合函数?
【发布时间】:2020-01-06 03:51:26
【问题描述】:

我有一个类Market,其中包含一个名为m_updatesMarketUpdate 对象集合。对于 UI,我使用类型安全构建器在 tableview 中创建列,如下所示:

override val root = tableview<Market> {
    val sortedMarketList = SortedList<Market>(markets)
    sortedMarketList.comparatorProperty().bind(this.comparatorProperty())
    items = sortedMarketList
...
column("Strikes", Market::m_strikes)
...

m_strikes 属性只是 Market 对象直接拥有的 SimpleIntegerProperty。但是,我需要能够构建这样的列:

...
column("Created At", Market::m_updates::first::m_time)
...

...
column("Last Update", Market::m_updates::last::m_time)
...

其中m_timeMarketUpdate 对象拥有的SimpleLongProperty。当Market 对象更新时,一个新的MarketUpdate 对象将添加到m_updates 集合的末尾。这意味着绑定需要自动从一个对象转换到另一个对象,并且需要通知 tableview 并更新自身以反映新对象中的数据。我认为通过上述集合的first()last() 函数进行绑定以非常简单的方式捕获了这个想法,但它不会编译。

有很多属性,例如m_strikesm_time。我怎样才能优雅地做到这一点?

【问题讨论】:

  • 我想我现在正在使用 SimpleObjectProperty 和一个虚拟对象做一些事情,但我不相信一旦进行了所有必要的调整后它会正常工作。一旦我能够测试它,我会发表另一条评论。
  • 我尝试的解决方案是将val m_first = SimpleObjectProperty&lt;MarketUpdate&gt;(dummyMarketUpdate) 添加到市场对象,在适当的时候更新它,然后将列行更改为column("Created At", Market::m_first::get)。这需要按照text = it.m_time.get().toString() 的行设置文本,但这没什么大不了的。不幸的是,此解决方案会阻止表格的排序功能正常工作。它可能会尝试根据 m_first.get() 返回的内容而不是单元格中显示的文本进行排序。
  • Comparator lambda 可能已经解决了排序问题。需要确保对实时数据进行正确排序。

标签: javafx collections binding kotlin tornadofx


【解决方案1】:

如果我了解您的用例,您要做的是创建一个可观察值,该值表示给定 Market 对象中第一次和最后一次更新的时间属性。为此,您可以基于每个Market 对象内的updates 列表创建objectBinding,然后提取first()last() 元素的timeProperty。在以下示例中,只要您在任何 Market 对象中增加更新列表,TableView 就会更新。

请记住,该示例要求每个Market 至少有一个更新。如果这不是您的情况,请确保相应地处理 null。

class Market {
    val updates = FXCollections.observableArrayList<MarketUpdate>()
}

class MarketUpdate {
    val timeProperty = SimpleObjectProperty(LocalDateTime.now())
}

class MarketList : View("Markets") {
    val markets = FXCollections.observableArrayList<Market>()
    val data = SortedFilteredList<Market>(markets)

    override val root = borderpane {
        prefWidth = 500.0

        center {
            tableview(markets) {
                column<Market, LocalDateTime>("Created at", { objectBinding(it.value.updates) { first() }.select { it!!.timeProperty } })
                column<Market, LocalDateTime>("Last update", { objectBinding(it.value.updates) { last() }.select { it!!.timeProperty } })
            }
        }
        bottom {
            toolbar {
                // Click to add an update to the first entry
                button("Add update").action {
                    markets.first().updates.add(MarketUpdate())
                }
            }
        }
    }

    init {
        // Add some test entries
        markets.addAll(
                Market().apply { updates.addAll(MarketUpdate(), MarketUpdate(), MarketUpdate()) },
                Market().apply { updates.addAll(MarketUpdate(), MarketUpdate(), MarketUpdate()) },
                Market().apply { updates.addAll(MarketUpdate(), MarketUpdate(), MarketUpdate()) },
                Market().apply { updates.addAll(MarketUpdate(), MarketUpdate(), MarketUpdate()) }
        )
    }
}

我使用了 SortedFilteredList 来更轻松地处理排序。排序在这里起作用的原因是列实际上是由 LocalDateTime 值表示的。

我希望这能给你一些想法:)

【讨论】:

  • 谢谢。这里几乎是凌晨 5 点,但我会在休息后尝试整合其中的一些想法。我目前正在走的路径还不错,显示更新并且排序在隔离中正常工作,但更新当前没有触发它,所以它最终偏离了排序状态。我之前的代码中有一个 hacky 回调来解决这个问题。无论如何,我会让你知道发生了什么。
  • 太棒了!我认为如果您可以依赖可观察值,最好避免手动更新代码,这就是我一直追求的目标。
  • 你好。有问题。表格视图不会随着数据的变化而自动排序。可以使用列标题手动排序,但是当基础数据更新时,行不会重新排序。我之前遇到过这个问题,这就是我使用上面提到的 hacky 回调的原因,但我不知道在这种情况下如何解决它。在您的示例中,如果我手动按升序对“上次更新”列进行排序并按“添加更新”,我希望整行移动到列表的底部。如何做到这一点?
  • 即使列数据发生变化,TableView 也不会重新评估排序条件,因此您必须在 TableView 上调用 sort。一个很好的方法是创建一个事件并在您扩充数据时触发该事件。然后您可以在 TableView 构建器中收听该事件并调用 sort:subscribe&lt;MarketChanged&gt; { sort() }
  • 我应该担心比赛条件吗?也就是说,即使底层数据的所有更新都发生在创建 MarketChanged 事件的同一个线程上,并且最后创建了 MarketChanged 事件,是否有可能在某些列更新之前消费了 MarketChanged 事件发生了吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-05
  • 2013-05-21
  • 2012-06-30
  • 1970-01-01
  • 2021-11-22
  • 2020-12-29
相关资源
最近更新 更多