Create a themed date chooser
See original GitHub issue@daniilS, I’m working on a themed date chooser which has 3-components…
- a
DateChooser
class that creates theTopLevel
window. - an
ask_date
function that opens theDateChooser
and returns adatetime
object. - a
DateEntry
class, that works like an entry field with a date chooser button attached.
I expect all of these to be used independently based on the needs of the situation.
A few more things I still need to work out…
- When using the
DateEntry
, the date chooser should use the date in the entry field as the default, so that if the button is invoked again, the calendar in the popup reflects the new date, not the current date as it does now. - I need to implement the themes for the various components so that they are changeable. Currently they are fixed to the primary theme color, and not inside of the class. Possibly I can subclass the ttk style and have something unique to the calendar widget, but I’ll think about it.
Below is the proto-type code for this widget. It’s not clean by any stretch, but it works.
import calendar
from datetime import datetime
from tkinter import IntVar, Toplevel, StringVar
from tkinter import ttk
from tkinter.ttk import Frame, Entry
from ttkbootstrap import Style
def ask_date(parent):
outvar = StringVar()
dp = DateChooserPopup(parent, outvar)
if outvar.get():
return datetime.strptime(outvar.get(), '%Y-%m-%d')
class DateEntry(Frame):
def __init__(self, parent=None, dateformat='%Y-%m-%d', **kw):
super().__init__(master=parent, **kw)
self.parent = parent
self.dateformat = dateformat
self.entry = Entry(self)
self.entry.pack(side='left', fill='x', expand='yes')
self.button = ttk.Button(self, text='📅', command=self.on_date_select)
self.button.pack(side='left')
# insert default value
self.entry.insert('end', datetime.today().strftime(dateformat))
def on_date_select(self):
date = ask_date(self.entry)
self.entry.delete('0', 'end')
self.entry.insert('end', date.strftime(self.dateformat))
class DateChooserPopup(Toplevel):
def __init__(self, parent, variable=None, **kw):
super().__init__()
self.withdraw()
self.transient(parent)
self.parent = parent
self.update_idletasks() # actualize geometry
x = parent.winfo_rootx() + parent.winfo_width()
y = parent.winfo_rooty() + parent.winfo_height()
self.overrideredirect(True)
self.resizable(False, False)
self.today = datetime.today()
self.date = datetime.today()
self.calendar = calendar.Calendar()
self.cframe = ttk.Frame(self, padding=10, borderwidth=1, relief='raised')
self.tframe = ttk.Frame(self.cframe)
self.dframe = None
self.titlevar = StringVar(value=f'{self.date.strftime("%B %Y")}')
self.datevar = IntVar()
self.variable = variable or StringVar()
self.setup()
self.geometry(f'+{x}+{y}')
self.wait_window()
def next_month(self):
year, month = calendar._nextmonth(self.date.year, self.date.month)
self.date = datetime(year=year, month=month, day=1)
self.dframe.destroy()
self.draw_calendar()
def prev_month(self):
year, month = calendar._prevmonth(self.date.year, self.date.month)
self.date = datetime(year=year, month=month, day=1)
self.dframe.destroy()
self.draw_calendar()
def setup(self):
"""Setup the calendar widget"""
parent = self.parent
self.cframe.pack(fill='both')
self.tframe.pack(fill='x')
self.calendar.setfirstweekday(calendar.SUNDAY)
self.draw_titlebar()
self.draw_calendar()
self.deiconify()
self.focus_set()
def date_select(self, index):
row, col = index
date = self.monthdates[row][col].strftime('%Y-%m-%d')
self.variable.set(date)
self.after(10, self.destroy)
def draw_titlebar(self):
"""Create the title bar"""
# previous month button
self.btn_prev = ttk.Button(self.tframe, text='«', style='primary.Link.TButton', command=self.prev_month)
self.btn_prev.pack(side='left')
# month and year title
self.title_label = ttk.Label(self.tframe, textvariable=self.titlevar, anchor='center')
self.title_label.pack(side='left', fill='x', expand='yes')
# next month button
self.btn_next = ttk.Button(self.tframe, text='»', style='primary.Link.TButton', command=self.next_month)
self.btn_next.pack(side='left')
def draw_calendar(self):
self.titlevar.set(f'{self.date.strftime("%B %Y")}')
self.monthdays = self.calendar.monthdayscalendar(self.date.year, self.date.month)
self.monthdates = self.calendar.monthdatescalendar(self.date.year, self.date.month)
self.dframe = ttk.Frame(self.cframe)
self.dframe.pack(fill='both', expand='yes')
# days of the week header
for i, wd in enumerate(['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']):
wd_lbl = ttk.Label(self.dframe, text=wd, anchor='center', padding=(0, 5, 0, 10), width=4)
wd_lbl.grid(row=0, column=i, sticky='nswe')
# calendar days
for row, wk in enumerate(self.monthdays):
self.dframe.rowconfigure(row, weight=1)
for col, day in enumerate(wk):
self.dframe.columnconfigure(col, weight=1)
if day == 0:
ttk.Label(self.dframe, text=self.monthdates[row][col].day, anchor='center',
style='secondary.TLabel',
padding=(0, 0, 0, 10)).grid(row=row + 1, column=col, sticky='nswe')
else:
if all([
day == self.today.day,
self.date.month == self.today.month,
self.date.year == self.today.year]):
day_style = 'success.Toolbutton'
else:
day_style = 'calendar.primary.Outline.Toolbutton'
rb = ttk.Radiobutton(self.dframe, variable=self.datevar, value=day, text=day, style=day_style,
padding=(0, 0, 0, 10), command=lambda x=row, y=col: self.date_select([x, y]))
rb.grid(row=row + 1, column=col, sticky='nswe')
def define_style(self):
pass
if __name__ == '__main__':
# TODO setup the styling in the __init__ file, and setup the class so that it can be easilily modified.
# TODO add documentation to all classes and methods.
# TODO make sure the date chooser defaults to the entry field value instead of the current date.
style = Style()
style.configure('calendar.primary.Outline.Toolbutton', lightcolor='#fff', darkcolor='#fff', bordercolor='#fff')
style.map('calendar.primary.Outline.Toolbutton',
darkcolor=[
('disabled', '#fff'),
('pressed', '!disabled', '#273747'),
('selected', '!disabled', '#273747'),
('hover', '!disabled', '#2c3e50')],
bordercolor=[
('disabled', '#fff'),
('pressed', '!disabled', '#273747'),
('selected', '!disabled', '#273747'),
('hover', '!disabled', '#2c3e50')],
lightcolor=[
('disabled', '#fff'),
('pressed', '!disabled', '#273747'),
('selected', '!disabled', '#273747'),
('hover', '!disabled', '#2c3e50')])
root = style.master
picker = DateEntry(root, dateformat='%B %d, %Y', padding=20)
picker.pack()
root.mainloop()
Issue Analytics
- State:
- Created 2 years ago
- Reactions:2
- Comments:17 (12 by maintainers)
Top Results From Across the Web
Date Idea Generator - 580+ Date Ideas They'll Love
Try our free dating tool with 580+ date ideas to find the perfect date. Based on your budget, lifestyle, activity, and more!
Read more >Date Ideas Generator - The Calculator
This date ideas generator will give you some possibilities on where to take your date out, tailored for your needs and based on...
Read more >Date designs, themes, templates and downloadable ...
Date. 5,519 inspirational designs, illustrations, and graphic elements from the world's best designers. Want more inspiration? Browse our search results.
Read more >Date Picker - Form Widgets
Our free Date Picker widget lets users quickly select dates from a collapsible calendar. Add it to your order form, reservation form, booking...
Read more >Date pickers - Material Design
Date pickers let users select dates using a dialog. ... dates that don't require a calendar view, make the mobile date input picker...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Looks a lot better.
Colours look good, but I would personally use the default colour for the numbers in the calendar.