pythontkintertk-toolkittkcalendar

Is it possible to embed custom text in python tkcalendar's date


I am creating a shift table by tkcalendar.

I would like to make the selected dates show their own timetable, as illustrated in the picture.

Is it possible to achieve this by any methods provided by tkcalendar? Or the only way I can do this is created a custom calendar from zero? I went through the document, but hardly finding something useful.

NOTE: I don't want tooltip(pop-up text)

Demo Image


Solution

  • It is not possible from tkcalendar's methods to display text below the day number in the Calendar. However, you don't have to start from scratch, you can create an Agenda class inheriting from Calendar and only rewrite the methods displaying the events to put them inside the day's label instead of in a popup.

    To be precise, 3 methods need to be modified: _display_days_without_othermonthdays(), _display_days_with_othermonthdays() and _show_event(), see code below. The code might seem long but I actually only modified a couple of lines in each methods compared to there original version in tkcalendar.

    from tkcalendar import Calendar
    
    class Agenda(Calendar):
    
        def __init__(self, master=None, **kw):
            Calendar.__init__(self, master, **kw)
            # change a bit the options of the labels to improve display
            for i, row in enumerate(self._calendar):
                for j, label in enumerate(row):
                    self._cal_frame.rowconfigure(i + 1, uniform=1)
                    self._cal_frame.columnconfigure(j + 1, uniform=1)
                    label.configure(justify="center", anchor="n", padding=(1, 4))
    
        def _display_days_without_othermonthdays(self):
            year, month = self._date.year, self._date.month
    
            cal = self._cal.monthdays2calendar(year, month)
            while len(cal) < 6:
                cal.append([(0, i) for i in range(7)])
    
            week_days = {i: 'normal.%s.TLabel' % self._style_prefixe for i in range(7)}  # style names depending on the type of day
            week_days[self['weekenddays'][0] - 1] = 'we.%s.TLabel' % self._style_prefixe
            week_days[self['weekenddays'][1] - 1] = 'we.%s.TLabel' % self._style_prefixe
            _, week_nb, d = self._date.isocalendar()
            if d == 7 and self['firstweekday'] == 'sunday':
                week_nb += 1
            modulo = max(week_nb, 52)
            for i_week in range(6):
                if i_week == 0 or cal[i_week][0][0]:
                    self._week_nbs[i_week].configure(text=str((week_nb + i_week - 1) % modulo + 1))
                else:
                    self._week_nbs[i_week].configure(text='')
                for i_day in range(7):
                    day_number, week_day = cal[i_week][i_day]
                    style = week_days[i_day]
                    label = self._calendar[i_week][i_day]
                    label.state(['!disabled'])
                    if day_number:
                        txt = str(day_number)
                        label.configure(text=txt, style=style)
                        date = self.date(year, month, day_number)
                        if date in self._calevent_dates:
                            ev_ids = self._calevent_dates[date]
                            i = len(ev_ids) - 1
                            while i >= 0 and not self.calevents[ev_ids[i]]['tags']:
                                i -= 1
                            if i >= 0:
                                tag = self.calevents[ev_ids[i]]['tags'][-1]
                                label.configure(style='tag_%s.%s.TLabel' % (tag, self._style_prefixe))
                            # modified lines:
                            text = '%s\n' % day_number + '\n'.join([self.calevents[ev]['text'] for ev in ev_ids])
                            label.configure(text=text)
                    else:
                        label.configure(text='', style=style)
    
        def _display_days_with_othermonthdays(self):
            year, month = self._date.year, self._date.month
    
            cal = self._cal.monthdatescalendar(year, month)
    
            next_m = month + 1
            y = year
            if next_m == 13:
                next_m = 1
                y += 1
            if len(cal) < 6:
                if cal[-1][-1].month == month:
                    i = 0
                else:
                    i = 1
                cal.append(self._cal.monthdatescalendar(y, next_m)[i])
                if len(cal) < 6:
                    cal.append(self._cal.monthdatescalendar(y, next_m)[i + 1])
    
            week_days = {i: 'normal' for i in range(7)}  # style names depending on the type of day
            week_days[self['weekenddays'][0] - 1] = 'we'
            week_days[self['weekenddays'][1] - 1] = 'we'
            prev_m = (month - 2) % 12 + 1
            months = {month: '.%s.TLabel' % self._style_prefixe,
                      next_m: '_om.%s.TLabel' % self._style_prefixe,
                      prev_m: '_om.%s.TLabel' % self._style_prefixe}
    
            week_nb = cal[0][1].isocalendar()[1]
            modulo = max(week_nb, 52)
            for i_week in range(6):
                self._week_nbs[i_week].configure(text=str((week_nb + i_week - 1) % modulo + 1))
                for i_day in range(7):
                    style = week_days[i_day] + months[cal[i_week][i_day].month]
                    label = self._calendar[i_week][i_day]
                    label.state(['!disabled'])
                    txt = str(cal[i_week][i_day].day)
                    label.configure(text=txt, style=style)
                    if cal[i_week][i_day] in self._calevent_dates:
                        date = cal[i_week][i_day]
                        ev_ids = self._calevent_dates[date]
                        i = len(ev_ids) - 1
                        while i >= 0 and not self.calevents[ev_ids[i]]['tags']:
                            i -= 1
                        if i >= 0:
                            tag = self.calevents[ev_ids[i]]['tags'][-1]
                            label.configure(style='tag_%s.%s.TLabel' % (tag, self._style_prefixe))
                        # modified lines:
                        text = '%s\n' % date.day + '\n'.join([self.calevents[ev]['text'] for ev in ev_ids])
                        label.configure(text=text)
    
        def _show_event(self, date):
            """Display events on date if visible."""
            w, d = self._get_day_coords(date)
            if w is not None:
                label = self._calendar[w][d]
                if not label.cget('text'):
                    # this is an other month's day and showothermonth is False
                    return
                ev_ids = self._calevent_dates[date]
                i = len(ev_ids) - 1
                while i >= 0 and not self.calevents[ev_ids[i]]['tags']:
                    i -= 1
                if i >= 0:
                    tag = self.calevents[ev_ids[i]]['tags'][-1]
                    label.configure(style='tag_%s.%s.TLabel' % (tag, self._style_prefixe))
                # modified lines:
                text = '%s\n' % date.day + '\n'.join([self.calevents[ev]['text'] for ev in ev_ids])
                label.configure(text=text)
    
    if __name__ == '__main__':
        import tkinter as tk
        root = tk.Tk()
        root.geometry("800x500")
        agenda = Agenda(root, selectmode='none')
        date = agenda.datetime.today() + agenda.timedelta(days=2)
        agenda.calevent_create(date, 'Hello World', 'message')
        agenda.calevent_create(date, 'Reminder 2', 'reminder')
        agenda.calevent_create(date + agenda.timedelta(days=-7), 'Reminder 1', 'reminder')
        agenda.calevent_create(date + agenda.timedelta(days=3), 'Message', 'message')
        agenda.calevent_create(date + agenda.timedelta(days=3), 'Another message', 'message')
    
        agenda.tag_config('reminder', background='red', foreground='yellow')
    
        agenda.pack(fill="both", expand=True)
        root.mainloop()    
    

    screenshot