How to connect a signal from the controller in PyQ

2019-08-22 16:22发布

问题:

Why doesn't the following example work?

from PyQt4 import QtGui
import sys

class TestView(QtGui.QWidget):

    def __init__(self):
        super(TestView, self).__init__()
        self.initUI()

    def initUI(self):
        self.btn = QtGui.QPushButton('Button', self)
        self.btn.resize(self.btn.sizeHint())
        self.btn.move(50, 50)

class TestViewController():

    def __init__(self, view):
        view.btn.clicked.connect(self.buttonClicked)
        view.show()

    def buttonClicked(self):
        print 'clicked'

def main():
    app = QtGui.QApplication(sys.argv)
    view = TestView()
    TestViewController(view)
    app.exec_()

if __name__ == '__main__':
    main()

The example is supposed to represent an MVC structure (like the one in Figure 4 -- without the Model) where the controller (TestViewController) receives a reference to the view (TestView) and connects the clicked signal from the view's button view.btn to its function self.buttonClicked.

I'm sure the line view.btn.clicked.connect(self.buttonClicked) is executed but, apparently, it has no effect. Does anyone knows how to solve that?


Update (awful solution):

In the example, if I replace the line

view.btn.clicked.connect(self.buttonClicked)

with

view.clicked = self.clicked
view.btn.clicked.connect(view.clicked)

it works. I'm still not happy with that.

回答1:

The reason it is not working is because the controller class is being garbage collected before you can ever click anything for it.

When you set view.clicked = self.clicked, what you're actually doing is making one of the objects from the controller persist on the view object so it never gets cleaned up - which isn't really the solution.

If you store your controller to a variable, it will protect it from collection.

So if you change your code above to read:

ctrl = TestViewController(view)

You'll be all set.

That being said - what exactly you are trying to do here, I am not sure...it seems you're trying to setup an MVC system for Qt - but Qt already has a pretty good system for that using the Qt Designer to separate the interface components into UI (view/template) files from controller logic (QWidget subclasses). Again, I don't know what you are trying to do and this may be a dumb down version of it, but I'd recommend making it all one class like so:

from PyQt4 import QtGui
import sys

class TestView(QtGui.QWidget):

    def __init__(self):
        super(TestView, self).__init__()
        self.initUI()

    def initUI(self):
        self.btn = QtGui.QPushButton('Button', self)
        self.btn.resize(self.btn.sizeHint())
        self.btn.move(50, 50)
        self.btn.clicked.connect(self.buttonClicked)

    def buttonClicked(self):
        print 'clicked'

def main():
    app = QtGui.QApplication(sys.argv)
    view = TestView()
    view.show()
    app.exec_()

if __name__ == '__main__':
    main()

Edit: Clarifying the MVC of Qt

So this above example doesn't actually load the ui dynamically and create a controller/view separation. Its a bit hard to show on here. Best to work through some Qt/Designer based examples/tutorials - I have one here http://bitesofcode.blogspot.com/2011/10/introduction-to-designer.html but many can be found online.

The short answer is, your loadUi method can be replace with a PyQt4.uic dynamic load (and there are a number of different ways to set that up) such that your code ultimately reads something like this:

from PyQt4 import QtGui
import PyQt4.uic
import sys

class TestController(QtGui.QWidget):

    def __init__(self):
        super(TestController, self).__init__()

        # load view
        uifile = '/path/to/some/widget.ui'
        PyQt4.uic.loadUi(uifile, self)

        # create connections (assuming there is a widget called 'btn' that is loaded)
        self.btn.clicked.connect(self.buttonClicked)

    def buttonClicked(self):
        print 'clicked'

def main():
    app = QtGui.QApplication(sys.argv)
    view = TestController()
    view.show()
    app.exec_()

if __name__ == '__main__':
    main()

Edit 2: Storing UI references

If it is easier to visualize this concept, you Can also store a reference to the generated UI object:

from PyQt4 import QtGui
import PyQt4.uic
import sys

class TestController(QtGui.QWidget):

    def __init__(self):
        super(TestController, self).__init__()

        # load a view from an external template
        uifile = '/path/to/some/widget.ui'
        self.ui = PyQt4.uic.loadUi(uifile, self)

        # create connections (assuming there is a widget called 'btn' that is loaded)
        self.ui.btn.clicked.connect(self.buttonClicked)

    def buttonClicked(self):
        print 'clicked'

def main():
    app = QtGui.QApplication(sys.argv)
    view = TestController()
    view.show()
    app.exec_()

if __name__ == '__main__':
    main()