enthought / enthought/traitsui
TableColumn renderer to display a combobox even when a cell is in display mode
- Dominant language
- Python
- Stars
- 306
- Forks
- 99
- PR merge metrics
- No merged PRs in 30d
Description
**Context**
Suppose one has an enum trait to be used as an `ObjectColumn` with the `TableEditor`, the enum can be edited using an EnumEditor:
```
from traits.api import HasStrictTraits, Str, Int, List, Instance, observe
from traitsui.api import EnumEditor, View, Item, TableEditor, ObjectColumn
class MyEntry(HasStrictTraits):
name = Str()
value = Int()
@observe("value")
def _print_value_change_event(self, event):
print(event)
class ManyEntry(HasStrictTraits):
entries = List(Instance(MyEntry))
view = View(
Item(
"entries",
editor=TableEditor(
columns=[
ObjectColumn(name="value", editor=EnumEditor(values=[0, 1, 2, 3])),
],
)
)
)
if __name__ == "__main__":
many = ManyEntry(entries=[MyEntry(), MyEntry()])
many.configure_traits()
```
This is how it behaves

**The issue**
The display mode of the item does not hint about the fact that it is editable. If it was drawn with a combobox, that would be more obvious.
**Feature/Enhancement request**
A renderer targeted at the EnumEditor which paints the combobox even during display mode.
Perhaps an ObjectColumn with this renderer provided as a default would be useful (similar to the `CheckboxColumn` in `traitsui.extras`).
**Performance Consideration**
- Drawing/painting a widget on a per-cell basis could be expensive if the table has many items. Therefore this should be an opt-in that users of TraitsUI can decide. In cases where a table is expected to contain a few items, it would be a nicer UX to immediately see a cell being editable.
**Potential implementation**
The following renderer will paint a combobox even when the cell is in display mode:
```
from pyface.qt import QtCore, QtGui
from traitsui.qt4.table_editor import TableDelegate
class EnumRenderer(TableDelegate):
""" Paint the combobox widget even when a cell is in display mode.
Note that this has a performance cost when a table contains many rows.
"""
def createEditor(self, parent, option, index):
control = super().createEditor(parent, option, index)
# this makes the editor has the same width as the combobox drawn while not being edited.
control.setSizeAdjustPolicy(
QtGui.QComboBox.AdjustToContentsOnFirstShow
)
return control
def paint(self, painter, option, index):
self.initStyleOption(option, index)
style = QtGui.QApplication.style()
cb_option = QtGui.QStyleOptionComboBox()
cb_option.palette = option.palette
cb_option.rect = option.rect
cb_option.state = option.state
cb_option.currentText = str(index.data(QtCore.Qt.DisplayRole))
widget = option.widget
style.drawComplexControl(QtGui.QStyle.CC_ComboBox, cb_option, painter, widget)
style.drawControl(
QtGui.QStyle.CE_ComboBoxLabel, cb_option, painter, widget
)
```
and define the `ObjectColumn` like this:
```
ObjectColumn(name="value", editor=EnumEditor(values=[0, 1, 2, 3]), renderer=EnumRenderer())
```
Then the view looks like this

**Orthogonal Observation: The value is changed even before the editor is closed**
- After the user has clicked a new value, they have to click somewhere else to close the editor, even though, actually, the underlying value has already changed when the combobox value changes. The idea of staying in an edit mode without closing the editor is to defer committing a change and to allow for a change to be cancelled (e.g. by hitting Escape), but that does not apply here. This is related to https://github.com/enthought/traitsui/issues/1299. In some situations, it would be desirable to commit and close the editor immediately. In some other situation, one may want to be able to cancel a change and/or defer the change until after the editor is closed.
- To close the editor immediately, one could do something like this in the renderer:
```
def createEditor(self, parent, option, index):
control = super().createEditor(parent, option, index)
control.setSizeAdjustPolicy(
QtGui.QComboBox.AdjustToContentsOnFirstShow
)
control.currentIndexChanged.connect(
self.commitAndCloseEditor, type=QtCore.Qt.QueuedConnection
)
return control
def commitAndCloseEditor(self):
sender = self.sender()
if sender is None:
return
self.commitData.emit(sender)
self.closeEditor.emit(sender)
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.