0

in the code below I am trying to figure out the best way to share data from the "Data_Class" Class to the "Tab_2" Class. Specifically I am trying to get the values from "Data_Class" in tab1 to fill in the QLineEdit in tab2 when the buttons are clicked. I have tried using signals and sending direct to the "Tab_2" class as well but the best I can get is the values to print out in "Tab_2" but not fill the QLineEdit.
In the "MainWindow" Class I build and seperate the data and then embed "Data_Class" using FlowLayout while sending the data to "Data_Class" to create the buttons and also set the values to them but now I can't get the data sent over to "Tab_2" Class.
Also, the signal in "Data_Class" doesn't work, I just left it in anyway.
Does anyone know what I should do to receive the values properly or have any suggestions? Thanks.

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_()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Richard
  • 445
  • 1
  • 5
  • 21

2 Answers2

0

The response of @S. Nick is wrong since QWidget inherits from QObject and therefore supports the signals. Let's check it with the following code:

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

The error is caused by bad programming practice, for example in your code you see the following:

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

Which is equivalent to:

Data_Class(self).some_xxxx

if self were a Data_Class it would be valid but in this case self is Tab_2, so have code that has the following format:

SOME_CLASS(self).some_xxx

it is a bad practice of programming since not verifying the belonging of self to the class can hide many errors as it is in this case.


In OOP the interactions between objects must occur in scopes where both objects exist, in your case where the objects of the class Data_Class exist and the object belonging to Tab_2 also, currently there is no part of your code that exists, so you have to create it.

for this we keep the object that belongs to Tab_2 as member of the class and we will use it in the loop where you create the 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)  # <----
...
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • 1
    Thanks for explaing things a bit. I also knew that self.data_class = Data_Class etc was bad code. I was trying all sorts of stuff seeing what kind of response I could get and I just left it it in anyway to try to make sure it was more clear what I was going for. – Richard May 20 '18 at 22:44
  • 1
    @Richard In OOP the interactions between objects, the classes do not interact, sometimes we forget about it and we generate this type of problems. :D – eyllanesc May 20 '18 at 22:47
-1

New signals should only be defined in sub-classes of QObject. Try it:

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

enter image description here

S. Nick
  • 12,879
  • 8
  • 25
  • 33
  • the QWidget class inherits from QObject, so the problem is not that. see http://doc.qt.io/qt-5/qwidget.html: **Inherits: QObject and QPaintDevice** – eyllanesc May 20 '18 at 22:10
  • http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html New signals should only be defined in sub-classes of QObject. – S. Nick May 20 '18 at 22:20
  • QWidget is a subclass of QObject and therefore any subclass of QWidget is also QObject, if the widgets were not they would not have signals, for example QPushButton has the signal clicked. you can check it with the following `assert(isinstance(QWidget(), QObject))` – eyllanesc May 20 '18 at 22:22
  • I signaled the sub-classes of QObject. Everything is working. What is the problem? – S. Nick May 20 '18 at 22:27
  • A question of quality must have a good foundation, that works once does not mean it always works, so your code works in some way now but it is a terrible code that will bring future readers confusions and more problems. – eyllanesc May 20 '18 at 22:31
  • 1
    Hi, Thanks for your help. It seems to work. I just got it working too but the only way I could get it to work was sending a signal back to the mainwindow and and then signaling from there to the Tab_2 class. I am going to leave this open for a little bit in case someone else has any suggestions and then i'll select your answer. Thanks again. – Richard May 20 '18 at 22:31