0

When QTabWidget is used, it is straightforward to call a function (e.g. on_tab_changed) when any of the tabs is clicked on with something like self.currentChanged.connect(self.on_tab_changed). However, I cannot figure out how to do similarly with QDockWidget. I was able to come up with a simplified MRE that reads:

from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys

class Window(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("OOP")
        self.uinterface()
        self.show()

    def uinterface(self):

        self.dock = QDockWidget("OOP example", self)
        self.listWidget = QListWidget()
        self.listWidget.addItem("One")
        self.listWidget.addItem("Two")
        self.listWidget.addItem("Three")
        self.dock.setWidget(self.listWidget)
        self.dock.currentChanged.connect(self.on_tab_changed) # This is the line that I'd like to modify

    def on_tab_changed(self, index: int) -> None:
        print ("DO SOMETHING")

App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())

This doesn't work because currentChanged is not a Signal for QDockWidget (which is for QTabWidget) although both derive from QWidget. I want to come up with a relatively simple way to accomplish this task as converting QDockWidget into QTabWidget is relatively straightforward for the MRE but a lot more complex and time consuming for the whole app.

P.S. Based on some of the comments, maybe the code could have read

    ...
    self.dock.setWidget(self.listWidget)
    self.dock.Clicked.connect(self.on_dockwidget_clicked) 

def on_dockwidget_clicked(self) -> None:
    print ("DO SOMETHING")

