【问题标题】:PyQt Issue sending values to different classPyQt问题将值发送到不同的类
【发布时间】:2018-05-20 20:30:13
【问题描述】:

在下面的代码中,我试图找出将数据从“Data_Class”类共享到“Tab_2”类的最佳方式。具体来说,我试图从 tab1 中的“Data_Class”中获取值,以在单击按钮时填写 tab2 中的 QLineEdit。我也尝试过使用信号并直接发送到“Tab_2”类,但我能得到的最好的结果是在“Tab_2”中打印出来的值,但不填充 QLineEdit。
在“MainWindow”类中,我构建并分离数据,然后使用 FlowLayout 嵌入“Data_Class”,同时将数据发送到“Data_Class”以创建按钮并为其设置值,但现在我无法获取发送过来的数据到“Tab_2”类。
另外,“Data_Class”中的信号不起作用,我还是把它留在了。
有谁知道我应该怎么做才能正确接收这些值或有任何建议?谢谢。

import functools
import sys
from PyQt5.QtCore import pyqtSignal, QSize,  QRect, Qt, QPoint
from PyQt5.QtWidgets import QLineEdit, QPushButton,QVBoxLayout, QScrollArea, QWidget, \
    QApplication, QTabWidget, QStyle, QSizePolicy, QLayout


class Data_Class(QWidget):
    data_Signal = pyqtSignal(str)
    def __init__(self, item_num=None, item_data=None):
        super(Data_Class, self).__init__()
        self.tab_2 = Tab_2
        self.num = item_num
        self.dat = item_data
        self.setFixedWidth(150)
        self.layout = QVBoxLayout()
        self.btn = QPushButton()
        self.btn.setText(str(self.num))
        self.layout.addWidget(self.btn)
        self.setLayout(self.layout)
        self.btn_click = functools.partial(self.set_btns, btn_data=self.dat)
        self.btn.clicked.connect(self.btn_click)

    def set_btns(self, btn_data):
        print(btn_data)
        self.data_Signal.emit(btn_data)

class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.resize(368,315)
        self.vbox = QVBoxLayout()
        self.tabWidget = QTabWidget()
        self.tabWidget.setTabPosition(QTabWidget.North)
        self.vbox.addWidget(self.tabWidget)
        self.tab1 = QWidget()
        self.tabWidget.addTab(self.tab1, ("tab1"))
        self.tab2 = QWidget()
        self.tabWidget.addTab(self.tab2, ("tab2"))
        self.layout1 = QVBoxLayout()
        self.layout2 = QVBoxLayout()
        self.tab1.setLayout(self.layout1)
        self.tab2.setLayout(self.layout2)
        self.scrollarea = QScrollArea(self)
        self.scrollarea.setWidgetResizable(True)
        self.widgets = QWidget()
        self.Layout = FlowLayout(self.widgets) ##############FlowLayout
        self.scrollarea.setWidget(self.widgets)
        self.layout1.addWidget(self.scrollarea)
        self.layout2.addWidget(Tab_2(self))
        self.setLayout(self.vbox)
        self.items =[]
        self.extract()

    def extract(self):
        mydict = {'num': 1, 'data': 'data1'},{'num': 2, 'data': 'data2'}, {'num': 3, 'data': 'data3'}, \
                 {'num': 4, 'data': 'data4'},{'num': 5, 'data': 'data5'},{'num': 6, 'data': 'data6'}, \
                 {'num': 7, 'data': 'data7'}, {'num': 8, 'data': 'data8'}, {'num': 9, 'data': 'data9'}, \
                 {'num': 10, 'data': 'data10'}, {'num': 11, 'data': 'data11'}, {'num': 12, 'data': 'data12'}
        self.items[:] = mydict
        for item in self.items:
            self.add_btns(item)

    def add_btns(self, item):
        layout = self.Layout ##################FlowLayout
        item_num = item['num']
        item_data = item['data']
        widget = Data_Class(item_num, item_data)
        layout.addWidget(widget)

class Tab_2(QWidget):
    def __init__(self, parent=None):
        super(Tab_2, self).__init__(parent)
        self.data_class = Data_Class
        self.vert_layout = QVBoxLayout(self)
        self.line_edit = QLineEdit()
        self.vert_layout.addWidget(self.line_edit)
        self.setLayout(self.vert_layout)
        self.data_class(self).data_Signal.connect(self.set_line_edit)

    def set_line_edit(self, btn_data):
        print(btn_data)
        self.line_edit.setText(btn_data)

