使用信号在类之间进行通信

5
我希望使用“信号(signals)”在视图和应用程序控制器之间进行通信。我有以下的方法,但由于我是PyQt的初学者,所以不知道这是否正确。有人可以告诉我是否正确或是否有更好的解决方案吗?
编辑:我已将示例更改为完全可工作的示例。
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_())
1个回答

3
个人而言,我不会创建一个单独的通用控制器类。这可能是我的个人偏好,但我倾向于将实际的QWidget类视为我的控制器,而视图通常是我从QtDesigner生成的仅GUI定义(例如Ui_Dialog),或手动创建的。我在相关的QWidget中进行所有连接。
现在说说你的代码,我不知道你是否只是将这个片段视为你正在采取方向的一般伪代码示例......但它有错误......我通常建议发布工作代码,这样人们就不会因为它而感到困惑,或者只是询问它是否是布置代码的正确方向。
您忘记在QMainWindow超类上调用__init __()
我不确定controller.show()会做什么(目前失败),因为我没有看到您打算如何将该show()命令转发到您的主窗口对象的示例? 同样,我真的看不出为什么需要单独的类。
以下是我认为更现实的示例,再次考虑QWidget类本身作为控制器:
## 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)

非Gui库模块(控制器)

## nonGuiModule.py ##

class LibModule(object):

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

控制器(任何入口点)

## 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_())

这种方法可能更容易编码,但我认为GUI编程的一个目标应该是将“GUI”和“业务逻辑”分离开来,通常是如何做到这一点的呢? - Razer
如果您有一个非GUI模块,想要将其与GUI依赖项分开,并连接信号到其方法,那么这是一件好事。但我从未见过有人将所有事件和信号转发到Qt中的通用控制器类的代码。当您将严格的UI类作为视图(仅布局而没有逻辑)并将其子类化为控制器时,仍然会分离逻辑。这就是我迄今为止看到的做法。看看大量的代码示例。 - jdi
我刚刚更新了我的代码示例,展示了我认为视图和控制器的位置。对我来说,视图是一个单独的UI模块,仅定义布局而没有业务逻辑。然后你有一个继承自它并添加所有逻辑的类,将它们全部联系在一起。 - jdi
@Razer:我又更新了一次,将部分逻辑分离成非GUI类型的模块。主入口脚本仍然被用作控制器,但是一些信号被连接到控制器上的处理程序,而一些信号则被连接到库类上。对我来说,如果处理程序将与GUI进行大量交互并且所有成员都在该类上,那么将处理程序保留在QWidget上是有意义的。否则,您将为通用控制器添加这个额外的复杂度,以成为所有这些GUI成员的委托。 - jdi
我想在mainUI.py中澄清为什么在以下代码行中使用了"self": obj.layout.addWidget(self.cbFoo) 和 obj.layout.addWidget(self.cbBar)? - Amanda.py
@Amanda.py 看起来像是打错了,我刚刚修正了它。谢谢! - jdi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接