Content description:
This is the date picker class for the project.
You are free to use it in your projects (MIT license).
The date picker window is an instance of the tk.Toplevel class. I blocked the widgets in the parent window until the date picker window was closed (grab_set() function). The following lines describe the position of the date picker window relative to its parent window. Then an instance of the Calendar class is set up and dp_frame variable was set to hold an instance of the tk.Frame class. Finally, the create_cal_ui() method arranges the widgets in the window.
class DatePicker():
"""Date picker module for tkinter.
Arguments:
root - parent window
date_entry - parent class tk.Entry
date_strf - date format string
"""
def __init__(self, root, date_entry, date_strf):
self.root = root
self.date_entry = date_entry
self.date_strf = date_strf
self.top_level = tk.Toplevel(self.root)
self.top_level.grab_set()
self.top_level.title('Date picker')
x = self.root.winfo_rootx()
y = self.root.winfo_rooty()
width = self.root.winfo_width()
height = self.root.winfo_height()
self.top_level.geometry(
'+{}+{}'.format(x + int(width / 2), y + int(height / 2)))
self.c = calendar
self.cal = self.c.Calendar(self.c.firstweekday())
self.dp_frame = None
self.create_cal_ui()
The create_cal_ui() method takes year and month as arguments - by default: current year and current month. The monthdayscalendar() function from the Calendar class then retrieves the list of days in the month. Each week is a separate list, so you have a list of lists. Days not belonging to the current month are marked as 0 (zero). Month_txt represents the month string, and self.month contains the integer value of the month.
def create_cal_ui(self, year=datetime.today().year, month=datetime.today().month):
"""Create date picker UI.
Arguments:
year - selected year
month - selected month
Returns:
None
"""
mc = self.cal.monthdayscalendar(year, month)
self.month = month
self.year = year
month_txt = self.c.month_name[month]
self.date_str = f'{month_txt} {year}'
if self.dp_frame is not None:
self.dp_frame.destroy()
self.dp_frame = tk. Frame(self.top_level)
self.dp_frame.grid(column=0, row=0)
self.prev_img = tk.PhotoImage(file='Resources/prev.gif')
self.next_img = tk.PhotoImage(file='Resources/next.gif')
prev_btn = tk.Button(self.dp_frame, image=self.prev_img, relief='flat')
prev_btn.bind(
'<Button-1>', lambda event: self.set_date(event, 'prev_btn'))
prev_btn.grid(row=0, column=0)
next_btn = tk.Button(self.dp_frame, image=self.next_img, relief='flat')
next_btn.bind(
'<Button-1>', lambda event: self.set_date(event, 'next_btn'))
next_btn.grid(row=0, column=6)
self.date_lbl = tk.Label(self.dp_frame, text=self.date_str,
font=12)
self.date_lbl.grid(row=0, column=1, columnspan=5, sticky='WE')
week_names = self.c.day_abbr
for i, name in enumerate(week_names):
label = tk.Label(self.dp_frame, text=name).grid(column=i, row=1)
col = 0
row = 2
for week in mc:
for day in week:
state = 'normal'
if day == 0:
state = 'disabled'
day = ''
day = str(day)
button = tk.Button(self.dp_frame, text=day,
relief='flat', state=state,
command=partial(self.get_date, day))
button.grid(column=col, row=row)
col += 1
row += 1
col = 0
If the month is changed, the current frame will be destroyed. The prev_btn and next_btn buttons change the month, and the date_lbl label shows the selected month. To change the month, you need to bind a method to the button. The event argument does not contain sender name information. So I created a lambda function that passes an additional string argument to the set_date() method.
When the button is pressed, the get_date() method is fired. I used a partial function from the functools module. The Lambda function does not work in this case due to late binding.
set_date method code:
def set_date(self, arg, sender):
if sender == 'prev_btn':
self.month -= 1
if self.month < 1:
self.month = 12
self.year -= 1
if sender == 'next_btn':
self.month += 1
if self.month > 12:
self.month = 1
self.year += 1
self.create_cal_ui(self.year, self.month)
get_date method code:
def get_date(self, day):
day = int(day)
self.date_entry.delete(0, tk.END)
d = datetime(self.year, self.month, day)
self.date_entry.insert(0, d.strftime(self.date_strf))
self.top_level.destroy()
Finally you get a datetime instance with formatting and the date picker is destroyed.
