Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Differentiating between signal sources in PySide

Is there trivial or elegant way to differentiate between many same-type signal sources in PySide/PyQt?

I am learning PySide. I have written simple application, which multiplies two numbers from two different QLineEdit() objects. Result is displayed in third QLineEdit.

Multiplier and multiplicand QLineEdit.textChanged() signals are connected to one method (TxtChanged). In this method i have to differentiate between signal sources. After some trials I figured out some workaround based upon placeholder text (4 lines below "is there another way?" comment in my code)

code:

import sys
from PySide import QtGui, QtCore

class myGUI(QtGui.QWidget):

    def __init__(self, *args, **kwargs):
        QtGui.QWidget.__init__(self, *args, **kwargs)

        self.multiplier = 0
        self.multiplicand = 0

        self.myGUIInit()

    def myGUIInit(self):
        # input forms
        a1_label = QtGui.QLabel("a1")
        a1_edit = QtGui.QLineEdit()
        a1_edit.setPlaceholderText("a1")

        a2_label = QtGui.QLabel("a2")
        a2_edit = QtGui.QLineEdit()
        a2_edit.setPlaceholderText("a2")

        # output form
        a1a2_label = QtGui.QLabel("a1*a2")
        self.a1a2_edit = QtGui.QLineEdit()
        self.a1a2_edit.setReadOnly(True)


        # forms events
        a1_edit.textChanged.connect(self.TxtChanged)
        a2_edit.textChanged.connect(self.TxtChanged)

        # grid
        grid = QtGui.QGridLayout()
        grid.setSpacing(10)

        grid.addWidget(a1_label,1,0)
        grid.addWidget(a1_edit,1,1)

        grid.addWidget(a2_label,2,0)
        grid.addWidget(a2_edit,2,1)

        grid.addWidget(a1a2_label,3,0)
        grid.addWidget(self.a1a2_edit,3,1)

        self.setLayout(grid)
        self.setGeometry(100,100,200,200)
        self.setWindowTitle("a*b")
        self.show()

    def TxtChanged(self,text):
        sender = self.sender()
        sender_text = sender.text()
        if sender_text == '': sender_text = '0'

        # is there another way?
        if sender.placeholderText() == 'a1':
            self.multiplicand = sender_text
        else:
            self.multiplier = sender_text

        product = int(self.multiplier) * int(self.multiplicand)

        print(self.multiplier,self.multiplicand,product)

        self.a1a2_edit.setText(str(product))


def main():
    app = QtGui.QApplication(sys.argv)
    mainWindow = myGUI()
    sys.exit(app.exec_())

main()

best regards, ostrzysz

like image 797
ostrzysz Avatar asked Feb 04 '12 21:02

ostrzysz


3 Answers

You can use the functools.partial function - and therefore connect your signals to straight to your method/function but rather to a python object which will automatically call your function with some extra data you pass it:

from functools import partial
...
        ....
        a1_edit.textChanged.connect(partial(self.TxtChanged, a1_edit))
        a2_edit.textChanged.connect(partial(self.TxtChanged, a2_edit))
        ...

    def TxtChanged(self,sender, text):
        # and here you have the "sender" parameter as it was filled in the call to "partial"
        ...

partials is part of the stdlib, and is very readable, but one can always use lambda instead of partial for the same effect -

a1_edit.textChanged.connect(lambda text: self.TxtChanged(a1_edit, text))

In this way the object yielded by the lambda expression will be a temporary function that will use the values for "self" and "a1_edit" from the current local variables (at the time the button is clicked), and the variable named "text" will be supplied by Pyside's callback.

like image 120
jsbueno Avatar answered Nov 14 '22 23:11

jsbueno


One thing that bugs me most in your code is that you are using placeholderText to differentiate. QObjects has another property called objectName that is more suitable for your task. And, you don't need to use sender.text() to get the text of QLineEdit. textChanged already sends it, so you will have it in your text parameter.

Also, using a dictionary instead of two separate variables (multiplier and multiplicand) will simplify your code further.

Here is the changed code:

class myGUI(QtGui.QWidget):

    def __init__(self, *args, **kwargs):
        QtGui.QWidget.__init__(self, *args, **kwargs)

        self.data = {"multiplier": 0,
                     "multiplicand": 0}

        self.myGUIInit()

    def myGUIInit(self):
        a1_label = QtGui.QLabel("a1")
        a1_edit = QtGui.QLineEdit()
        a1_edit.setObjectName("multiplicand")

        a2_label = QtGui.QLabel("a2")
        a2_edit = QtGui.QLineEdit()
        a2_edit.setObjectName("multiplier")

        # skipped the rest because same

    def TxtChanged(self, text):
        sender = self.sender()

        # casting to int while assigning seems logical.
        self.data[sender.objectName()] = int(text)

        product = self.data["multiplier"] * self.data["multiplicand"]

        print(self.data["multiplier"], self.data["multiplicand"], product)

        self.a1a2_edit.setText(str(product))
like image 37
Avaris Avatar answered Nov 14 '22 23:11

Avaris


Although @jsbueno and @Avaris answered your direct question about signal sources, I wouldn't relay on this sources in your concrete case. You can make instance members a1_edit and a2_edit:

...
self.a1_edit = QtGui.QLineEdit()
...
self.a2_edit = QtGui.QLineEdit()
...

It will simplify your TxtChanged function:

def TxtChanged(self,text):
    try:
        multiplier = int(self.a1_edit.text())
        multiplicand = int(self.a2_edit.text())
    except ValueError:
        self.a1a2_edit.setText('Enter two numbers')
        return
    product = multiplier * multiplicand
    print(multiplier, multiplicand, product)
    self.a1a2_edit.setText(str(product))

Also, instead of handling ValueError exception, you can use QIntValidator for input controls:

self.int_validator = QtGui.QIntValidator()
self.a1_edit.setValidator(self.int_validator)
self.a2_edit.setValidator(self.int_validator)
like image 38
reclosedev Avatar answered Nov 14 '22 23:11

reclosedev