I'd like to let the user pick one out of a list of dates from my calendar but prevent her from selecting any other date.
Based on this answer I managed to restrict the date range that can be selected but I couldn't exclude days in between.
import datetime as dt
import tkinter as tk
from tkinter import ttk
from tkcalendar import DateEntry
# fmt: off
dates = [
"2024-04-08", "2024-04-10", "2024-04-11", "2024-04-12",
"2024-04-15", "2024-04-16", "2024-04-17", "2024-04-18", "2024-04-19",
"2024-04-22",
"2024-05-21", "2024-05-22", "2024-05-23", "2024-05-24",
"2024-05-27", "2024-05-28", "2024-05-29", "2024-05-30", "2024-05-31",
"2024-06-03", "2024-06-04", "2024-06-05", "2024-06-07",
"2024-06-10", "2024-06-11", "2024-06-12", "2024-06-13", "2024-06-14",
]
# fmt: on
root = tk.Tk()
date_entry_var = tk.StringVar()
date_entry = DateEntry(
root,
textvariable=date_entry_var,
date_pattern="yyyy-mm-dd",
mindate=dt.date.fromisoformat(dates[0]),
maxdate=dt.date.fromisoformat(dates[-1]),
)
date_entry.pack()
root.mainloop()
Is there a way that makes it possible to select from a list of dates with a calendar widget?
tk
is required because I'm working with tk
in the entire app but answer do not need to use the tkcalendar
module if there are alternatives.
I got original code for Calendar and I added variable allowed_dates
which gets list of objects datetime
and it uses it to disable other dates which are not on this list (in range allowed_dates[0]
, allowed_dates[-1]
.
I tried to do similar code as for mindate
,maxdate
but it still may need changes in some functions
Main part is
# --- display
def _display_calendar(self):
# ... code ...
allowed_dates = self['allowed_dates']
if allowed_dates is not None:
import datetime as dt
one_day = dt.timedelta(days=1)
date = allowed_dates[0]
end_date = allowed_dates[-1]
while date <= end_date:
if date not in allowed_dates:
mi, mj = self._get_day_coords(date)
if mi is not None:
print(date, mi, mj, '!disabled')
self._calendar[mi][mj].state(['disabled'])
date += one_day
And this is how I use it - it needs to convert list of strings to list of objects datetime
I also added buttons which add/remove allowed dates to Calendar
or DateEntry
(based on variable widget
) and it also change mindate
,maxdate
.
Strange is when I added 2024-06-06
to Calendar
then it also added it to DateEntry
but it could't remove it. It also did't add date in DateEntry
when it is not in range mindate
, maxdate
.
import datetime as dt
import tkinter as tk
#from tkinter import ttk
#from tkcalendar import DateEntry
from mycalendar import Calendar
from mycalendar import DateEntry
# fmt: off
dates = [
"2024-04-08", "2024-04-10", "2024-04-11", "2024-04-12",
"2024-04-15", "2024-04-16", "2024-04-17", "2024-04-18", "2024-04-19",
"2024-04-22",
"2024-05-21", "2024-05-22", "2024-05-23", "2024-05-24",
"2024-05-27", "2024-05-28", "2024-05-29", "2024-05-30", "2024-05-31",
"2024-06-03", "2024-06-04", "2024-06-05", "2024-06-07",
"2024-06-10", "2024-06-11", "2024-06-12", "2024-06-13", "2024-06-14",
]
# fmt: on
root = tk.Tk()
dt_dates = [ dt.date.fromisoformat(date) for date in dates ]
# example mycalendar.Calendar
tk.Label(root, text="Calendar").pack()
cal = Calendar(
root,
date_pattern="yyyy-mm-dd",
mindate=dt_dates[0],
maxdate=dt_dates[-1],
allowed_dates=dt_dates,
locale="en_GB.utf-8", # to show it in English instead of my native Polish
# to make screenshot
)
cal.pack()
date_entry_var = tk.StringVar()
# example mycalendar.DateEntry
tk.Label(root, text="DateEntry").pack()
date_entry = DateEntry(
root,
textvariable=date_entry_var,
date_pattern="yyyy-mm-dd",
mindate=dt_dates[0],
maxdate=dt_dates[-1],
allowed_dates=dt_dates,
locale="en_GB.utf-8", # to show it in English instead of my native Polish
# to make screenshot
)
date_entry.pack()
# --- test buttons ---
#widget = cal
widget = date_entry
# ---
def show_allowed_dates():
for date in widget['allowed_dates']: # not `cal.allowed_dates`
print('allowed:', date)
button_show = tk.Button(root, text="Show Allowed Dates", command=show_allowed_dates)
button_show.pack(fill='x')
# ---
def add_allowed_date(date):
dt_date = dt.date.fromisoformat(date)
if dt_date not in widget['allowed_dates']:
print('add allowed:', date)
widget['allowed_dates'].append(dt_date)
# other methods
#widget['allowed_dates'] += [append(dt.date.fromisoformat(date)]
#widget['allowed_dates'].extend( [append(dt.date.fromisoformat(date)] )
widget['allowed_dates'] = sorted(widget['allowed_dates'])
# what if new date is not in `mindate`, `maxdate` ???
if widget['allowed_dates'][0] < widget['mindate']:
widget['mindate'] = widget['allowed_dates'][0]
if widget['allowed_dates'][-1] > widget['maxdate']:
widget['maxdate'] = widget['allowed_dates'][-1]
# redraw it
if widget == cal:
widget._display_calendar()
for date in ('2024-06-06', '2024-06-26', '2024-07-10'):
button_add = tk.Button(root, text=f"Add Allowed Date: {date}", command=lambda x=date:add_allowed_date(x))
button_add.pack(fill='x')
# ---
def remove_allowed_date(date):
dt_date = dt.date.fromisoformat(date)
if dt_date in widget['allowed_dates']:
print('remove allowed:', date)
widget['allowed_dates'].remove(dt_date)
# what if removed date is `mindate` or `maxdate` ???
if widget['mindate'] < widget['allowed_dates'][0]:
widget['mindate'] = widget['allowed_dates'][0]
if widget['maxdate'] > widget['allowed_dates'][-1]:
widget['maxdate'] = widget['allowed_dates'][-1]
# redraw it
if widget == cal:
widget._display_calendar()
for date in ('2024-06-06', '2024-06-26', '2024-07-10'):
button_remove = tk.Button(root, text=f"Remove Allowed Date: {date}", command=lambda x=date:remove_allowed_date(x))
button_remove.pack(fill='x')
# ---
root.mainloop()
If I put original DateEntry without from tkcalendar.calendar_ import Calendar
in the same file mycalendar.py
then it uses my calendar.
And it needs only to use mycalendar.DateEntry
instead of original tkCalendar.DateEntry
with allowed_dates
Full code for Calendar
is too long to show it here so I put it in GitHub