I’m using Python 3.x to create a multilingual “calendar” widget that has separate spinboxes for year, month, day, etc. A requirement is that whatever widget is used must NEVER hide any part of the GUI at any time, thus ruling out drop down lists etc. , making spinboxes an obvious choice.
If anyone knows where public-domain source code for a pure python/tkinter “date & time entry” widgit (ideally handling leap year and all of the end-of-month cases) can be found, pointing me to that repository would be appreciated.
The closest solution I could find was this OptionMenu solution (Change OptionMenu based on what is selected in another OptionMenu ) but it can’t be used due to the “don’t hide” requirement. Using that as inspiration (and after reviewing the Spinbox documentation at http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/spinbox.html) I came up with the code below. To keep things simple I’ve removed my code that handles leap year and end-of-month issues. The code works except for two problems:
Problem 1 – I can’t get “DaySpinBox” to dynamically adjust its’ range to set itself to the month in the “MonthSpinBox”:
According to how I read Mark Lutz’s “Programming Python” book (third edition, pages 383 & 384) I should be able to dynamically set the maximum/last day of DaySpinBox (by using “values=” or the ”to=” and ”from_=” options) to match the month appearing in MonthSpinBox. I’m tracing the MonthSpinBox variable (“SelectedMonth”) and, as per the book, I’ve tried updating DaySpinBox using these approaches:
1 - Using “to= SelectedMonth”. Some attempts at this appear as comments in the call that creates DaySpinBox. (I’m new to lambdas, perhaps I am not using them correctly?),
2 - Using “DaySpinBox[ ‘to’ ] = SelectedMonth ”, and
3 - Using “DaySpinBox.config( to = SelectedMonth )” .
I’ve also tried using “values=” with all approaches without success (the “to=....” option is preferred).
Question 1: Can Spinboxes be set up to use dynamic ranges or, as I’m starting to suspect, are their ranges unchangeable after the Spinbox has been created?
Question 2: After much web searching I haven’t found anything that explicitly states which Spinbox parameters (the “to=” , “from=” and “value=” keywords) can be dynamically changed and which ones cannot – where can I find this information (for all widget types) ?
Problem 2 – MonthSpinBox is always initialized to January instead of the current month
I’m using the “textvariable” keyword to initialize the year, month and day spinboxes to “today”. This works for YearSpinBox and DaySpinBox but not for MonthSpinBox. (Annoyingly I think MonthSpinBox was working but I broke it trying to fix DaySpinBox). The only obvious difference is that Year and Day spinboxes use integers while the Month spinbox uses strings. Any suggestions?
For both problems I considered the possibility of a LEGB issue but no functions are nested so variable hiding shouldn’t be an issue - unless one of my variables is duplicating and hiding one defined in tkinter, etc.
What am I missing?
BTW, I know about the pros/cons of using globals, also about PEP8 (http://www.python.org/dev/peps/pep-0008/) - see the second bullet in the table of contents. ;>)
Thanks.
from tkinter import *
from tkinter import ttk
from datetime import datetime
from collections import OrderedDict
root = Tk() # put here so IntVar (etc.) is useable during initialization
StartUpDateTime = datetime.now()
DefaultYear = StartUpDateTime.year
DefaultMonthNumber = StartUpDateTime.month
DefaultDayOfMonth = StartUpDateTime.day
# These are only used to build the Month name & length dictionary
YearAndMonthLengths = [ 365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
EnglishMonthNames = ( 'Entire Year', 'January', 'February', 'March',
'April', 'May', 'June', 'July', 'August', 'September',
'October', 'November', 'December' )
FrenchMonthNames = ( 'année entière', 'janvier', 'février', 'mars',
'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
'octobre', 'novembre', 'décembre' )
#SpanishMonthNames = ....
#ItalianMonthNames = ....
Month_Names = FrenchMonthNames
Month_Names = EnglishMonthNames # comment out this line to use French
# Create dictionary containing month names that appear in the Month spinbox.
# OrderedDict used because Month name (=key) field must be language-independent.
DaysInMonth = OrderedDict()
for i in range( 0, len( Month_Names ) ) :
DaysInMonth[ Month_Names[ i ] ] = YearAndMonthLengths[ i ]
DefaultMonthName = Month_Names[ DefaultMonthNumber ]
DefaultMonthLength = DaysInMonth[ DefaultMonthName ]
# Initialize the Spinbox interface variables to todays date
SelectedYear = IntVar( value = DefaultYear )
SelectedMonthName = StringVar( value = DefaultMonthName )
SelectedMonthLength = IntVar( value = DefaultMonthLength )
SelectedDay = IntVar( value = DefaultDayOfMonth )
#-------------------------------------------------------------------------------
def GetChosenMonthLength( *args ) :
global SelectedMonthLength
SelectedMonthLength.set( DaysInMonth[ SelectedMonthName.get() ] )
return
#-------------------------------------------------------------------------------
mainframe = Frame( root )
mainframe.grid( row = 0, column = 0 )
# Show today's date. If it's July 17th, 2023 the spinboxes must be initialized
# to "July" ("julliet" if using French), "17" and "2023".
YearSpinBox = Spinbox( mainframe,
from_ = 2000, to = 2050,
textvariable = SelectedYear,
wrap = TRUE, state = 'readonly', width = 5 )
MonthSpinBox = Spinbox( mainframe,
values = tuple( DaysInMonth.keys() )[1:],
textvariable = SelectedMonthName,
wrap = TRUE, state = 'readonly', width = 10 )
DaySpinBox = Spinbox( mainframe,
from_ = 1,
to = SelectedMonthLength.get(),
# to = DaysInMonth[ SelectedMonthName.get() ],
# values = tuple( str( i ) for i in range( 1, SelectedMonthLength.get() + 1 ) ),
# values = lambda : tuple( str( i ) for i in range( 1, DaysInMonth[ SelectedMonthName.get() ] + 1 ) )
textvariable = SelectedDay,
wrap = TRUE, state = 'readonly', width = 16
)
MonthSpinBox.grid( row = 0, column = 0 )
DaySpinBox.grid( row = 0, column = 1 )
YearSpinBox.grid( row = 0, column = 2 )
SelectedMonthName.trace( 'w', GetChosenMonthLength )
mainloop()
BTW, the sample code omits the debugging code that proved all Spinbox interface variables are being updated correctly - including the variable that contains the last day of the changed/new month (which gets “sent” to the Day Spinbox using the “TO=” keyword).