batuman
batuman

Reputation: 7304

'QWidget' object has no attribute 'set_status_message'

I use PyQt5 and Python2.7.

I have four Classes. App, UIWidget, PlayStreaming and Thread.

App is parent of UIWidget.

UIWidget is parent of PlayStreaming.

PlayStreaming is parent of Thread.

I like to pass Statusbar message from Thread Class to App Class, so that I can update status.

I use self.parent().set_status_message('') from children classes subsequently to display message with self.statusBar().showMessage('') in App class.

But I have error in UIWidget as

AttributeError: 'QWidget' object has no attribute 'set_status_message'
Aborted (core dumped)

How can I update to Status bar in MainWindow from Child class?

My code is as follow.

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow
import cv2
import time
import face_recognition.api as face_recognition

class Thread(QtCore.QThread):
    changePixmap = QtCore.pyqtSignal(QtGui.QImage)
    scaled_size = QtCore.QSize(640, 480)          
    curScale=1.0
    def run(self):
        cap = cv2.VideoCapture(-1)
        cap.set(3,1280);
        cap.set(4,1024);
        time.sleep(2)
        self.maxHeight=cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
        self.maxScale=self.maxHeight/480.0
        while True:
            ret, frame = cap.read()                  
            if ret:
                r=1
                face_locations=[]
                rescaleSize=int(480*self.curScale)
                if(frame.shape[0] > 480 and frame.shape[1] > 640):
                    r = rescaleSize / float(frame.shape[0])
                    dim = (int(frame.shape[1] * r), rescaleSize)
                    face_locations = face_recognition.face_locations(cv2.resize(frame, dim, fx=0.0, fy=0.0))
                else:
                    face_locations = face_recognition.face_locations(frame)
                for face_location in face_locations:  
                    top, right, bottom, left = face_location
                    cv2.rectangle(frame,(int(right/r),int(top/r)),(int(left/r),int(bottom/r)),(0,255,0),2)
                rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                convertToQtFormat = QtGui.QImage(rgbImage.data, rgbImage.shape[1], rgbImage.shape[0], QtGui.QImage.Format_RGB888)
                p = convertToQtFormat.scaled(self.scaled_size, QtCore.Qt.KeepAspectRatio)
                self.changePixmap.emit(p)

    @QtCore.pyqtSlot(QtCore.QSize)
    def scaled(self, scaled_size):
        self.scaled_size = scaled_size 

    @QtCore.pyqtSlot()
    def scaleup(self):
        self.curScale = self.curScale + 0.1
        if self.curScale > self.maxScale:
            self.curScale = self.maxScale
        self.parent().set_status_message('Cur scale:'+str(self.curScale))

    @QtCore.pyqtSlot()
    def scaledown(self):
        self.curScale = self.curScale - 0.1
        if self.curScale < 1.0:
            self.curScale = 1.0
        self.parent().set_status_message('Cur scale:'+str(self.curScale))


class PlayStreaming(QtWidgets.QLabel):
    reSize = QtCore.pyqtSignal(QtCore.QSize)
    scaleupSignal = QtCore.pyqtSignal()
    scaledownSignal = QtCore.pyqtSignal()
    def __init__(self):
        super(PlayStreaming, self).__init__()
        self.initUI()

    @QtCore.pyqtSlot(QtGui.QImage)
    def setImage(self, image):
        self.label.setPixmap(QtGui.QPixmap.fromImage(image))

    def initUI(self):
        self.setWindowTitle("Image")
        # create a label
        self.label = QtWidgets.QLabel(self)
        th = Thread(self)
        th.changePixmap.connect(self.setImage)
        self.scaleupSignal.connect(th.scaleup)
        self.scaledownSignal.connect(th.scaledown)
        self.reSize.connect(th.scaled)
        th.start()
        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)

    def resizeEvent(self, event):
        self.reSize.emit(self.size())
    def set_status_message(self, message):
        return self.parent().set_status_message(message) 

