Я пытаюсь создать QTableView, который ведет себя подобно Excel в некоторых аспектах:
- Если значение ячейки начинается с
=
, оно будет рассматриваться как формула.
- Формулы могут ссылаться на другие ячейки в той же таблице (в обозначениях A1, но это просто деталь).
- Если 1) редактируемая ячейка, 2) значение редактирования начинается с
=
и 3) вы щелкаете по другой ячейке из того же QTableView, затем a) ссылка на эту ячейку автоматически добавляется в редактируемую формулу, а b) фокус остается в ячейке в режиме редактирования, так что вы можете продолжать редактировать формулу / добавлять дополнительные ссылки на другие ячейки.
Как я могу сделать этот последний элемент?
В идеале я хотел бы сначала обнаружить щелчок по ячейке, затем проверить, находится ли еще одна ячейка в режиме редактирования, добавить ссылку на эту ячейку и предотвратить потерю виджета режима редактирования (т. Е. QLineEdit
). фокус. Однако к тому времени, когда щелчок таблицы обнаружен, виджет режима редактирования уже потерял фокус и собирается быть уничтоженным. Похоже, не существует способа предотвратить потерю фокуса этим виджетом.
Обходной путь, который я нашел:
- Когда виджет редактирования теряет фокус, сохраните строку / столбец там, где он был, его последнюю позицию курсора и т. Д., И не пытайтесь отменить событие
- Если следующее нажатие - это ячейка, заново откройте ячейку в этой строке / столбце в режиме редактирования и добавьте ссылку, где позиция курсора была
Это работает достаточно хорошо, но иногда трудно убедиться, что ячейка действительно была , когда в следующий раз щелкнули . Вместо этого можно было бы сфокусироваться на совершенно другом элементе управления или щелкнуть другие части таблицы (например, заголовки / полосы прокрутки). focusOutEvent
QTableView, похоже, не срабатывает, если перед этим ячейка находилась в режиме редактирования.
Любые предложения о том, как это исправить, или любой другой подход, который я мог бы попробовать?
См. Пример моего кода ниже. Если вы редактируете ячейку двойным щелчком, затем вводите строку, которая начинается с =
, и щелкаете по другой ячейке, эта ссылка действительно добавляется к выражению. Проблемы начинаются, когда вы щелкаете где-то еще (полосы прокрутки или кнопка / QLineEdit ниже), а затем нажимаете обратно на ячейку таблицы (вы не ожидаете, что вернетесь в режим редактирования с добавленной ссылкой на эту ячейку). Я работаю с mouseReleaseEvent
, потому что таким образом я могу определить выделение области таблицы, а не только ячейки.
Примечание: Пример приведен в Python / PyQt5, но проблема должна быть такой же в C ++
import sys
from PyQt5 import QtCore, QtWidgets
Qt = QtCore.Qt
def num2col(num):
if num <= 0:
raise ValueError('The column index must be > 0')
letters = ''
while num:
mod = (num - 1) % 26
letters += chr(mod + 65)
num = (num - 1) // 26
return ''.join(reversed(letters))
def index2ref(row, col):
return '{}{}'.format(num2col(col + 1), row + 1)
class CellEdit(QtWidgets.QLineEdit):
def __init__(self, table, index, parent=None):
super(CellEdit, self).__init__(parent)
self._table = table # type: MyTableView
self._table.editingWidget = self
self.index = index # type: QtCore.QModelIndex
self._leavingNormally = False
def focusInEvent(self, event):
print('Focus gained for {},{}'.format(self.index.row(), self.index.column()))
def focusOutEvent(self, event):
print('Focus lost for {},{}'.format(self.index.row(), self.index.column()))
if self._leavingNormally:
return
if self._table.lastCell is None and self.text().startswith('='):
if self.hasSelectedText():
selection = (self.selectionStart(), len(self.selectedText()))
else:
selection = (self.cursorPosition(), 0)
self._table.lastCell = (
self.index,
self.text(),
selection
)
def keyPressEvent(self, e):
if e.key() in (Qt.Key_Enter, Qt.Key_Return):
print('Closing CellEdit normally by pressing Return')
self._leavingNormally = True
super(CellEdit, self).keyPressEvent(e)
def addReference(self, ref):
txt = self.text()
if self.hasSelectedText():
start = self.selectionStart()
end = start + len(self.selectedText())
else:
start = self.cursorPosition()
end = start
txt = txt[:start] + ref + txt[end:]
self.setText(txt)
self.setCursorPosition(start + len(ref))
class MyItemDelegate(QtWidgets.QItemDelegate):
def __init__(self, table, parent=None):
super(MyItemDelegate, self).__init__(parent)
self._table = table # type: MyTableView
def createEditor(self, parent, option, index):
if not index.isValid():
return super(MyItemDelegate, self).createEditor(parent, option, index)
edit = CellEdit(self._table, index, parent)
return edit
class MyTableModel(QtCore.QAbstractTableModel):
def __init__(self, rows, cols, parent=None):
super(MyTableModel, self).__init__(parent)
self._rows = rows
self._cols = cols
self._data = [
[index2ref(r, c) for c in range(cols)]
for r in range(rows)
]
def flags(self, index):
if not index.isValid():
return Qt.ItemIsEditable
return super(MyTableModel, self).flags(index) | Qt.ItemIsEditable
def rowCount(self, parent=None):
return self._rows
def columnCount(self, parent=None):
return self._cols
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
return None
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
if role != Qt.EditRole:
return False
self._data[index.row()][index.column()] = value
return True
class MyTableView(QtWidgets.QTableView):
def __init__(self, rows, cols, parent=None):
super(MyTableView, self).__init__(parent)
# This will store, in order:
# - The index of cell whose edit mode just lost focus
# - The text when that happened
# - The cursor position when that happened
self.lastCell = None # type: tuple
# This will be set as soon as the editing widget gets created, not when
# it loses focus
self.editingWidget = None # type: CellEdit
self._model = MyTableModel(rows, cols)
self.setModel(self._model)
self._delegate = MyItemDelegate(self)
self.setItemDelegate(self._delegate)
def mouseReleaseEvent(self, event):
index = self.selectionModel().selectedIndexes()
if len(index) == 0:
super(MyTableView, self).mousePressEvent(event)
self.lastCell = None
return
print('Cell clicked')
if self.lastCell is None:
print('No cell was being edited')
super(MyTableView, self).mousePressEvent(event)
return
if len(index) == 1 and self.lastCell[0].row() == index[0].row() and \
self.lastCell[0].column() == index[0].column():
# We are clicking the widget we are just editing, so no reference
# is added
print('We clicked the cell that was being edited')
self.lastCell = None
super(MyTableView, self).mousePressEvent(event)
return
if not self.lastCell[1].startswith('='):
# Excel wouldn't put references in cells which do not contain
# formulas, so neither do we
print('The cell that was being edited did not contain a formula')
super(MyTableView, self).mousePressEvent(event)
return
# Add the reference to this cell to the formula, and ignore the click
self.openAndAddReference(index)
event.accept()
def openAndAddReference(self, index):
# Store this locally because the focusOutEvent will reset it
lastCell = self.lastCell
self.edit(lastCell[0])
self.editingWidget.setSelection(*lastCell[2])
ref = index2ref(index[0].row(), index[0].column())
if len(index) > 1:
ref = '{}:{}'.format(
ref,
index2ref(index[-1].row(), index[-1].column())
)
self.editingWidget.addReference(ref)
self.lastCell = None
def focusOutEvent(self, e):
print('Focus lost for entire table')
self.lastCell = None
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
layout = QtWidgets.QVBoxLayout()
table = MyTableView(5, 4)
layout.addWidget(table)
# This push button is added just to have something to focus on other
# than the table
layout.addWidget(QtWidgets.QPushButton())
layout.addWidget(QtWidgets.QLineEdit())
self.setLayout(layout)
def main():
app = QtWidgets.QApplication(sys.argv)
widget = MainWindow()
widget.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()