albeit this still would not work and it'd be necessary to use the accepted solution.

  • 1
    I don't understand your question. A QDockWidget only contains *one* widget, so there's no "change" to be notified of. It's also normally added to the main window through [`addDockWidget()`](https://doc.qt.io/qt-5/qmainwindow.html#addDockWidget), not just by creating it as a child of the window. Also, the fact that QDockWidget and QTabWidget both inherit from QWidget is irrelevant: signals are not generic for any class, each inherits the signals from its base class and eventually add its own based on its needs. QWidget doesn't have a `currentChanged` signal, because it wouldn't make any sense. – musicamante Sep 14 '22 at 22:45
  • But each tab is clickable so that is when I'd like to call a function (it doesn't have to be a change per se, just a click or something else). I agree with your comment about `addDockWidget` and it is actually used in the whole app. However, I simplified as much as I could for the MRE. – afernandezody Sep 14 '22 at 22:55
  • 1
    *Reproducible* also refers to the fact that the code should reproduce the **concept** of the post. Your code has only one dock widget: even assuming that a `currentChanged` signal existed, it wouldn't be emitted, exactly as it wouldn't for a QTabWidget with a single tab. That said, dock widgets have no concept of "change", especially when not tabbed (which is the default). So, are you using more dock widgets? Are you *tabifying* them? If so: 1. you never mentioned that in the question; 2. [`QMainWindow.tabifiedDockWidgetActivated`](//doc.qt.io/qt-5/qmainwindow.html#tabifiedDockWidgetActivated) – musicamante Sep 15 '22 at 00:59
  • The app itself is a plugin (for QGIS) so everything cascades down into a long list of moving pieces with many lines of code. I apologize if my MRE is not descriptive enough. There's a single dock widget that has a number of tabs. I will try to tabify the widget (if it's not already in that mode) and use 'tabifiedDockWidgetActivated`. – afernandezody Sep 15 '22 at 02:05
  • 1
    Sorry but your comment is still confusing. As said above, a dock widget contains only *one* widget. If that widget **contains** a QTabWidget *in itself*, that's a **completely** different story. If, otherwise, there are *multiple* dock widgets that are "tabified", then it means that there are **more than one** dock widgets. This, unless a very special type of QDockWidget is created which implements a QTabBar for its title "widget" through [`setTitleBarWidget()`](https://doc.qt.io/qt-5/qdockwidget.html#setTitleBarWidget), but such an approach would be quite odd and problematic. – musicamante Sep 15 '22 at 03:11
  • That is the case: There's only one dock widget containing a QTabWidget. I don't have the skills or experience to implement the other approach, so we better leave aside for the time being. – afernandezody Sep 15 '22 at 13:06
  • With that last comment your question is even more confusing. In your code there is ***no*** QTabWidget, and even if there was, its tab change would have absolutely **nothing** to do with the dock widget, nor its "activation" (which doesn't exist for single dock widgets) has anything to do with anything "changing". Sincerely, it's really unclear to understand what you want if you confuse widgets and purposes. You want to know when the dock is "clicked"? Then ask *that*, don't add distracting details that are completely irrelevant to the question and also don't reflect the code you share. – musicamante Sep 15 '22 at 13:43

1 Answers1

1

You could overwrite the the event method a QDockWidget subclass and listen for mousePressEvents and emit a custom signal that sends some kind of identifier, in case you have multiple dockWidgets and you need to know which one sent the signal. Then you can set your mainWindow to listen for the custom signal and connect you method slot to it.

For Example:

from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys

class DockWidget(QDockWidget):

    hasFocus = pyqtSignal([QDockWidget])

    def __init__(self, text, parent=None):
        super().__init__(text, parent=parent)
        self.setObjectName(text)

    def event(self, event):
        if event.type() == QEvent.MouseButtonPress and event.button() == 1:
            self.hasFocus.emit(self)
        return super().event(event)

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("OOP")
        self.uinterface()
        self.show()

    def uinterface(self):
        self.dock = DockWidget("OOP example", self)
        self.listWidget = QListWidget()
        self.listWidget.addItem("One")
        self.listWidget.addItem("Two")
        self.listWidget.addItem("Three")
        self.dock.setWidget(self.listWidget)
        self.dock.hasFocus.connect(self.on_dock_focus)

    def on_dock_focus(self, dockwidget):
        print(f"DO SOMETHING WITH {dockwidget.objectName()}")

App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())
Alexander
  • 16,091
  • 5
  • 13
  • 29
  • 1
    Note: object names are not and cannot be unique by their nature. Assuming that a reference to the "source" widget is required, then it's better to either use `self.sender()` (remembering that the sender is the last Qt object that emitted the signal connected to the receiver, and that signals might be queued) or just emit the instance instead. – musicamante Sep 15 '22 at 04:04
  • @musicamante Good tip. Why can't object names be unique? – Alexander Sep 15 '22 at 04:12
  • 1
    Conceptually, it's almost like id's in HTML: you *should* always create unique id's, but you cannot have full control over the content, especially when it's dynamic (consider the case of iframes). An object can have children, each one with its "unique" name: what happens when you have a parent that adds *two* or more instances of that object? While you could change the name of each instance, you cannot know about every name that *that* object creates for each one of its children (especially if created dynamically). The object name interface is useful, but must be used with awareness. – musicamante Sep 15 '22 at 04:34
  • 1
    For instance, Qt creates object names for some complex widgets in order to access them more easily: non-native QFileDialogs have names for each of their dynamic widgets (text fields, directory/filter combos, file views, etc), QCalendarWidget uses names to identify elements in its toolbar. QScrollArea has names for the internal widget container of its scroll bars, and the viewport itself: assuming you added a number of QScrollAreas to a widget, doing `widget.findChildren(QWidget, 'qt_scrollarea_viewport')` will return as many viewport widgets as the scroll area count. – musicamante Sep 15 '22 at 04:43
  • @Alexander. I accepted your solution earlier as it worked. Furthermore, I was able to implement it in the larger code, which was the final objective. The only minor comment (which doesn't affect my intents) is that (upon clicking) it prints the message twice rather than once. – afernandezody Sep 15 '22 at 16:00
  • 1
    @afernandezody I fixed it. It was firing the event once for the button press and again for the button release. Now it should only fire when clicked. – Alexander Sep 15 '22 at 18:56