class UIWidget(QtWidgets.QWidget):

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

        # Initialize tab screen
        self.tabs = QtWidgets.QTabWidget()
        self.tab1 = QtWidgets.QWidget()
        self.tab2 = QtWidgets.QWidget()
        self.tab3 = QtWidgets.QWidget()

        # Add tabs
        self.tabs.addTab(self.tab1, "Face")
        self.tabs.addTab(self.tab2, "Human")
        self.tabs.addTab(self.tab3, "Vehicle")

        self.display = PlayStreaming()
        # Create first tab
        self.createGridLayout()
        self.tab1.layout = QtWidgets.QVBoxLayout()
        self.tab1.layout.addWidget(self.display, stretch=1)
        self.tab1.layout.addWidget(self.horizontalGroupBox)
        self.tab1.setLayout(self.tab1.layout)

        # Add tabs to widget
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.tabs)

    def createGridLayout(self):
        self.horizontalGroupBox = QtWidgets.QGroupBox("")
        self.horizontalGroupBox.setStyleSheet("QGroupBox{ background-color: red; border: none;}")  
        hlay1 = QtWidgets.QHBoxLayout()
        self.TestButton=QtWidgets.QPushButton('Test')
        hlay1.addWidget(self.TestButton) 
        self.RunButton=QtWidgets.QPushButton('Run')
        hlay1.addWidget(self.RunButton) 
        self.ScaleUpButton=QtWidgets.QPushButton('ScaleUp')
        self.ScaleUpButton.clicked.connect(self.display.scaleupSignal)
        hlay1.addWidget(self.ScaleUpButton) 
        self.ScaleDownButton=QtWidgets.QPushButton('ScaleDown')
        self.ScaleDownButton.clicked.connect(self.display.scaledownSignal)
        hlay1.addWidget(self.ScaleDownButton) 

        hlay2 = QtWidgets.QHBoxLayout()
        hlay2.addWidget(QtWidgets.QPushButton('Set Faces')) 
        hlay2.addWidget(QtWidgets.QPushButton('FacePose'))
        hlay2.addWidget(QtWidgets.QPushButton('Gender')) 
        hlay2.addWidget(QtWidgets.QPushButton('Age'))
        hlay2.addWidget(QtWidgets.QPushButton('Recognize'))

        layout = QtWidgets.QVBoxLayout()        
        layout.addLayout(hlay1)
        layout.addLayout(hlay2)
        self.horizontalGroupBox.setLayout(layout)

    def set_status_message(self, message):
        return self.statusBar().set_status_message(message) 

class App(QMainWindow): 
    def __init__(self):
        super(App,self).__init__()
        self.title = 'FaceHumanVehicle'
        self.left = 10
        self.top = 10
        self.width = 1000
        self.height = 800   
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        self.form_widget = UIWidget(self) 
        self.statusBar().showMessage('') 
        self.setCentralWidget(self.form_widget) 
        self.show()

    def set_status_message(self, message):
        return self.statusBar().showMessage(message) 

if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

Upvotes: 0

Views: 4385

Answers (1)

ekhumoro
ekhumoro

Reputation: 120718

Whenever you add a widget to a layout, it will be automatically re-parented to the widget that contains the layout. So this code:

    self.display = PlayStreaming()
    # Create first tab
    self.createGridLayout()
    self.tab1.layout = QtWidgets.QVBoxLayout()
    self.tab1.layout.addWidget(self.display, stretch=1)

will mean that self.display.parent() returns self.tab1, which obviously does not have a set_status_message method (since it's just a plain QWidget).

However, it's bad practice to try to directly access gui methods from a thread, so a better solution is to emit a custom signal that sends the status-bar message:

class Thread(QtCore.QThread):
    statusMessage = QtCore.pyqtSignal(str)
    ...
    def scaleup(self):
        ...
        self.statusMessage.emit('Cur scale:'+str(self.curScale))

You can then connect this up within your PlayStreaming class and use its window() method to access the top-level main-window:

class PlayStreaming(QtWidgets.QLabel):
    ...
    def initUI(self):
        ...
        th = Thread(self)
        th.statusMessage.connect(self.handle_status_message)

    def handle_status_message(self, message):
        self.window().set_status_message(message) 

Upvotes: 1

Related Questions