class FlowLayout(QLayout):
    def __init__(self, parent=None, margin=-0, hspacing=-0, vspacing=-0):
        super(FlowLayout, self).__init__(parent)
        self._hspacing = hspacing
        self._vspacing = vspacing
        self._items = []
        self.setContentsMargins(margin, margin, margin, margin)

    def __del__(self):
        del self._items[:]

    def addItem(self, item):
        self._items.append(item)

    def horizontalSpacing(self):
        if self._hspacing >= 0:
            return self._hspacing
        else:
            return self.smartSpacing(
                QStyle.PM_LayoutHorizontalSpacing)

    def verticalSpacing(self):
        if self._vspacing >= 0:
            return self._vspacing
        else:
            return self.smartSpacing(
                QStyle.PM_LayoutVerticalSpacing)

    def count(self):
        return len(self._items)

    def itemAt(self, index):
        if 0 <= index < len(self._items):
            return self._items[index]

    def takeAt(self, index):
        if 0 <= index < len(self._items):
            return self._items.pop(index)

    def expandingDirections(self):
        return Qt.Orientations(0)

    def hasHeightForWidth(self):
        return True

    def heightForWidth(self, width):
        return self.doLayout(QRect(0, 0, width, 0), True)

    def setGeometry(self, rect):
        super(FlowLayout, self).setGeometry(rect)
        self.doLayout(rect, False)

    def sizeHint(self):
        return self.minimumSize()

    def minimumSize(self):
        size = QSize()
        for item in self._items:
            size = size.expandedTo(item.minimumSize())
        left, top, right, bottom = self.getContentsMargins()
        size += QSize(left + right, top + bottom)
        return size

    def doLayout(self, rect, testonly):
        left, top, right, bottom = self.getContentsMargins()
        effective = rect.adjusted(+left, +top, -right, -bottom)
        x = effective.x()
        y = effective.y()
        lineheight = 0
        for item in self._items:
            widget = item.widget()
            hspace = self.horizontalSpacing()
            if hspace == -1:
                hspace = widget.style().layoutSpacing(
                    QSizePolicy.PushButton,
                    QSizePolicy.PushButton, Qt.Horizontal)
            vspace = self.verticalSpacing()
            if vspace == -1:
                vspace = widget.style().layoutSpacing(
                    QSizePolicy.PushButton,
                    QSizePolicy.PushButton, Qt.Vertical)
            nextX = x + item.sizeHint().width() + hspace
            if nextX - hspace > effective.right() and lineheight > 0:
                x = effective.x()
                y = y + lineheight + vspace
                nextX = x + item.sizeHint().width() + hspace
                lineheight = 0
            if not testonly:
                item.setGeometry(
                    QRect(QPoint(x, y), item.sizeHint()))
            x = nextX
            lineheight = max(lineheight, item.sizeHint().height())
        return y + lineheight - rect.y() + bottom

    def smartSpacing(self, pm):
        parent = self.parent()
        if parent is None:
            return -1
        elif parent.isWidgetType():
            return parent.style().pixelMetric(pm, None, parent)
        else:
            return parent.spacing()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    wnd = MainWindow()
    wnd.show()
    app.exec_()

