Search code examples
pythonpython-3.xpyqt5

How to pass the value in one window class to one another?


Hi guys I want the user to choose the motor speed before going into step. So, in the first class, I made the first window which asked for the motor speed and another one is the recording and predicting process. In the recording process, I want to collect the WAV file as future data and use the motor speed as a file name. But I cannot pass the self.RPM value from one class to another. This is some part of the code.

class Ui_Form(object):
    
    def setupUi(self, Form):

        #The rest of the code

    def retranslateUi(self, Form):

        #The rest of the code

    def start_click(self):
        print('Start button click')
        _translate = QtCore.QCoreApplication.translate
        self.label.setText(_translate("Form", "<html><head/><body><p align=\"center\"><span style=\" color:#000000;\">Recording</span></p></body></html>"))
        app.processEvents()
        time.sleep(1)
        count = 0
        round = 0

        for i in range(3):
            round = i + 1
            text = "Round " + str(round) + "/3"
            self.label.setText(text)
            app.processEvents()
            print(text)

            #Recording
            now = datetime.now()
            day = now.strftime("%d")
            month = now.strftime("%m")
            year = now.strftime("%y")
            hour = now.strftime("%H")
            minute = now.strftime("%M")
            second = now.strftime("%S")
            print(day,"/",month,"/",year,hour,":",minute,":",second)
            
            CHUNK = 1024 #The number of frames in the buffer
            FORMAT = pyaudio.paInt16
            CHANNELS = 1 #Each frame will have 2 samples
            RATE = 44100 #The number of samples collected per second or we can called sample rate
            RECORD_SECONDS = 2 #Duration of recording
            WAVE_OUTPUT_FILENAME = f"Data2/Test/{day}{month}{year}_{hour}{minute}{second}.wav" <--- I want to add RPM value here

            p = pyaudio.PyAudio()

            stream = p.open(format = FORMAT, channels = CHANNELS, rate = RATE, input = True, frames_per_buffer = CHUNK)
            
            print("* recording")
            
            frames = []

            for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
                data = stream.read(CHUNK) #read audio data from the stream
                frames.append(data)
            
            print("* done recording")
            self.label.setText(_translate("Form", "<html><head/><body><p align=\"center\"><span style=\" color:#000000;\">Done Recording</span></p></body></html>"))
            
            stream.stop_stream()
            stream.close()
            p.terminate()
            
            wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb') # 'rb' read only, 'wb' write only
            wf.setnchannels(CHANNELS)
            wf.setsampwidth(p.get_sample_size(FORMAT))
            wf.setframerate(RATE)
            wf.writeframes(b''.join(frames)) #
            wf.close()

    #The rest of the code

class Ui_Form2(object):

    def openWindow(self):
        self.window = QtWidgets.QWidget()
        self.ui = Ui_Form()
        self.ui.setupUi(self.window)
        self.window.show()

    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(422, 202)
        Form.setFixedSize(422, 202)
        self.pushButton = QtWidgets.QPushButton(Form, clicked = lambda: self.openWindow())
        self.pushButton.setGeometry(QtCore.QRect(10, 120, 121, 71))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.pushButton.setFont(font)
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(Form, clicked = lambda: self.openWindow())
        self.pushButton_2.setGeometry(QtCore.QRect(150, 120, 121, 71))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.pushButton_2.setFont(font)
        self.pushButton_2.setObjectName("pushButton_2")
        self.pushButton_3 = QtWidgets.QPushButton(Form, clicked = lambda: self.openWindow())
        self.pushButton_3.setGeometry(QtCore.QRect(290, 120, 121, 71))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.pushButton_3.setFont(font)
        self.pushButton_3.setObjectName("pushButton_3")
        self.label = QtWidgets.QLabel(Form)
        self.label.setGeometry(QtCore.QRect(70, 20, 281, 81))
        self.label.setFrameShape(QtWidgets.QFrame.WinPanel)
        self.label.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.label.setObjectName("label")

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.pushButton.setText(_translate("Form", "1300 RPM"))
        self.pushButton.clicked.connect(lambda: self.rpm_button_clicked(self.pushButton.text()))
        self.pushButton_2.setText(_translate("Form", "1500 RPM"))
        self.pushButton_2.clicked.connect(lambda: self.rpm_button_clicked(self.pushButton_2.text()))
        self.pushButton_3.setText(_translate("Form", "1800 RPM"))
        self.pushButton_3.clicked.connect(lambda: self.rpm_button_clicked(self.pushButton_3.text()))
        self.label.setText(_translate("Form", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt; font-weight:600;\">RPM Selection</span></p></body></html>"))
    
    def rpm_button_clicked(self, button_text):
        rpm_values = {'1300 RPM': 1300, '1500 RPM': 1500, '1800 RPM': 1800}
        self.RPM = rpm_values[button_text]
        print(self.RPM)
    
if __name__ == "__main__":
    Form = QtWidgets.QWidget()
    ui = Ui_Form2()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec_())

