question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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 the TopLevel window.
  • an ask_date function that opens the DateChooser and returns a datetime 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.

date-picker

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:closed
  • Created 2 years ago
  • Reactions:2
  • Comments:17 (12 by maintainers)

github_iconTop GitHub Comments

1reaction
israel-dryercommented, May 9, 2021

Looks a lot better.

image

1reaction
daniilScommented, May 9, 2021

Colours look good, but I would personally use the default colour for the numbers in the calendar.

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found