Привязать наблюдаемое свойство к функции коллекции?

У меня есть класс Market, который содержит набор объектов MarketUpdate с именем m_updates. Для пользовательского интерфейса я использую конструкторы, безопасные для типов, для создания столбцов в табличном представлении следующим образом:

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

Свойство m_strikes — это просто свойство SimpleIntegerProperty, непосредственно принадлежащее объекту Market. Однако мне нужно иметь возможность создавать такие столбцы:

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

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

где m_time — свойство SimpleLongProperty, принадлежащее объекту MarketUpdate. Когда объект Market обновляется, новый объект MarketUpdate добавляется в конец коллекции m_updates. Это означает, что привязка должна автоматически переходить от одного объекта к другому, и что табличное представление должно быть уведомлено и обновлено, чтобы отразить данные в новом объекте. Я думаю, что привязка с помощью функций first() и last() коллекции, как описано выше, очень просто передает идею, но не компилируется.

Есть много свойств, таких как m_strikes и m_time. Как я могу добиться этого изящно?


person HerdleDerdle    schedule 31.08.2017    source источник
comment
Я думаю, что в данный момент я что-то понял, используя SimpleObjectProperty и фиктивный объект, но я не уверен, что он будет работать должным образом после внесения всех необходимых корректировок. Я опубликую еще один комментарий, как только смогу проверить его.   -  person HerdleDerdle    schedule 31.08.2017
comment
Решение, с которым я экспериментировал, заключалось в том, чтобы добавить val m_first = SimpleObjectProperty<MarketUpdate>(dummyMarketUpdate) к объекту Market, обновить его при необходимости, а затем изменить строку столбца на column("Created At", Market::m_first::get). Это требует, чтобы текст был установлен по строкам text = it.m_time.get().toString(), но это не имеет большого значения. К сожалению, это решение препятствует правильной работе функции сортировки таблицы. Возможно, он пытается выполнить сортировку на основе того, что было возвращено m_first.get(), а не текста, отображаемого в ячейке.   -  person HerdleDerdle    schedule 31.08.2017
comment
Компаратор лямбда, возможно, решил проблему сортировки. Необходимо убедиться, что оперативные данные отсортированы правильно.   -  person HerdleDerdle    schedule 31.08.2017


Ответы (1)


Насколько я понимаю ваш вариант использования, вы хотите создать наблюдаемое значение, которое представляет свойство времени для первого и последнего обновлений в данном объекте Market. Для этого вы можете создать objectBinding на основе списка updates внутри каждого объекта Market, а затем извлечь timeProperty элемента first() или last(). В следующем примере TableView будет обновляться, как только вы дополните список обновлений в любом объекте Market.

Имейте в виду, что в примере требуется, чтобы каждый 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.

Результирующий интерфейс

Я надеюсь, что это даст вам некоторые идеи :)

person Edvin Syse    schedule 31.08.2017
comment
Спасибо за это. Сейчас почти 5 утра, но я попытаюсь интегрировать некоторые из этих идей после того, как немного отдохну. Путь, по которому я сейчас иду, не так уж плох, обновления отображаются, и сортировка работает должным образом изолированно, но в настоящее время обновления не вызывают ее, поэтому в конечном итоге она уходит от отсортированного состояния. У меня был хакерский обратный вызов в моем предыдущем коде для решения этой проблемы. В любом случае, я дам вам знать, что происходит. - person HerdleDerdle; 31.08.2017
comment
Большой! Я думаю, что лучше избегать ручного обновления кода, если вы можете полагаться на наблюдаемые значения, к чему я всегда стремлюсь. - person Edvin Syse; 31.08.2017
comment
И снова здравствуйте. Наличие проблемы. Табличное представление не сортируется автоматически по мере изменения данных. Возможна ручная сортировка с использованием заголовков столбцов, но при обновлении базовых данных порядок строк не меняется. У меня была эта проблема раньше, поэтому я использовал хакерский обратный вызов, о котором я упоминал выше, но я не знаю, как его решить в этих условиях. В вашем примере, если я вручную отсортирую столбец «Последнее обновление» в порядке возрастания и нажму «Добавить обновление», я ожидаю, что вся строка будет перемещена в конец списка. Как этого добиться? - person HerdleDerdle; 31.08.2017
comment
TableView не переоценивает критерии сортировки даже при изменении данных столбца, поэтому вам нужно вызвать сортировку в TableView. Хороший способ сделать это — создать событие и активировать его при дополнении данных. Затем вы можете прослушать это событие внутри построителя TableView и вызвать сортировку: subscribe<MarketChanged> { sort() } - person Edvin Syse; 01.09.2017
comment
Должен ли я беспокоиться о состоянии гонки? Другими словами, даже если все обновления базовых данных происходят в том же потоке, который создает событие MarketChanged, а событие MarketChanged создается последним, возможно ли, что событие MarketChanged будет использовано до того, как некоторые из обновлений столбца будут обработаны? произошло? - person HerdleDerdle; 01.09.2017
comment
Нет, просто убедитесь, что вы добавили данные в поток пользовательского интерфейса — не нужно беспокоиться об условиях гонки. - person Edvin Syse; 02.09.2017
comment
Я был готов к тому, что это станет источником сотен других проблем, но на данный момент пользовательский интерфейс все еще достаточно отзывчив при простом вызове Platform.runLater. Фоновый поток выполняет работу с REST API и впоследствии передает эстафету потоку пользовательского интерфейса. Но я придумал простую логику наблюдения вместо того, чтобы пытаться использовать события, поскольку у меня не было узла, с которого можно было бы притворяться, что я запускаю событие. Спасибо за вашу помощь. - person HerdleDerdle; 04.09.2017
comment
События вообще не запускаются с узлов? - person Edvin Syse; 04.09.2017
comment
О, вы говорите о классе TornadoFX под названием FXEvent? Возможно, я начал исследовать, как создать собственное событие JavaFX, которое, как указано в статье StackOverflow, будет запущено из узла, среди прочего. Смешение и сопоставление номенклатуры иногда может сбивать с толку. Ваше здоровье. - person HerdleDerdle; 04.09.2017
comment
Да, я говорю о FXEvent и EventBus в TornadoFX :) github.com/edvin/tornadofx-guide/blob/master/15.%20EventBus.md - person Edvin Syse; 06.09.2017