I want to use the value from one class on another or anyone got a better idea than this can suggest


Solution

  • Either you use signal/slots (a Qt mechanism for observer/observable), or do it the Python object way.

    Signal and slot

    In the first case, you need to declare a signal in the class (A) where you select the motor speed, then in your other class (B) you connect the signal for your instance of A to a slot in your B instance.

    When doing Qt graphical applications, I prefer to use toy examples to better understand how it works.

    Here is what I mean :

    import sys
    from typing import Optional
    
    from PyQt5 import QtWidgets, QtCore
    
    
    class WindowA(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__(parent=None)
            self.setWindowTitle("A")
    
            central_widget = QtWidgets.QPushButton("Choose")
            central_widget.clicked.connect(self.on_button_Choose_pushed)
    
            self.setCentralWidget(central_widget)
    
        def on_button_Choose_pushed(self, checked: bool) -> None:
            print("'choose' button clicked")
            file_dialog = QtWidgets.QFileDialog(parent=self)
            if file_dialog.exec():
                print("a file was selected")
                filenames = file_dialog.selectedFiles()
                print(filenames)
                assert len(filenames) == 1
                print("emitting the signal")
                self.file_selected.emit(filenames[0])
            else:
                print("no file selected")
    
        file_selected = QtCore.pyqtSignal(str)
    
    
    class WindowB(QtWidgets.QMainWindow):
        def __init__(self, window_a: WindowA):
            super().__init__(parent=None)
            self.setWindowTitle("B")
    
            self.filename: Optional[str] = None
    
            central_widget = QtWidgets.QPushButton("Do something")
            central_widget.clicked.connect(self.on_button_Do_Something_pushed)
    
            self.setCentralWidget(central_widget)
    
            window_a.file_selected.connect(self.on_file_selected)
    
        def on_button_Do_Something_pushed(self, checked: bool) -> None:
            print("'do something' button clicked")
            print("filename is :", repr(self.filename))
            # do something
    
        @QtCore.pyqtSlot(str)
        def on_file_selected(self, filename: str) -> None:
            print("'file selected' slot received a signal, filename =", repr(filename))
            self.filename = filename
    
    
    def main() -> int:
        app = QtWidgets.QApplication([])
        window_a = WindowA()
        window_a.setVisible(True)
        window_b = WindowB(window_a)  # passing A to B
        window_b.setVisible(True)
        return app.exec()
    
    
    if __name__ == "__main__":
        sys.exit(main())
    

    (the 2 windows may appear one on top of the other)

    Click the button B "do something" button, then the A button "choose" and select a file, then click B again. You will get :

    'do something' button clicked
    filename is : None
    'choose' button clicked
    a file was selected
    ['/home/stack_overflow/log.log']
    emitting the signal
    'file selected' slot received a signal, filename = '/home/stack_overflow/log.log'
    'do something' button clicked
    filename is : '/home/stack_overflow/log.log'
    

    Just objects

    This is the over way around. You need your A to know of B, so that when a file is selected from A, it sets the variable for B.

    import sys
    from typing import Optional
    
    from PyQt5 import QtWidgets, QtCore
    
    
    class WindowA(QtWidgets.QMainWindow):
        def __init__(self, window_b: "WindowB"):
            super().__init__(parent=None)
            self.setWindowTitle("A")
    
            self._window_b = window_b  # save it for later
    
            central_widget = QtWidgets.QPushButton("Choose")
            central_widget.clicked.connect(self.on_button_Choose_pushed)
    
            self.setCentralWidget(central_widget)
    
        def on_button_Choose_pushed(self, checked: bool) -> None:
            print("'choose' button clicked")
            file_dialog = QtWidgets.QFileDialog(parent=self)
            if file_dialog.exec():
                print("a file was selected")
                filenames = file_dialog.selectedFiles()
                print(filenames)
                assert len(filenames) == 1
                print("setting the variable of B")
                self._window_b.filename = filenames[0]
            else:
                print("no file selected")
    
    
    class WindowB(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__(parent=None)
            self.setWindowTitle("B")
    
            self.filename: Optional[str] = None
    
            central_widget = QtWidgets.QPushButton("Do something")
            central_widget.clicked.connect(self.on_button_Do_Something_pushed)
    
            self.setCentralWidget(central_widget)
    
        def on_button_Do_Something_pushed(self, checked: bool) -> None:
            print("'do something' button clicked")
            print("filename is :", self.filename)
            # do something
    
    
    def main() -> int:
        app = QtWidgets.QApplication([])
        window_b = WindowB()
        window_b.setVisible(True)
        window_a = WindowA(window_b)  # passing B to A
        window_a.setVisible(True)
        return app.exec()
    
    
    if __name__ == "__main__":
        sys.exit(main())
    

    This way, A sets the variable of B.

    Conclusion

    If you understand these 2 techniques, you can change the structure. You can have a class containing the data, and the other being given a reference to the first class. Or you could make the code that instantiate both of them (here is is the main() function) connect the signal from the first to the slot of the second.