I am not finding any documentation showing which widgets QDataWidgetMapper actually works for and have not found any implementation of mapping with a QTableWidget.
It definitely works for QLineEdit's and QComboBoxes, which are input widgets, but is it possible to map to a QTableWidget?
Goal is to use QUndoStack to undo/redo text change in each widget when added to the QUndostack. I want to be able to undo/redo text changes for the items in the QTableWidget as well as the QLineEdits and QComboBoxes.
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class CommandTextEdit(QtWidgets.QUndoCommand):
def __init__(self, window, index, oldText, newText, description):
super(CommandTextEdit,self).__init__()
self.index = index
self.window = window
self.oldText = oldText
self.newText = newText
def redo(self):
self.index.model().itemDataChanged.disconnect(self.window.itemDataChangedSlot)
self.index.model().setData(self.index, self.newText, QtCore.Qt.EditRole)
self.index.model().itemDataChanged.connect(self.window.itemDataChangedSlot)
def undo(self):
self.index.model().itemDataChanged.disconnect(self.window.itemDataChangedSlot)
self.index.model().setData(self.index, self.oldText, QtCore.Qt.EditRole)
self.index.model().itemDataChanged.connect(self.window.itemDataChangedSlot)
class Model(QtCore.QAbstractListModel):
itemDataChanged = QtCore.pyqtSignal(object,object, object, object)
def __init__(self, text = [], parent = None):
super(Model,self).__init__(parent)
self._text = text
def rowCount(self,parent=QtCore.QModelIndex()):
return len(self._text)
def data(self,index,role):
row = index.row()
if role == QtCore.Qt.EditRole:
return self._text[row]
if role == QtCore.Qt.DisplayRole:
value = self._text[row]
return self._text[row]
def model(self):
return self
def flags(self, index):
return QtCore.Qt.ItemIsEditable |QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def setData(self, index, value, role = QtCore.Qt.UserRole + 1):
if index.isValid():
if role == QtCore.Qt.EditRole:
oldValue = self.data(index,role)
self._text[index.row()] = value
self.dataChanged.emit(index, index)
if oldValue != value:
self.itemDataChanged.emit(index, oldValue, value, role)
return True
return False
class Window(QtWidgets.QMainWindow):
def __init__(self, parent = None):
super(Window,self).__init__(parent)
self.setCentralWidget(QtWidgets.QWidget(self))
self.setWindowTitle('Widget Mapping GUI')
mainlayout = QtWidgets.QVBoxLayout()
#Information for widgets
items = ["","H","N","M"]
#LineEdit1
self.labelLineEdit1 = QtWidgets.QLabel()
self.labelLineEdit1.setText('LineEdit1')
self.LineEdit1 = QtWidgets.QLineEdit()
firstBox = QtWidgets.QHBoxLayout()
firstBox.addWidget(self.labelLineEdit1)
firstBox.addWidget(self.LineEdit1)
#LineEdit2
self.labelLineEdit2 = QtWidgets.QLabel()
self.labelLineEdit2.setText('LineEdit2')
self.LineEdit2 = QtWidgets.QLineEdit()
secondBox = QtWidgets.QHBoxLayout()
secondBox.addWidget(self.labelLineEdit2)
secondBox.addWidget(self.LineEdit2)
#ComboBox1
self.labelComboBox1 = QtWidgets.QLabel()
self.labelComboBox1.setText('ComboBox1')
self.ComboBox1 = QtWidgets.QComboBox()
self.ComboBox1.addItems(items)
thirdBox = QtWidgets.QHBoxLayout()
thirdBox.addWidget(self.labelComboBox1)
thirdBox.addWidget(self.ComboBox1)
#ComboBox2
self.labelComboBox2 = QtWidgets.QLabel()
self.labelComboBox2.setText('ComboBox2')
self.ComboBox2 = QtWidgets.QComboBox()
self.ComboBox2.addItems(items)
fourthBox = QtWidgets.QHBoxLayout()
fourthBox.addWidget(self.labelComboBox2)
fourthBox.addWidget(self.ComboBox2)
#TableWidget
self.TableWidget = QtWidgets.QTableWidget(5,1)
#Set header labels
self.TableWidget.setHorizontalHeaderLabels(["Dimensions"])
self.TableWidget.setVerticalHeaderLabels(["A","B","C","D","E"])
self.TableWidget.setItem(0,1,QtWidgets.QTableWidgetItem('1'))
fifthBox = QtWidgets.QVBoxLayout()
fifthBox.addWidget(self.TableWidget)
#Add Layouts
mainlayout.addLayout(firstBox)
mainlayout.addLayout(secondBox)
mainlayout.addLayout(thirdBox)
mainlayout.addLayout(fourthBox)
mainlayout.addLayout(fifthBox)
self.centralWidget().setLayout(mainlayout)
#Model
self.mapper = None
self.model = Model(['','','','',''])
self.setModel(self.model)
#QUndoStack
self.undoStack = QtWidgets.QUndoStack()
self.stackView = QtWidgets.QUndoView(self.undoStack)
self.stackView.setWindowTitle('StackView')
self.stackView.show()
#Run init methods
self.createActions()
self.makeConnections()
self.listViewMethod()
def createActions(self):
self.UndoAct = QtWidgets.QAction("Undo", self)
self.UndoAct.setShortcut(QtGui.QKeySequence.Undo)
self.UndoAct.triggered.connect(self.undoStack.undo)
self.RedoAct = QtWidgets.QAction("Redo", self)
self.RedoAct.setShortcut(QtGui.QKeySequence.Redo)
self.RedoAct.triggered.connect(self.undoStack.redo)
self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(self.UndoAct)
self.toolbar.addAction(self.RedoAct)
def listViewMethod(self):
self.listView = QtWidgets.QListView()
self.listView.setModel(self.model)
self.listView.setWindowTitle('ListView')
self.listView.show()
def makeConnections(self):
self.model.itemDataChanged.connect(self.itemDataChangedSlot)
def setModel(self, model):
self.mapper = QtWidgets.QDataWidgetMapper(self)
self.mapper.setOrientation(QtCore.Qt.Vertical)
self.mapper.setModel(self.model)
self.mapper.addMapping(self.LineEdit1, 0)
self.mapper.addMapping(self.LineEdit2, 1)
self.mapper.addMapping(self.ComboBox1, 2)
self.mapper.addMapping(self.ComboBox2, 3)
self.mapper.addMapping(self.TableWidget,4)
self.mapper.toFirst()
def itemDataChangedSlot(self, index, oldValue, value, role):
if role == QtCore.Qt.EditRole:
command = CommandTextEdit(self, index, oldValue, value, "Text changed from '{0}' to '{1}'".format(oldValue, value))
self.undoStack.push(command)
def main():
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
To map the items of a QTableWidget
, I used the setCellWidget
method to map each cell to the model using a custom QLineEdit class.
Updated Code:
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
#implemented stylesheets for QLineEdit to imitate a QTableWidgetItem
class CustomLineEdit(QtWidgets.QLineEdit):
def __init__(self):
super(CustomLineEdit,self).__init__()
self.setReadOnly(True)
self.setFrame(False)
self.setStyleSheet("QLineEdit { border: none; } \n" \
"QLineEdit::focus {background-color: #3daee9;} \n"\
"QLineEdit::focus::pressed {background-color: none;} \n"\
"QLineEdit::hover {background-color: #3daee9;}"
)
def mouseDoubleClickEvent(self,event):
self.setReadOnly(False)
self.setStyleSheet("QLineEdit { border: none; \nbackground-color: none;} \n" \
)
def focusOutEvent(self,event):
self.setReadOnly(True)
self.setStyleSheet("QLineEdit { border: none; } \n" \
"QLineEdit::focus {background-color: #3daee9;} \n"\
"QLineEdit::focus::pressed {background-color: none;} \n"\
"QLineEdit::hover {background-color: #3daee9;}"
)
class CommandTextEdit(QtWidgets.QUndoCommand):
def __init__(self, window, index, oldText, newText, description):
super(CommandTextEdit,self).__init__()
self.index = index
self.window = window
self.oldText = oldText
self.newText = newText
def redo(self):
self.index.model().itemDataChanged.disconnect(self.window.itemDataChangedSlot)
self.index.model().setData(self.index, self.newText, QtCore.Qt.EditRole)
self.index.model().itemDataChanged.connect(self.window.itemDataChangedSlot)
def undo(self):
self.index.model().itemDataChanged.disconnect(self.window.itemDataChangedSlot)
self.index.model().setData(self.index, self.oldText, QtCore.Qt.EditRole)
self.index.model().itemDataChanged.connect(self.window.itemDataChangedSlot)
class Model(QtCore.QAbstractListModel):
itemDataChanged = QtCore.pyqtSignal(object,object, object, object)
def __init__(self, text = [], parent = None):
super(Model,self).__init__(parent)
self._text = text
def rowCount(self,parent=QtCore.QModelIndex()):
return len(self._text)
def data(self,index,role):
row = index.row()
if role == QtCore.Qt.EditRole:
return self._text[row]
if role == QtCore.Qt.DisplayRole:
value = self._text[row]
return self._text[row]
def model(self):
return self
def flags(self, index):
return QtCore.Qt.ItemIsEditable |QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def setData(self, index, value, role = QtCore.Qt.UserRole + 1):
if index.isValid():
if role == QtCore.Qt.EditRole:
oldValue = self.data(index,role)
self._text[index.row()] = value
self.dataChanged.emit(index, index)
if oldValue != value:
self.itemDataChanged.emit(index, oldValue, value, role)
return True
return False
class Window(QtWidgets.QMainWindow):
def __init__(self, parent = None):
super(Window,self).__init__(parent)
self.setCentralWidget(QtWidgets.QWidget(self))
self.setWindowTitle('Widget Mapping GUI')
mainlayout = QtWidgets.QVBoxLayout()
#Information for widgets
items = ["","H","N","M"]
#LineEdit1
self.labelLineEdit1 = QtWidgets.QLabel()
self.labelLineEdit1.setText('LineEdit1')
self.LineEdit1 = QtWidgets.QLineEdit()
firstBox = QtWidgets.QHBoxLayout()
firstBox.addWidget(self.labelLineEdit1)
firstBox.addWidget(self.LineEdit1)
#LineEdit2
self.labelLineEdit2 = QtWidgets.QLabel()
self.labelLineEdit2.setText('LineEdit2')
self.LineEdit2 = QtWidgets.QLineEdit()
secondBox = QtWidgets.QHBoxLayout()
secondBox.addWidget(self.labelLineEdit2)
secondBox.addWidget(self.LineEdit2)
#ComboBox1
self.labelComboBox1 = QtWidgets.QLabel()
self.labelComboBox1.setText('ComboBox1')
self.ComboBox1 = QtWidgets.QComboBox()
self.ComboBox1.addItems(items)
thirdBox = QtWidgets.QHBoxLayout()
thirdBox.addWidget(self.labelComboBox1)
thirdBox.addWidget(self.ComboBox1)
#ComboBox2
self.labelComboBox2 = QtWidgets.QLabel()
self.labelComboBox2.setText('ComboBox2')
self.ComboBox2 = QtWidgets.QComboBox()
self.ComboBox2.addItems(items)
fourthBox = QtWidgets.QHBoxLayout()
fourthBox.addWidget(self.labelComboBox2)
fourthBox.addWidget(self.ComboBox2)
#TableWidget
self.TableWidget = QtWidgets.QTableWidget(5,1)
#Set header labels
self.TableWidget.setHorizontalHeaderLabels(["Dimensions"])
self.TableWidget.setVerticalHeaderLabels(["A","B","C","D","E"])
self.TableWidget.setCellWidget(0,0,CustomLineEdit())
self.TableWidget.setCellWidget(1,0,CustomLineEdit())
self.TableWidget.setCellWidget(2,0,CustomLineEdit())
self.TableWidget.setCellWidget(3,0,CustomLineEdit())
self.TableWidget.setCellWidget(4,0,CustomLineEdit())
self.TableWidget.setItem(1,0,QtWidgets.QTableWidgetItem('1'))
fifthBox = QtWidgets.QVBoxLayout()
fifthBox.addWidget(self.TableWidget)
#Add Layouts
mainlayout.addLayout(firstBox)
mainlayout.addLayout(secondBox)
mainlayout.addLayout(thirdBox)
mainlayout.addLayout(fourthBox)
mainlayout.addLayout(fifthBox)
self.centralWidget().setLayout(mainlayout)
#Model
self.mapper = None
self.model = Model(['' for i in range(9)])
self.setModel(self.model)
#QUndoStack
self.undoStack = QtWidgets.QUndoStack()
self.stackView = QtWidgets.QUndoView(self.undoStack)
self.stackView.setWindowTitle('StackView')
self.stackView.show()
#Run init methods
self.createActions()
self.makeConnections()
self.listViewMethod()
def createActions(self):
self.UndoAct = QtWidgets.QAction("Undo", self)
self.UndoAct.setShortcut(QtGui.QKeySequence.Undo)
self.UndoAct.triggered.connect(self.undoStack.undo)
self.RedoAct = QtWidgets.QAction("Redo", self)
self.RedoAct.setShortcut(QtGui.QKeySequence.Redo)
self.RedoAct.triggered.connect(self.undoStack.redo)
self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(self.UndoAct)
self.toolbar.addAction(self.RedoAct)
def listViewMethod(self):
self.listView = QtWidgets.QListView()
self.listView.setModel(self.model)
self.listView.setWindowTitle('ListView')
self.listView.show()
def makeConnections(self):
self.model.itemDataChanged.connect(self.itemDataChangedSlot)
def setModel(self, model):
self.mapper = QtWidgets.QDataWidgetMapper(self)
self.mapper.setOrientation(QtCore.Qt.Vertical)
self.mapper.setModel(self.model)
self.mapper.addMapping(self.LineEdit1, 0)
self.mapper.addMapping(self.LineEdit2, 1)
self.mapper.addMapping(self.ComboBox1, 2)
self.mapper.addMapping(self.ComboBox2, 3)
self.mapper.addMapping(self.TableWidget.cellWidget(0,0), 4)
self.mapper.addMapping(self.TableWidget.cellWidget(1,0), 5)
self.mapper.addMapping(self.TableWidget.cellWidget(2,0), 6)
self.mapper.addMapping(self.TableWidget.cellWidget(3,0), 7)
self.mapper.addMapping(self.TableWidget.cellWidget(4,0), 8)
self.mapper.toFirst()
def itemDataChangedSlot(self, index, oldValue, value, role):
if role == QtCore.Qt.EditRole:
command = CommandTextEdit(self, index, oldValue, value, "Text changed from '{0}' to '{1}'".format(oldValue, value))
self.undoStack.push(command)
def main():
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()