Table, потеря фокуса при добавлении, удалении генерируемых колонок

Здравствуйте, cuba ver. 7.2.11
Две сущности: invoice, invoiceItem, отношение - композиция, один ко многим. Сгенерирован экран редактирования для invoice, в таблице invoiceItems генерируемые колонки:

        <table id="invoiceItemsTable" dataContainer="invoiceItemsDc" width="100%" height="600px" editable="true">
              ...
            <columns>
                <column id="name" editable="true" generator="newNameCell"/>
                <column id="price" editable="true" generator="newPriceCell"/>
                <column id="nds" editable="true" generator="newNdsCell"/>
                <column id="currency" editable="true" generator="newCurrencyCell"/>
                <column id="quantity" editable="true"/>
            </columns>
            <buttonsPanel>
                <checkBox id="readOnlyField" caption="Read only"/>
                 ...
            </buttonsPanel>
        </table>

Пример генераторов:

fun newNameCell(entity: InvoiceItem): Component {
    val field = uiComponents.create(TextField.NAME) as TextField<String>
    setValueSource(field, entity, "name")
    field.setWidthFull()
    return field
}
private fun setValueSource(field: Component, entity: InvoiceItem, property: String) {
    if (field is HasValueSource<*>) {
        val ic = invoiceItemsTable.getInstanceContainer(entity)
        (field as HasValueSource<*>).valueSource = ContainerValueSource(ic, property)
    }
}

Так же добавлен чекбокс, который переключает таблицу в режим просмотра\редактирования:

private val columnsGeneratorMap = hashMapOf<String, Function<InvoiceItem, Component>>(
    "name" to Function { newNameCell(it) },
    "price" to Function { newPriceCell(it) },
    "nds" to Function { newNdsCell(it) },
    "currency" to Function { newCurrencyCell(it) }
)

@Subscribe("readOnlyField")
private fun onReadOnlyFieldValueChange(event: HasValue.ValueChangeEvent<Boolean>) {
    changeReadOnly(event.value!!)
}

fun changeReadOnly(readOnly: Boolean) {
    invoiceItemsTable.isEditable = !readOnly
    if (readOnly) {
        columnsGeneratorMap.keys.forEach { invoiceItemsTable.removeGeneratedColumn(it) }
    } else {
        columnsGeneratorMap.forEach { pair ->
            invoiceItemsTable.addGeneratedColumn(pair.key) {
                pair.value.apply(it)
            }
        }
    }
}

И переопределен экшен создания нового invoiceItem:

@Subscribe("invoiceItemsTable.create")
private fun onInvoiceItemsTableCreate(event: Action.ActionPerformedEvent) {
    if (!invoiceItemsTable.isEditable) {
        changeReadOnly(false)
    }
    var newItem: InvoiceItem = metadata.create(InvoiceItem::class.java)
    newItem.invoice = editedEntity
    newItem = dataContext.merge(newItem)
    invoiceItemsDc.mutableItems += newItem
    invoiceItemsTable.setSelected(newItem)
}

1.При первоначальном открытии редактора, поведение при переключении табом нормальное:
https://recordit.co/DD4k74AKDR
После переключения режима редактирования, фокус табом сбивается:
https://recordit.co/HichU1Owjv
2. Для генерируемой колонки price добавил кэш компонента, в котором обновляю valueSource:

//HashMap<Pair<entityId, columnId>, field>
private val fields = HashMap<Pair<UUID, String>, Component>()

fun newPriceCell(entity: InvoiceItem): Component {
    val field = fields.computeIfAbsent(Pair(entity.id, "price")) {
        val field = uiComponents.create(TextField.NAME) as TextField<BigDecimal>
        field.setWidthFull()
        return@computeIfAbsent field
    }
    setValueSource(field, entity, "price")
    return field
}

Значения, вводимые в эту колонку после переключения режима редактирования применяется через раз:
https://recordit.co/qc9AXj1WJt

Демо проект:
tableeditor.rar (76.5 КБ)

Здравствуйте!

Частично проблема связана вот с этой: 7.2 Фокус в редактируемых колонках таблицы.

Когда удаляются генериремые колонки, в контейнер таблицы добавляется слушатель, который после добавления генерируемых колонок, лишний раз перерисовывает таблицу.

Как обходное решение могу предложить заменить последнюю колонку на генерируемую и не изменять editable таблицы, а сохранять это состояние и проверять его в функциях генерируемых колонок:

fun changeReadOnly(readOnly: Boolean) {
    val shouldBeEditable = !readOnly
    if (isTableEditable != shouldBeEditable) {
        isTableEditable = shouldBeEditable
        
        invoiceItemsTable.repaint()
    }
}

В колонки можно добавить такую проверку:

fun newNameCell(entity: InvoiceItem): Component {
    if (!isTableEditable) {
        return Table.PlainTextCell(metadataTools.format(entity.name))
    }
    ...
    return field
}

@pinyazhin, Спасибо!
Подскажите, рекомендуете ли вы использовать кэш компонентов (у компонентов могут быть valueChangeListener’ы, которые меняют значения в соседних колонках и валидаторы) для генераторов, или он может вызывать какие-то сайд эффекты?
В таблице порядка 20 колонок и может быть 50+ строк .
И вопрос по кэшу:

private val fields = HashMap<Pair<UUID, String>, Component>()

fun newPriceCell(entity: InvoiceItem): Component {
    val field = fields.computeIfAbsent(Pair(entity.id, "price")) {
        val field = uiComponents.create(TextField.NAME) as TextField<BigDecimal>
        field.setWidthFull()
        return@computeIfAbsent field
    }
    setValueSource(field, entity, "price")
    return field
}

private fun setValueSource(field: Component, entity: InvoiceItem, property: String) {
    if (field is HasValueSource<*>) {
        val ic = invoiceItemsTable.getInstanceContainer(entity)
        (field as HasValueSource<*>).valueSource = ContainerValueSource(ic, property)
    }
}

При такой реализации при переключении режима редактирования все ок, но если сделать так:

fun newPriceCell(entity: InvoiceItem): Component {
    return fields.computeIfAbsent(Pair(entity.id, "price")) {
        val field = uiComponents.create(TextField.NAME) as TextField<BigDecimal>
        field.setWidthFull()
        setValueSource(field, entity, "price")
        return@computeIfAbsent field
    }
}

поменять значение в компоненте, и переключиться в режим просмотра - значение будет старое.

Добрый день!

К сожалению, по сайд эффектам не могу сказать. Если таблица большая и компонентов будет много, возможно стоит посмотреть в сторону DataGrid с его редактором строк: DataGrid Использование режима внутристрочного редактирования.

По поводу проблемы. InstanceContainer-ы, которые простовляются в поля кэшируются в таблице. И при каждой перерисовке таблицы этот кэш сбрасывается, причем сами InstanceContainer-ы больше не содержат ссылки на редактируемую сущность. Поэтому во втором варианте, когда берётся поле из кэша, в нём хранится InstanceContainer без сущности и соответственно изменения в таком поле сохраняться в сущность не будут.