Search code examples
pythonpyqtglobal-variablespyside6pyqt6

Problem with function duplication and global variables in PySide6 app, a function to print day and month is duplicated when button pressed


I'm a beginner and I know there is something I missed but I don't know exactly what, So I have a PySide6 app, and I created a function to generate a calendar in a QTableWidget using calendar module in python all worked fine but the problem came when I tried to add a navigation buttons to get next and previous month: This is my function:

import sys
import os
import platform
import datetime as dt
import time
import calendar

from PySide6 import *
from PySide6 import QtGui
from PySide6 import QtWidgets
from PySide6 import QtCore
from PySide6.QtGui import QColor

from functools import partial

yy = int(dt.datetime.now().strftime("%Y"))
mm = int(dt.datetime.now().strftime("%m"))

class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        global widgets
        widgets = self.ui

        # Calender generator
        self.calender_gen(mm,yy)



    def calender_gen(self, mm_g, yy_g):
        # Creat table rows and columns
        widgets.tableWidget_3.setRowCount(5)
        widgets.tableWidget_3.setColumnCount(7)

        # Table header labels
        week_list = ["Sat","Sun","Mon","Tue","Wed","Thu","Fri"]
        widgets.tableWidget_3.setHorizontalHeaderLabels(week_list)

        # Start inserting days of the month into the table
        row = 0
        col = 0
        for week in calendar.monthcalendar(yy_g,mm_g):
            for day in week:
                if day == 0:
                    widgets.tableWidget_3.setItem(row,col,QTableWidgetItem(" "))
                else:
                    widgets.tableWidget_3.setItem(row,col,QTableWidgetItem(str(day)))
                col += 1
            row += 1
            col = 0
        print(mm_g,yy_g)
        # Connect Buttons to function
        widgets.pushButton_3.clicked.connect(partial(self.next_calendar_butt,mm_g,yy_g))
        widgets.pushButton_2.clicked.connect(partial(self.prev_calendar_butt,mm_g,yy_g))

    def next_calendar_butt(self,mm_new, yy_new):
        mm_new += 1
        if mm_new > 12:
            mm_new = 1
            yy_new += 1

        widgets.tableWidget_3.setRowCount(0)
        widgets.tableWidget_3.setColumnCount(0)
        self.calender_gen(mm_new,yy_new)

    def prev_calendar_butt(self,mm_g_new,yy_g_new):
        mm_g_new -= 1
        if mm_g_new == 0:
            mm_g_new = 12
            yy_g_new -= 1

        widgets.tableWidget_3.setRowCount(0)
        widgets.tableWidget_3.setColumnCount(0)
        self.calender_gen(mm_g_new,yy_g_new)

When I run the app the calendar shows in the table as in the image image of the GUI table

The Console output Console prints 11 2021

When I click on pushButton_3 for first time click it works normally and prints '12 2021' in the console console prints 12 2021 But after I click on the same button again it starts duplication: console prints '12 2021 1 2022' console prints 12 2021 1 2022 If I click again it prints '12 2021 1 2022 1 2022 2 2022' as in the image enter image description here with every click it duplicates more where it should only print one statement i.e '2 2022'

I tried to move the below lines out of the calendar_gen() function but I coudn't deliver the parameters, even after declaring global variables and assign them to the parameters :

widgets.pushButton_3.clicked.connect(partial(self.next_calendar_butt,mm_g,yy_g))
widgets.pushButton_2.clicked.connect(partial(self.prev_calendar_butt,mm_g,yy_g))

I've tried to do this: Inside the calendar_gen() function I declared global variables and assigned them to the function parameters in order to create something like a global parameter

global var_mm
global var_yy

var_mm = mm_g
var_yy = yy_g

then in the init(self) function I put those 2 lines:

def __init__(self):
    widgets.pushButton_3.clicked.connect(partial(self.next_calendar_butt,var_mm,var_yy))
    widgets.pushButton_2.clicked.connect(partial(self.prev_calendar_butt,var_mm,var_yy))

But this didn't work in the console it prints '11 2021' when I run the app then when I click on pushButton_3 it prints '12 2021' and when I click again on it, it prints '12 2021' again and so on Same with the other button it prints '10 2021' again and again


Solution

  • Qt signal connections are not exclusive (by default), and a signal can be connected to the same function more than once.

    Since you're connecting the clicked signals of the buttons in calender_gen, everytime that function is called you're adding a further connection to those signals. The result is that the connected functions will be called as many time as they have been connected every time the signal is emitted.

    A proper solution is to connect to the functions that would switch the month and keep a reference to the current month for the "new" month computation.

    Since the functions are almost identical, it's better to group them in a unique function, and then connect the signals to separate functions that would eventually call that former function with an appropriate parameter:

    class MainWindow(QMainWindow):
        def __init__(self):
            # ...
            self.calender_gen(mm,yy)
    
            self.pushButton_2.clicked.connect(self.prev_month)
            self.pushButton_3.clicked.connect(self.next_month)
    
        def calender_gen(self, mm_g, yy_g):
            # Creat table rows and columns
            self.tableWidget_3.setRowCount(5)
            self.tableWidget_3.setColumnCount(7)
    
            # Table header labels
            week_list = ["Sat","Sun","Mon","Tue","Wed","Thu","Fri"]
            self.tableWidget_3.setHorizontalHeaderLabels(week_list)
    
            # Start inserting days of the month into the table
            row = 0
            col = 0
            for week in calendar.monthcalendar(yy_g,mm_g):
                for day in week:
                    if day == 0:
                        self.tableWidget_3.setItem(row,col,QTableWidgetItem(" "))
                    else:
                        self.tableWidget_3.setItem(row,col,QTableWidgetItem(str(day)))
                    col += 1
                row += 1
                col = 0
    
            self.current_month = mm_g
            self.current_year = yy_g
    
        def prev_month(self):
            self.step_month(-1)
    
        def next_month(self):
            self.step_month(1)
    
        def step_month(self, delta):
            mm_new = self.current_month + delta
            mm_year = self.current_year
            if mm_new > 12:
                mm_new = 1
                mm_year += 1
            elif mm_new < 1:
                mm_new = 12
                mm_year -= 1
    
            self.calender_gen(mm_new, mm_year)
    

    Obviously, properly implementing QCalendarWidget might be much simpler, as it already provides most of the functionalities.