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.

MisterTea
  • 21
  • 1
  • 9
  • 1
    It seems like you just want the built-in [`setattr`](https://docs.python.org/3/library/functions.html#setattr) function. – jonrsharpe Feb 25 '17 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 – MisterTea Feb 25 '17 at 17:03

1 Answers1

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!

Community
  • 1
  • 1
Crispin
  • 2,070
  • 1
  • 13
  • 16
  • 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. – MisterTea Feb 25 '17 at 17:14
  • 1
    This is overcomplicated. The standard idiom is to use `lambda` or `partial`: e.g. `widget.signal.connect(lambda: func(arg1, arg2))`. – ekhumoro Feb 25 '17 at 19:47
  • thanks, @ekhumoro. I updated the demo to use lambdas. I'm not sure why I ever thought QSignalMapper was the way to go. – Crispin Feb 26 '17 at 01:39