Skip to content
Advertisement

Click a button and change MainWindow

I am learning Qt and I am interesting in how to change some features of MainWindow.

I was trying this code, but there were some errors when I clicked the first button:

Traceback (most recent call last):
File "main.py", line 15, in run_the_first_button_was_clicked
the_first_button_was_clicked(self)
File "clickedButton.py", line 15, in the_first_button_was_clicked
self.button2.clicked.connect(self.the_second_button_was_clicked) 

AttributeError: 'MainWindow' object has no attribute 'the_second_button_was_clicked'

what I did wrong (how could I do ‘the_second_button_was_clicked’ callable )?

main.py

from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton
import sys
from clickedButton import the_first_button_was_clicked, the_second_button_was_clicked


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("MainWindow")
        self.button1 = QPushButton("button1")
        self.button1.clicked.connect(self.run_the_first_button_was_clicked)
        self.setCentralWidget(self.button1)

    def run_the_first_button_was_clicked(self):
        the_first_button_was_clicked(self)


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

clickedButton.py

from PySide2.QtWidgets import QPushButton
from PySide2 import QtCore


def the_first_button_was_clicked(self):
    self.setWindowTitle("the_first_button_was_clicked next will be the_second_button_was_clicked")
    self.resize(800, 600)

    self.button1.setText("the_first_button_was_clicked")
    self.button1.setEnabled(False)

    self.button2 = QPushButton("button2")
    self.button2.setGeometry(QtCore.QRect(100, 100, 150, 150))
    self.button2.setVisible(True)
    self.button2.clicked.connect(self.the_second_button_was_clicked)


def the_second_button_was_clicked(self):
    self.setWindowTitle("the_second_button_was_clicked")
    self.resize(600, 800)

Advertisement

Answer

The issue has nothing to do with PyQt, but with how classes and instances work.

The first argument of instance methods always refers to the instance of the class, and it’s called self just for convention: it could actually be named in any way as long as its syntax is valid, just like any other variable.

When using functions that are declared outside a class, it’s good practice to avoid that naming convention (mostly to avoid confusion when reading code).

What is happening is that the self in def the_first_button_was_clicked(self): refers to the instance of MainWindow, which has no the_second_button_was_clicked method, hence the AttributeError exception.

The point is that both your functions are just functions, not methods (which are functions of an instance or a class): they are not members of the class.

Also note that creating a direct connection to the function will not work, as the self argument is only “created” when a function is a method.
As Heike pointed out in the comments, a possibility is to use lambda, which allows keeping an actual reference to the instance, while directly calling the function, which will be executed using the self argument provided, exactly as you did in run_the_first_button_was_clicked.

In the following examples I’m replacing self with mainWinInstance in order to make things more clear (which is the reason for which self should not be used in these cases).

def the_first_button_was_clicked(mainWinInstance):
    # we reference the function locally to this script, adding the "self" argument
    mainWinInstance.button2.clicked.connect(lambda: 
        the_second_button_was_clicked(mainWinInstance))

def the_second_button_was_clicked(mainWinInstance):
    # "self" (mainWinInstance) was manually added to the function arguments
    mainWinInstance.setWindowTitle("the_second_button_was_clicked")
    mainWinInstance.resize(600, 800)

Another possibility is to make the second function a member of the instance:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        # ...
        self.the_second_button_was_clicked = the_second_button_was_clicked

or:

def the_first_button_was_clicked(mainWinInstance):
    # ...
    mainWinInstance.the_second_button_was_clicked = the_second_button_was_clicked
    mainWinInstance.button2.clicked.connect(mainWinInstance.the_second_button_was_clicked)

In both cases the instance attribute has to be created before the connection (which also means before calling the first function in the first case).

Consider that this “monkey patching” approaches should only be used in special cases (mostly due to objects that cannot be subclassed because created autonomously), especially if done outside the class or even the script.

In most cases, what you’re doing is considered bad practice, and if you’re doing this with a class created on your own, there’s probably something really wrong in your implementation: you should better rethink your logic and implement everything within the class itself.

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement