Reputation: 275
I am trying to make a program that adds one month to a date variable every time the user presses a button.
Here's my code:
import datetime
from dateutil.relativedelta import relativedelta
from tkinter import *
gameDate = datetime.datetime(1985, 9, 1)
def nextMonth(gameDate, worldDateLabel):
gameDate = gameDate + relativedelta(months=1)
worldDateLabel.config(text=gameDate.strftime("%B %Y"))
return gameDate
window = Tk()
worldDateLabel = Label(window, text=gameDate.strftime("%B %Y"))
worldDateLabel.grid(row=0, column=0)
next_btn = Button(window, text="Next Month", command=lambda:nextMonth(gameDate, worldDateLabel))
next_btn.grid(row=1, column=0)
window.mainloop()
The date updates the first time I click on the button but then it stays to October and doesn't move to the next month.
I think it's because the program updates only the local variable inside the function instead of the main gameDate
one. I tried to fix that with the return at the end but that doesn't help.
I am not sure what I am doing wrong…
P.S.: I am trying not to use global
.
Upvotes: 2
Views: 533
Reputation: 123393
You most definitely don't need to use a global in this case. The way to avoid or at the very least minimize the use of them (which are known to be bad, even harmful ) in most programs, is to simply follow the object-oriented programming paradigm or methodology, also known as OOP.
Here's how to make a tkinter app based on applying its principles to the code in your question. I've also made an effort to avoid hardcoding many constant values into it to make it more flexible.
I noticed that you're using mixedCase
variable and function names in your code, which I have left in for the most part, but strongly encourage you should start following the PEP 8 - Style Guide for Python Code recommendations, especially those concerning naming styles, as I think it makes code a lot easier to read.
import datetime
from dateutil.relativedelta import relativedelta
import tkinter as tk
class TkinterApp:
def __init__(self, year, month, day, time_delta, date_format):
self.start_date = datetime.datetime(year, month, day)
self.time_delta = time_delta
self.date_format = date_format
self.window = tk.Tk()
self.window.grid_columnconfigure((0), weight=1) # Center column in window.
self.createWidgets()
self.window.mainloop()
def createWidgets(self):
self.gameDate = self.start_date
self.textVar = tk.StringVar(value=self.gameDate.strftime(self.date_format))
worldDateLabel = tk.Label( textvariable=self.textVar)
worldDateLabel.grid(row=0, column=0)
next_btn = tk.Button(self.window, text='Next Month', command=self.nextMonth)
next_btn.grid(row=1, column=0)
def nextMonth(self):
self.gameDate += self.time_delta
self.textVar.set(self.gameDate.strftime(self.date_format))
if __name__ == '__main__':
TkinterApp(1985, 9, 1, relativedelta(months=1), '%B %Y')
Upvotes: 0
Reputation: 77337
You need global in this case. The general rule to avoid globals has many exceptions. The problem with global variables is that it tends to couple different functions in a non-obvious way that may be better off being independent of each other. Think easier programming and fewer bugs.
The simpler the script, the less this is a concern. In your case, you have a program with a single main event loop, having a few extra global variables with that is okay. But as your code grows and you find different uses for that nextMonth
function, that's when it gets to be a problem.
One way to solve the problem is with a class. A class instance method remembers its instance, so you could define a class that does useful game date stuff. The class instance itself is still global, but then so is the Tk window. But you have now encapsulated the functionality and can use that class elsewhere if you want.
import datetime
from dateutil.relativedelta import relativedelta
from tkinter import *
class GameDate:
def __init__(self):
self.gameDate = datetime.datetime(1985, 9, 1)
def nextMonth(self, worldDateLabel):
self.gameDate = self.gameDate + relativedelta(months=1)
worldDateLabel.config(text=self.gameDate.strftime("%B %Y"))
window = Tk()
date = GameDate()
worldDateLabel = Label(window, text=gameDate.strftime("%B %Y"))
worldDateLabel.grid(row=0, column=0)
next_btn = Button(window, text="Next Month", command=lambda:date.nextMonth(worldDateLabel))
next_btn.grid(row=1, column=0)
window.mainloop()
Upvotes: 1