4

Hi I have a simple GUI as below with 2 buttons. I have written a method that changes the text on the button once the button is clicked. I want to make the method modular and general in order to apply the method to any button without rewriting. In the example below, how can I apply the printWow() method to button 2 without defining a new method for it?

import sys
from PyQt4.Qt import *

class MainWindow(QMainWindow):
    def __init__(self, *args):
        QMainWindow.__init__(self, *args)
        self.cw = QWidget(self)
        self.setCentralWidget(self.cw)
        self.btn1 = QPushButton("Click me", self.cw)
        self.btn1.setGeometry(QRect(50, 50, 100, 30))
        self.btn1.clicked.connect(self.printWow)

        self.btn2 = QPushButton("Click me", self.cw)
        self.btn2.setGeometry(QRect(50, 20, 100, 30))
        self.btn2.clicked.connect(self.printWow)

    def printWow(self):
        self.btn1.setText("WoW")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    myapp = MainWindow()
    myapp.show()
    sys.exit(app.exec_())
dnth
  • 879
  • 2
  • 12
  • 22

2 Answers2

1

You can determine which button was clicked by using the sender() method.

Returns a pointer to the object that sent the signal, if called in a slot activated by a signal; otherwise it returns 0. The pointer is valid only during the execution of the slot that calls this function from this object's thread context.

The pointer returned by this function becomes invalid if the sender is destroyed, or if the slot is disconnected from the sender's signal.

There is an important warning in the documention though, that will conflict with your post's title:

Warning: This function violates the object-oriented principle of modularity. However, getting access to the sender might be useful when many signals are connected to a single slot.


You can use sender() like so:

def printWow(self):
    self.sender().setText("WoW")
Andy
  • 49,085
  • 60
  • 166
  • 233
1

Basically, making a function "more generic" means identifying the variants and invariants in the different use cases. In your example, the variant(s) would be the object on which you want to call setText() and eventually the text itself, so your "generic" function would look something like:

def printText(self, target, text="wow"):
    target.setText(text)

Then since you cannot pass these arguments to connect() (well at least let's assume this is the case for the sake of our example), you need to wrap together the reference to the generic function and the arguments, in something that can be called without arguments. This is an exemple of "partial evaluation", and in it's simplest form it requires nothing more than a lambda:

class MainWindow(QMainWindow):
    def __init__(self, *args):
        QMainWindow.__init__(self, *args)
        self.cw = QWidget(self)
        self.setCentralWidget(self.cw)
        self.btn1 = QPushButton("Click me", self.cw)
        self.btn1.setGeometry(QRect(50, 50, 100, 30))
        self.btn1.clicked.connect(lambda: self.printWow(self.btn1))

        self.btn2 = QPushButton("Click me", self.cw)
        self.btn2.setGeometry(QRect(50, 20, 100, 30))
        self.btn2.clicked.connect(lambda: self.printText(self.btn2, "Yay!"))

    def printText(self, target, text="wow"):
        target.setText(text)

Note that in the above exemple you don't gain much from the extra complexity - you could as well put everything in the lambda ie:

self.btn1.clicked.connect(lambda: self.btn1.setText("Wow"))
self.btn2.clicked.connect(lambda: self.btn2.setText("Yay !"))

but I assume you have something more complex in mind...

Now since you mentions "modularity" (at least in your title), the real key to "modular" code is not necessarily (or at least not only) factoring out common code, but also and primarily splitting your code in well decoupled modules, each having clear and distinct responsabilities. A canonical example is separating UI code from "domain" code - the code that does the real job and shouldn't even know a UI exists at all...

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118