Moe Jan
Moe Jan

Reputation: 369

How to set ttk calendar programmatically

I am using this ttk calendar in my application.

What I want to do is set the calendar using a datetime.date instance so when the calendar appears the specified date is highlighted.

I thought I could go through the _show_selection method with manual text and bbox args. To test this idea, I placed this line at the end of the __init__ method:

self._show_selection('%02d'%16,(42,61,41,20))

I was hoping it would highlight the 16th of this month (May), but it did not.

I got the args from running print text, bbox in the _pressed method.

If anyone can shed some light on this, I'd greatly appreciate it.

Upvotes: 8

Views: 603

Answers (2)

Moe Jan
Moe Jan

Reputation: 369

For the most part @Oblivion is correct, however I want to be able to use a Datetime.date object and to be able to traverse the months and years if needed. Once I changed the set_day method below everything worked perfectly.

def set_day(self, dt_object):
    day = dt_object.day
    w = self._calendar

    if not w.winfo_viewable():
        w.after(200, self.set_day, dt_object)
        return
    while dt_object.year < self._date.year:
        self._prev_month()
    while dt_object.year > self._date.year:
        self._next_month()
    while dt_object.month < self._date.month:
        self._prev_month()
    while dt_object.month > self._date.month:
        self._next_month()
    text = '%02d' % day
    column = None
    for iid in self._items:
        rowvals = w.item(iid, 'values')
        try:
            column = rowvals.index(text)
        except ValueError as err:
            pass
        else:
            item = iid
            bbox = w.bbox(iid, column)
            break

    if column is not None:
        self._selection = (text, item, column)
        self._show_selection(text, bbox)
    else:
        print "Column is None" 

Upvotes: 1

Oblivion
Oblivion

Reputation: 1729

In __setup_selection() there is a binding on the Configure event. Presumably, it's there to remove the "selection canvas" when the calendar is resized. However, the Configure event also fires when the calendar is first mapped to screen, so your pre-selected date disappears before you ever get to see it.

set_day (below) lets you select a day programmatically. It sidesteps the problem of that first Configure event by re-scheduling itself if the widget is not yet visible.

changes to Calendar:

def __setup_selection(self, sel_bg, sel_fg):
    self._font = font.Font()
    self._canvas = canvas = tkinter.Canvas(self._calendar,
        background=sel_bg, borderwidth=0, highlightthickness=0)
    canvas.text = canvas.create_text(0, 0, fill=sel_fg, anchor='w')

    canvas.bind('<ButtonPress-1>', lambda evt: canvas.place_forget())
    #self._calendar.bind('<Configure>', lambda evt: canvas.place_forget())
    self._calendar.bind('<Configure>', self.on_configure)        
    self._calendar.bind('<ButtonPress-1>', self._pressed)

def on_configure(self, event):
    self._canvas.place_forget()
    if self._selection is not None:
        text, iid, column = self._selection
        bbox = self._calendar.bbox(iid, column)
        self._show_selection(text, bbox)

def _prev_month(self):
    """Updated calendar to show the previous month."""
    self._canvas.place_forget()
    self._selection = None #
    self._date = self._date - self.timedelta(days=1)
    self._date = self.datetime(self._date.year, self._date.month, 1)
    self._build_calendar() # reconstuct calendar

def _next_month(self):
    """Update calendar to show the next month."""
    self._canvas.place_forget()
    self._selection = None #
    year, month = self._date.year, self._date.month
    self._date = self._date + self.timedelta(
        days=calendar.monthrange(year, month)[1] + 1)
    self._date = self.datetime(self._date.year, self._date.month, 1)
    self._build_calendar() # reconstruct calendar

def set_day(self, day):
    w = self._calendar
    if not w.winfo_viewable():
        w.after(200, self.set_day, day)
        return

    text = '%02d' % day
    column = None
    for iid in self._items:
        rowvals = w.item(iid, 'values')
        try:
            column = rowvals.index(text)
        except ValueError as err:
            pass
        else:
            item = iid
            bbox = w.bbox(iid, column)
            break

    if column is not None:
        self._selection = (text, item, column)
        self._show_selection(text, bbox) 

#test
def test():
    import sys
    root = tkinter.Tk()
    root.title('Ttk Calendar')
    ttkcal = Calendar(firstweekday=calendar.SUNDAY)
    ttkcal.pack(expand=1, fill='both')

    if 'win' not in sys.platform:
        style = ttk.Style()
        style.theme_use('clam')

    ttkcal.set_day(16) #
    root.mainloop()

Upvotes: 0

Related Questions