【问题讨论】:

    标签: python pyqt pyqt5 qt-signals


    【解决方案1】:

    @S. Nick 的响应是错误的,因为 QWidget 继承自 QObject,因此支持信号。让我们用下面的代码检查一下:

    import sys
    
    from PyQt5.QtWidgets import QApplication, QWidget
    from PyQt5.QtCore import QObject
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        obj = QWidget()
        assert(isinstance(obj, QObject))
        obj.show()
        sys.exit(app.exec_())
    

    错误是由不良的编程习惯引起的,例如在您的代码中,您会看到以下内容:

    self.data_class = Data_Class
    ...
    self.data_class(self).data_Signal.connect(self.set_line_edit)
    

    相当于:

    Data_Class(self).some_xxxx
    

    如果selfData_Class,则它是有效的,但在这种情况下selfTab_2,因此代码格式如下:

    SOME_CLASS(self).some_xxx
    

    这是一种不好的编程习惯,因为在这种情况下不验证 self 是否属于类会隐藏许多错误。


    在 OOP 中,对象之间的交互必须发生在两个对象都存在的范围内,在您的情况下,Data_Class 类的对象存在并且属于 Tab_2 的对象也存在,目前您的代码不存在任何部分,所以您必须创建它。

    为此,我们将属于 Tab_2 的对象保留为该类的成员,我们将在您创建 for 的循环中使用它:

    class MainWindow(QWidget):
        def __init__(self):
            ...
            self.layout1.addWidget(self.scrollarea)
            self.tab_object = Tab_2(self) # <-----
            self.layout2.addWidget(self.tab_object)  # <-----
            ...
    
        def add_btns(self, item):
            layout = self.Layout ##################FlowLayout
            item_num = item['num']
            item_data = item['data']
            widget = Data_Class(item_num, item_data) # <-----
            widget.data_Signal.connect(self.tab_object.set_line_edit) # <-----
            layout.addWidget(widget)
    
    
    class Tab_2(QWidget):
        def __init__(self, parent=None):
            super(Tab_2, self).__init__(parent)
            #self.data_class = Data_Class # <----
            self.vert_layout = QVBoxLayout(self)
            self.line_edit = QLineEdit()
            self.vert_layout.addWidget(self.line_edit)
            self.setLayout(self.vert_layout)
            #self.data_class(self).data_Signal.connect(self.set_line_edit)  # <----
    ...
    

    【讨论】:

    • 感谢您稍微解释一下。我也知道 self.data_class= Data_Class 等是错误的代码。我尝试了各种各样的东西,看看我能得到什么样的回应,但我还是把它留在了那里,以确保更清楚我想要什么。
    • @Richard 在 OOP 中,对象之间的交互,类之间不交互,有时我们会忘记它并产生此类问题。 :D
    【解决方案2】:

    新信号只能在 QObject 的子类中定义。 试试看:

    import functools
    import sys
    from PyQt5.QtCore import pyqtSignal, QSize,  QRect, Qt, QPoint,    QObject  # +++
    from PyQt5.QtWidgets import QLineEdit, QPushButton,QVBoxLayout, QScrollArea, QWidget, \
        QApplication, QTabWidget, QStyle, QSizePolicy, QLayout
    
    # +++  New signals should only be defined in sub-classes of QObject.  
    class _Signals(QObject):
        data_Signal = pyqtSignal(str)
    
    class Data_Class(QWidget):
        #data_Signal = pyqtSignal(str)
        Signals = _Signals()
        def __init__(self, item_num=None, item_data=None):
            super(Data_Class, self).__init__()
            self.tab_2 = Tab_2
            self.num = item_num
            self.dat = item_data
            self.setFixedWidth(150)
            self.layout = QVBoxLayout()
            self.btn = QPushButton()
            self.btn.setText(str(self.num))
            self.layout.addWidget(self.btn)
            self.setLayout(self.layout)
            self.btn_click = functools.partial(self.set_btns, btn_data=self.dat)
            self.btn.clicked.connect(self.btn_click)
    
        def set_btns(self, btn_data):
            print(btn_data)
            #self.data_Signal.emit(btn_data)
            self.Signals.data_Signal.emit(btn_data)
    
    class MainWindow(QWidget):
        def __init__(self):
            super(MainWindow, self).__init__()
            self.resize(368,315)
            self.vbox = QVBoxLayout()
            self.tabWidget = QTabWidget()
            self.tabWidget.setTabPosition(QTabWidget.North)
            self.vbox.addWidget(self.tabWidget)
            self.tab1 = QWidget()
            self.tabWidget.addTab(self.tab1, ("tab1"))
            self.tab2 = QWidget()
            self.tabWidget.addTab(self.tab2, ("tab2"))
            self.layout1 = QVBoxLayout()
            self.layout2 = QVBoxLayout()
            self.tab1.setLayout(self.layout1)
            self.tab2.setLayout(self.layout2)
            self.scrollarea = QScrollArea(self)
            self.scrollarea.setWidgetResizable(True)
            self.widgets = QWidget()
            self.Layout = FlowLayout(self.widgets) ##############FlowLayout
            self.scrollarea.setWidget(self.widgets)
            self.layout1.addWidget(self.scrollarea)
            self.layout2.addWidget(Tab_2(self))
            self.setLayout(self.vbox)
            self.items =[]
            self.extract()
    
        def extract(self):
            mydict = {'num': 1, 'data': 'data1'},{'num': 2, 'data': 'data2'}, {'num': 3, 'data': 'data3'}, \
                     {'num': 4, 'data': 'data4'},{'num': 5, 'data': 'data5'},{'num': 6, 'data': 'data6'}, \
                     {'num': 7, 'data': 'data7'}, {'num': 8, 'data': 'data8'}, {'num': 9, 'data': 'data9'}, \
                     {'num': 10, 'data': 'data10'}, {'num': 11, 'data': 'data11'}, {'num': 12, 'data': 'data12'}
            self.items[:] = mydict
            for item in self.items:
                self.add_btns(item)
    
        def add_btns(self, item):
            layout = self.Layout ##################FlowLayout
            item_num = item['num']
            item_data = item['data']
            widget = Data_Class(item_num, item_data)
            layout.addWidget(widget)
    
    class Tab_2(QWidget):
        def __init__(self, parent=None):
            super(Tab_2, self).__init__(parent)
            self.data_class = Data_Class
            self.vert_layout = QVBoxLayout(self)
            self.line_edit = QLineEdit()
            self.vert_layout.addWidget(self.line_edit)
            self.setLayout(self.vert_layout)
            #self.data_class(self).data_Signal.connect(self.set_line_edit)
            self.data_class(self).Signals.data_Signal.connect(self.set_line_edit)
    
        def set_line_edit(self, btn_data):
            print(btn_data)
            self.line_edit.setText(btn_data)
    
    class FlowLayout(QLayout):
        def __init__(self, parent=None, margin=-0, hspacing=-0, vspacing=-0):
            super(FlowLayout, self).__init__(parent)
            self._hspacing = hspacing
            self._vspacing = vspacing
            self._items = []
            self.setContentsMargins(margin, margin, margin, margin)
    
        def __del__(self):
            del self._items[:]
    
        def addItem(self, item):
            self._items.append(item)
    
        def horizontalSpacing(self):
            if self._hspacing >= 0:
                return self._hspacing
            else:
                return self.smartSpacing(
                    QStyle.PM_LayoutHorizontalSpacing)
    
        def verticalSpacing(self):
            if self._vspacing >= 0:
                return self._vspacing
            else:
                return self.smartSpacing(
                    QStyle.PM_LayoutVerticalSpacing)
    
        def count(self):
            return len(self._items)
    
        def itemAt(self, index):
            if 0 <= index < len(self._items):
                return self._items[index]
    
        def takeAt(self, index):
            if 0 <= index < len(self._items):
                return self._items.pop(index)
    
        def expandingDirections(self):
            return Qt.Orientations(0)
    
        def hasHeightForWidth(self):
            return True
    
        def heightForWidth(self, width):
            return self.doLayout(QRect(0, 0, width, 0), True)
    
        def setGeometry(self, rect):
            super(FlowLayout, self).setGeometry(rect)
            self.doLayout(rect, False)
    
        def sizeHint(self):
            return self.minimumSize()
    
        def minimumSize(self):
            size = QSize()
            for item in self._items:
                size = size.expandedTo(item.minimumSize())
            left, top, right, bottom = self.getContentsMargins()
            size += QSize(left + right, top + bottom)
            return size
    
        def doLayout(self, rect, testonly):
            left, top, right, bottom = self.getContentsMargins()
            effective = rect.adjusted(+left, +top, -right, -bottom)
            x = effective.x()
            y = effective.y()
            lineheight = 0
            for item in self._items:
                widget = item.widget()
                hspace = self.horizontalSpacing()
                if hspace == -1:
                    hspace = widget.style().layoutSpacing(
                        QSizePolicy.PushButton,
                        QSizePolicy.PushButton, Qt.Horizontal)
                vspace = self.verticalSpacing()
                if vspace == -1:
                    vspace = widget.style().layoutSpacing(
                        QSizePolicy.PushButton,
                        QSizePolicy.PushButton, Qt.Vertical)
                nextX = x + item.sizeHint().width() + hspace
                if nextX - hspace > effective.right() and lineheight > 0:
                    x = effective.x()
                    y = y + lineheight + vspace
                    nextX = x + item.sizeHint().width() + hspace
                    lineheight = 0
                if not testonly:
                    item.setGeometry(
                        QRect(QPoint(x, y), item.sizeHint()))
                x = nextX
                lineheight = max(lineheight, item.sizeHint().height())
            return y + lineheight - rect.y() + bottom
    
        def smartSpacing(self, pm):
            parent = self.parent()
            if parent is None:
                return -1
            elif parent.isWidgetType():
                return parent.style().pixelMetric(pm, None, parent)
            else:
                return parent.spacing()
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        wnd = MainWindow()
        wnd.show()
        app.exec_()
    

    【讨论】:

    • QWidget类继承自QObject,所以问题不在于这个。参见doc.qt.io/qt-5/qwidget.html继承:QObject 和 QPaintDevice
    • pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html 新信号只能在 QObject 的子类中定义。
    • QWidget 是 QObject 的子类,因此 QWidget 的任何子类也是 QObject,如果小部件不是它们,它们将没有信号,例如 QPushButton 具有单击的信号。您可以通过以下assert(isinstance(QWidget(), QObject))查看
    • 我通知了 QObject 的子类。一切正常。有什么问题?
    • 您好,感谢您的帮助。它似乎工作。我也刚刚让它工作,但我能让它工作的唯一方法是将信号发送回主窗口,然后从那里向 Tab_2 类发送信号。如果其他人有任何建议,我将把它打开一点,然后我会选择你的答案。再次感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-08
    • 1970-01-01
    • 1970-01-01
    • 2016-03-10
    • 2011-11-12
    • 2020-07-26
    • 1970-01-01
    相关资源
    最近更新 更多