1

I've got a simple undo/redo framework done for a mainwindow, but the problem is that I want to be able to undo the last action, and not have to click undo till item changes.

What I have right now is pretty clunky, in which the QComboBoxes don't undo/redo correctly after you go to the top of the QUndoStack and then try to go back down with the redo button.

I am pretty sure its because I am not using the editingFinished and currentIndexChanged signals correctly, is there a more sophisticated way to do this?

You can connect the QLineEdit/QComboBox to a model/view and check for changes, what I would probably use is a QTableView so that I can set the first column as the old text and second as the new text and each row would be the QLineEdits. Can someone provide a small implementation of using a QTableView as a model for QUndoStack, as i'm not sure where to start?

from PyQt5 import QtCore,QtWidgets,QtGui
from PyQt5 import uic
import sys


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(366, 440)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.lineEdit1 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit1.setGeometry(QtCore.QRect(130, 150, 113, 20))
        self.lineEdit1.setObjectName("lineEdit1")
        self.lineEdit2 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit2.setGeometry(QtCore.QRect(130, 180, 113, 20))
        self.lineEdit2.setObjectName("lineEdit2")
        self.lineEdit3 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit3.setGeometry(QtCore.QRect(130, 210, 113, 20))
        self.lineEdit3.setObjectName("lineEdit3")
        self.comboBox = QtWidgets.QComboBox(self.centralwidget)
        self.comboBox.setGeometry(QtCore.QRect(130, 270, 69, 22))
        self.comboBox.setObjectName("comboBox")
        self.comboBox2 = QtWidgets.QComboBox(self.centralwidget)
        self.comboBox2.setGeometry(QtCore.QRect(130, 310, 69, 22))
        self.comboBox2.setObjectName("comboBox2")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 366, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))

class StoreCommand(QtWidgets.QUndoCommand):

    def __init__(self, field, text):
        QtWidgets.QUndoCommand.__init__(self)
        # Record the field that has changed.
        self.field = field
        # Record the text at the time the command was created.
        self.text = text

    def undo(self):
        # Remove the text from the file and set it in the field.
        self.field.setText(self.text)

    def redo(self):
        # Store the text in the file and set it in the field.
        self.field.setText(self.text)

class StoreComboCommand(QtWidgets.QUndoCommand):

    def __init__(self, field, text):
        QtWidgets.QUndoCommand.__init__(self)
        # Record the field that has changed.
        self.field = field
        # Record the text at the time the command was created.
        self.text = text

    def undo(self):
        # Remove the text from the file and set it in the field.
        index = self.field.findText(self.text)
        self.field.setCurrentIndex(index)

    def redo(self):
        # Store the text in the file and set it in the field.
        index = self.field.findText(self.text)
        self.field.setCurrentIndex(index)


class Example(QtWidgets.QMainWindow,Ui_MainWindow):
    def __init__(self,parent = None):
        super().__init__()
        self.setupUi(self)
        self.comboBox.addItem('')
        self.comboBox.addItem('H')
        self.comboBox.addItem('N')
        self.comboBox2.addItem('')
        self.comboBox2.addItem('H')
        self.comboBox2.addItem('N')
        #
        self.undoStack = QtWidgets.QUndoStack()
        self.stackview = QtWidgets.QUndoView(self.undoStack)
        #
        self.lineEdit1.editingFinished.connect(self.storeFieldText)
        self.lineEdit2.editingFinished.connect(self.storeFieldText)
        self.lineEdit3.editingFinished.connect(self.storeFieldText)
        self.comboBox.currentIndexChanged.connect(self.storeFieldComboText)
        self.comboBox2.currentIndexChanged.connect(self.storeFieldComboText)
        #Item actions
        self.undoAction = self.undoStack.createUndoAction(self, self.tr("&Undo"))
        self.undoAction.setShortcuts(QtGui.QKeySequence.Undo)
        self.redoAction = self.undoStack.createRedoAction(self, self.tr("&Redo"))
        self.redoAction.setShortcuts(QtGui.QKeySequence.Redo)

        self.itemToolbar = self.addToolBar("Item actions")
        self.itemToolbar.addAction(self.undoAction)
        self.itemToolbar.addAction(self.redoAction)

    def storeFieldText(self):
        command = StoreCommand(self.sender(),self.sender().text())
        self.undoStack.push(command)

    def storeFieldComboText(self):
        command = StoreComboCommand(self.sender(),self.sender().currentText())
        self.undoStack.push(command)

def main():
    app = QtWidgets.QApplication(sys.argv)
    gui = Example()
    gui.show()
    gui.stackview.show()
    sys.exit(app.exec_())    

if __name__ == "__main__":
    main()
Drees
  • 688
  • 1
  • 6
  • 21
  • Your question is not clear, maybe an MWE improves your explanation – eyllanesc Jul 13 '19 at 22:05
  • @eyllanesc I updated my question but I’ll have to do an example in a day or so – Drees Jul 14 '19 at 01:44
  • You say: *on a widget and it will be an action done by user*, not every widget has that behavior, so it would be great to what widget do you mean? – eyllanesc Jul 14 '19 at 01:49
  • What do you mean by *setting to QComboBox by user*? – eyllanesc Jul 14 '19 at 02:51
  • 1) if there is "n" QLineEdits, only the Ctrl + Z will work, it will only work in the QLineEdit that has the focus, you could explain in greater detail what you want 2) The Ctrl + Z does not work in QComboBox 3) an MWE would help a lot – eyllanesc Jul 14 '19 at 02:58
  • @eyllanesc Okay, I want it to undo/redo setting the text in the order the QLineEdit was changed and same for the QComboBoxes, similar to the way the undo/redo options in excel works – Drees Jul 14 '19 at 03:00
  • @eyllanesc I updated my question with some information I found on another post that was similar – Drees Jul 14 '19 at 04:51
  • 1
    With QComboBox it is simple since it has not implemented the functionality of undo-redo and therefore it is our job to implement it, but in the case of QLineEdit you already have one implemented that I am trying to override to implement my own undo-redo logic or trace it – eyllanesc Jul 14 '19 at 04:55
  • 1
    @Drees Your question seems to be an exact duplicate of the one you linked to. Your widgets are elements of a form, which should be linked to an item-model using e.g. [QDataWidgetMapper](https://doc.qt.io/qt-5/qdatawidgetmapper.html). Changes to the model can then be recorded/reversed using an undo-stack (which will then automatically update any associated widgets). See [here](https://stackoverflow.com/q/29527610/984421) and [here](https://stackoverflow.com/q/29640408/984421) for how to use an undo-stack with a model. – ekhumoro Jul 14 '19 at 10:40
  • @ekhumoro I know it’s kind of a duplicate but it doesn’t give a concrete example of how to implement it, they are just suggestions – Drees Jul 14 '19 at 18:30
  • @Drees No, all the answers there give concrete suggestions. And I also provided links to some (partial) implementations. The rest is up to you. – ekhumoro Jul 14 '19 at 23:15
  • @ekhumoro Yes, as you stated they are suggestions, the examples you provided helped me more than the post did, as someone with not much programming knowledge it’s more helpful with a written example and not just suggestions. Thanks for the info. – Drees Jul 15 '19 at 03:42

0 Answers0