5

I want to use signals for communicating between my view and my application controller. I have following approach but since I'm beginner in PyQt I don't know if that is the right one. Can anyone tell me If I am on the right path or are there better solutions?

EDIT: I have changed the example to a fully working example.

import sys
from PyQt4 import QtGui, QtCore

class View(QtGui.QMainWindow):

    sigFooChanged = QtCore.pyqtSignal()  
    sigBarChanged = QtCore.pyqtSignal()  

    def __init__(self):
        QtGui.QMainWindow.__init__(self)

        central_widget = QtGui.QWidget()
        central_layout = QtGui.QHBoxLayout()

        self.__cbFoo = QtGui.QComboBox()
        self.__cbBar = QtGui.QComboBox()
        self.__cbFoo.currentIndexChanged[str].connect(lambda x: self.sigFooChanged.emit())
        self.__cbBar.currentIndexChanged[str].connect(lambda x: self.sigBarChanged.emit())

        central_layout.addWidget(QtGui.QLabel("Foo:"))
        central_layout.addWidget(self.__cbFoo)
        central_layout.addWidget(QtGui.QLabel("Bar:"))
        central_layout.addWidget(self.__cbBar)

        central_widget.setLayout(central_layout)
        self.setCentralWidget(central_widget)

    def setFooModel(self, model):
        self.__cbFoo.setModel(model)

    def setBarModel(self, model):
        self.__cbBar.setModel(model)

class Controller:
    def __init__(self, view):
        self.__view = view
        # Connect all signals from view with according handlers
        self.__view.sigFooChanged.connect(self.handleFooChanged)
        self.__view.sigBarChanged.connect(self.handleBarChanged)

        self.__fooModel = QtGui.QStringListModel(["Foo1", "Foo2", "Foo3"])
        self.__barModel = QtGui.QStringListModel(["Bar1", "Bar2", "Bar3"])

        self.__view.setFooModel(self.__fooModel)
        self.__view.setBarModel(self.__barModel)

    def handleFooChanged(self):
        print("Foo Changed")

    def handleBarChanged(self):
        print("Bar Changed")

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    view = View()
    controller = Controller(view)
    view.show()
    sys.exit(app.exec_())
Razer
  • 7,843
  • 16
  • 55
  • 103

1 Answers1

3

Personally, I don't create a separate generic controller class like that. It could be my own preference, but I tend to consider the actual QWidget class my controller, and the view is usually the GUI-only definitions that I generate from QtDesigner (Ui_Dialog for example), or create manually. And I make all my connections in the relevant QWidget.

Now on to your code, I don't know if you are just considering this snippet a general pseudocode example of the direction you are taking...but it has errors... I would normally suggest posting working code so people don't get confused as to whether you are having errors because of it, or just asking if its generally a correct direction to laying out code.

You are forgetting to call __init__() on the QMainWindow superclass.

I'm not sure what controller.show() would do (fail as of right now) because I don't see an example of how you intend to forward that show() command to your main window object? Again I don't really see why its even necessary to have that separate class.

Here is how I would see a more realistic example, again considering the QWidget classes themselves to be the controllers:

View

## mainUI.py ##

from PyQt4 import QtCore, QtGui

class Ui_MyWidget(object):

    def setupUi(self, obj):
        obj.layout = QtGui.QVBoxLayout(obj)

        obj.cbFoo = QtGui.QComboBox()
        obj.cbBar = QtGui.QComboBox()

        obj.layout.addWidget(obj.cbFoo)
        obj.layout.addWidget(obj.cbBar)

Non-Gui Library Module (Controller)

## nonGuiModule.py ##

class LibModule(object):

    def handleBarChanged(self, *args):
        print("Bar Changed: %s" % args)

Controller (any entry point)

## main.py ##

import sys
from PyQt4 import QtCore, QtGui

from mainUI import Ui_MyWidget
from nonGuiModule import LibModule


class Main(QtGui.QMainWindow):

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

        self.resize(640,480)

        self._lib = LibModule()

        self.myWidget = MyWidget(self)
        self.setCentralWidget(self.myWidget)

        self.myWidget.sigFooChanged.connect(self.handleFooChanged)
        self.myWidget.sigBarChanged.connect(self._lib.handleBarChanged)


    def handleFooChanged(self, *args):
        print("Foo Changed: %s" % args)


class MyWidget(QtGui.QFrame, Ui_MyWidget):

    sigFooChanged = QtCore.pyqtSignal(str)  
    sigBarChanged = QtCore.pyqtSignal(str) 

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

        # this is where you set up from the view
        self.setupUi(self)

        self.cbFoo.addItems(['Foo1', 'Foo2'])
        self.cbBar.addItems(['Bar1', 'Bar2'])

        self.layout.addWidget(self.cbFoo)
        self.layout.addWidget(self.cbBar)

        # going to forward private signals to public signals
        self.cbFoo.currentIndexChanged[str].connect(self.sigFooChanged)
        self.cbBar.currentIndexChanged[str].connect(self.sigBarChanged)


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv[1:])
    view = Main()
    view.show()
    sys.exit(app.exec_())
jdi
  • 90,542
  • 19
  • 167
  • 203
  • Well, this approach is maybe more easy to code but I thought one goal of gui programming should be to separate `gui`and `business logic`? How is this done normally? – Razer Mar 04 '12 at 09:53
  • Its one thing if you have a non gui module that you want to keep separated from gui dependecy, and connect signals to its methods. But I have never seen code yet where someone is forwarding all events and signals to a generic controller class in Qt. You are still separating logic when you have a stricly UI class as a view (just layout and no logic), and then consider the QWidget that subclasses it to be the controller. Thats how I have seen it done to date. Look at tons of code examples. – jdi Mar 04 '12 at 16:42
  • I just updated my code example to show where I consider the view to be, and then controller. The view, to me, is the separate Ui module defining only a layout and no business logic. Then you have a class that inherits from it and adds in all the logic, tying it all together. – jdi Mar 04 '12 at 16:57
  • @Razer: I updated one more time to separate part of the logic into a non-gui type module. The main entry point script is still being used as the controller, yet some signals are connected to handlers on the controller, and some are being connected to the library class. To me, it makes sense to keep handlers on the QWidget when the handler is going to be doing a lot of interaction with the GUI and all of the members are right there on the class. Otherwise you are adding this extra layer of complexity for a generic controller to be the delegate for all of those gui members. – jdi Mar 04 '12 at 18:01
  • I would like to clarify in mainUI.py why it uses "self" in lines obj.layout.addWidget(self.cbFoo) obj.layout.addWidget(self.cbBar)? – Amanda.py Jul 20 '21 at 15:56
  • @Amanda.py that looked like a typo. I just fixed it. Thanks! – jdi Jul 21 '21 at 21:56