0

I need to make a UI for a class which makes a number of calculations from a set of variables. The code roughly looks like this:

class MainWindow:
    def __init__(self):
        pulse = Pulse()

        self.var1_lineEdit.textEdited.connect(self.set_attribute(pulse, "var1", new_value))
        self.var2_lineEdit.textEdited.connect(self.set_attribute(pulse, "var2", new_value))
        self.var3_lineEdit.textEdited.connect(self.set_attribute(pulse, "var3", new_value))
        ....

    def set_attribute(pulse, var_name, value):
        # get value from line edit
        # update value in pulse
        # update other calculations

class Pulse:
    def __init__(self):
        var1 = 1
        var2 = 2
        var3 = 3
        ....

My problem here is how to code set_attribute. Is there a way to access individual variables of another class by passing the name as a function argument? I would like to avoid having to define a new function for each variable.

Or is there a simpler way to do this? I am not very familiar with OOP and the proper way of coding UIs.

2
  • 1
    It seems like you just want the built-in setattr function. Commented Feb 25, 2017 at 12:53
  • It's more complicated than that, if I understood @waterboy5281 correctly. I'm trying to connect multiple signals to one slot yielding different results depending on the signal emitter Commented Feb 25, 2017 at 17:03

1 Answer 1

1

EDIT

The signal/slot connections you've proposed will get you in trouble. Signals are connected to slots using the connect function, which accepts a python callable as its argument--not the function call itself. That's the difference between

widget.textChanged.connect(self.set_attribute) # right syntax
widget.textChanged.connect(self.set_attribute()) # wrong syntax

So then, how do you tell set_attribute what to do? ekhumoro informed me the error of my ways re: use of QSignalMapper. It's an outdated technique. Best to simply use lambda functions (or partials) to get the desired effect.

I updated my demo for use of lambdas

For the purposes of this demo, I've created a simple QMainWindow with two QLineEdit widgets and a QPushButton.

# file ui_main.py
from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(359, 249)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.formLayoutWidget = QtWidgets.QWidget(self.centralwidget)
        self.formLayoutWidget.setGeometry(QtCore.QRect(0, 0, 351, 51))
        self.formLayoutWidget.setObjectName("formLayoutWidget")
        self.formLayout = QtWidgets.QFormLayout(self.formLayoutWidget)
        self.formLayout.setObjectName("formLayout")
        self.configParam1Label = QtWidgets.QLabel(self.formLayoutWidget)
        self.configParam1Label.setObjectName("configParam1Label")
        self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.configParam1Label)
        self.configEntry = QtWidgets.QLineEdit(self.formLayoutWidget)
        self.configEntry.setObjectName("configEntry")
        self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.configEntry)
        self.configParam2Label = QtWidgets.QLabel(self.formLayoutWidget)
        self.configParam2Label.setObjectName("configParam2Label")
        self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.configParam2Label)
        self.configEntry2 = QtWidgets.QLineEdit(self.formLayoutWidget)
        self.configEntry2.setObjectName("configEntry2")
        self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.configEntry2)
        self.pulseButton = QtWidgets.QPushButton(self.centralwidget)
        self.pulseButton.setGeometry(QtCore.QRect(130, 140, 75, 23))
        self.pulseButton.setObjectName("pulseButton")
        MainWindow.setCentralWidget(self.centralwidget)

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

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.configParam1Label.setText(_translate("MainWindow", "Config Param 1"))
        self.configParam2Label.setText(_translate("MainWindow", "Config Param 2"))
        self.pulseButton.setText(_translate("MainWindow", "GetPulse"))

In another file, after some imports, define Pulse--a simple class that contains two attributes

# file main.py
from PyQt5.QtCore import (QSignalMapper, pyqtSlot)
from PyQt5.QtWidgets import (QApplication, QMainWindow, QMessageBox)

from ui_main import Ui_MainWindow

class Pulse:
    def __init__(self):
        self.param_one = None
        self.param_two = None

Now, define MainWindow, setting up its UI and giving it a Pulse:

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.pulse = Pulse()

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.widget_one = self.ui.configEntry
        self.widget_two = self.ui.configEntry2
        self.pulse_button = self.ui.pulseButton

        self.widget_one.textChanged.connect(lambda: self.widget_handler('param_one', self.widget_one.text()))
        self.widget_one.textChanged.connect(lambda: self.widget_handler('param_two', self.widget_two.text()))

    def widget_handler(self, attr, value):
        setattr(self.pulse,attr,value)

    def handler(self, idx):
        widget, attr = self.attribute_widget_list[idx]
        widget_value = widget.text()
        setattr(self.pulse,attr,widget_value)

setattr is equivalent to, for example, self.pulse.param_one = widget_value.

To prove it works, let's use that QPushButton to probe the state of Pulse:

    @pyqtSlot()
    def on_pulseButton_clicked(self):
        message = QMessageBox()
        message.setText("{} - {}".format(self.pulse.param_one, self.pulse.param_two))
        message.exec()

Here I'm using a different signal/slot connection scheme, which you can read about here

And here's how we run the script:

if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

Hope that helps!

Sign up to request clarification or add additional context in comments.

3 Comments

thanks for the reminder on the connect function. In the mean time I wrote a function that blindly updates every attribute on each edit line change, it does the job but it's sloppy.Your solution seems very interesting, I'll give it a try once I get a first version of my ui running.
This is overcomplicated. The standard idiom is to use lambda or partial: e.g. widget.signal.connect(lambda: func(arg1, arg2)).
thanks, @ekhumoro. I updated the demo to use lambdas. I'm not sure why I ever thought QSignalMapper was the way to go.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.