Search code examples
pythonpyqtpyqt5qdockwidget

Update a QDockWidget's subwidgets which already happens naturally upon hovering over with mouse


I have a QDockWidget in a QMainWindow, the QDockWidget contains a stack of QUndoViews containing commands that I want to change the text of in the QUndoView. You accomplish this by doing command.setText(). However, the QUndoView is only showing the updated text when I hover over it with the mouse. Not even will calling the below right after setText work:

    self.editor().undoView().setFocus()
    self.editor().undoView().update()
    self.editor().undoView().hide()
    self.editor().undoView().show()

Here is the minimal code to demonstrate the problem:

from PyQt5.QtWidgets import (QMainWindow, QDockWidget, QPushButton, QLabel, \
                             QStackedWidget, QUndoStack, QUndoCommand, QApplication, \
                             QUndoView)
import sys
from PyQt5.QtCore import QObject, pyqtSignal, Qt

class Command(QUndoCommand):
    def __init__(self):
        super().__init__()

    def undo(self):
        print("Command Undone")

    def redo(self):
        print("Command Redone")


class CommandTimeline(QDockWidget):
    def __init__(self):
        super().__init__()
        self.stackedWidget = QStackedWidget()
        self.setWidget(self.stackedWidget)

    def addUndoView(self, stack):
        view = QUndoView(stack)
        self.stackedWidget.addWidget(view)
        return view


class Obj(QObject):
    nameChanged = pyqtSignal(str)

    def __init__(self, name):
        super().__init__()
        self._name = name

    def setName(self, name):
        self._name = name
        self.nameChanged.emit(name)

    def __str__(self):
        return self._name


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.undoStack = QUndoStack()
        self.commandTimeline = CommandTimeline()
        self.commandTimeline.addUndoView(self.undoStack)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.commandTimeline)
        button = QPushButton("Click")
        self.setCentralWidget(button)
        button.clicked.connect(self.changeObjName)
        self.obj = Obj("A")
        self.addCommand()

    def addCommand(self):
        def getText(obj):
            return "The command text: " + str(obj)
        command = Command()
        command.setText(getText(self.obj))
        self.obj.nameChanged.connect(lambda name: command.setText(getText(self.obj)))
        self.undoStack.push(command)

    def changeObjName(self):
        self.obj.setName("B")


if __name__ == "__main__":
    app = QApplication([])

    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

Run this code and notice that after clicking the button, the text in the undoview doesn't change, but after hovering over the dockwidget, the text updates immediately.

How can I trigger the update upon command.setText()?


Solution

  • QUndoCommand does not notify if the text is changed, it is designed to have a static text. So we must do that task, in this case use setFocus(), you signal that it does not work, but for me it works so you probably have not implemented it correctly or your MCVE is not verifiable.

    import sys
    
    from functools import partial
    
    from PyQt5.QtWidgets import (QMainWindow, QDockWidget, QPushButton, QLabel, \
                                 QStackedWidget, QUndoStack, QUndoCommand, QApplication, \
                                 QUndoView)
    from PyQt5.QtCore import QObject, pyqtSignal, Qt
    
    class Command(QUndoCommand):
        def undo(self):
            print("Command Undone")
    
        def redo(self):
            print("Command Redone")
    
    
    class CommandTimeline(QDockWidget):
        def __init__(self):
            super().__init__()
            self.stackedWidget = QStackedWidget()
            self.setWidget(self.stackedWidget)
    
        def addUndoView(self, stack):
            view = QUndoView(stack)
            self.stackedWidget.addWidget(view)
            return view
    
    
    class Obj(QObject):
        nameChanged = pyqtSignal(str)
    
        def __init__(self, name):
            super().__init__()
            self._name = name
    
        def setName(self, name):
            self._name = name
            self.nameChanged.emit(name)
    
        def __str__(self):
            return self._name
    
    
    class MainWindow(QMainWindow):
        def __init__(self):
            super().__init__()
            self.undoStack = QUndoStack()
            self.commandTimeline = CommandTimeline()
            self.view = self.commandTimeline.addUndoView(self.undoStack)
            self.addDockWidget(Qt.LeftDockWidgetArea, self.commandTimeline)
            button = QPushButton("Click")
            self.setCentralWidget(button)
            button.clicked.connect(self.changeObjName)
            self.obj = Obj("A")
            self.addCommand()
    
    
        def addCommand(self):
            command = Command()
            command.setText("The command text: {}".format(self.obj))
            self.obj.nameChanged.connect(partial(self.onNameChanged, command))
            self.undoStack.push(command)
    
        def changeObjName(self):
            self.obj.setName("B")
    
        def onNameChanged(self, command, name):
            fw = QApplication.focusWidget()
            command.setText("The command text: {}".format(name))
            self.view.setFocus()
            fw.setFocus()
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        window = MainWindow()
        window.show()
        sys.exit(app.exec_())