8

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.

Moe Jan
  • 369
  • 1
  • 4
  • 16
  • 1
    I am puzzled too. I put ` print((text, textw, bbox, canvas['background'], x, y, canvas['width'], canvas['height'], canvas.text), '\n', (self._calendar, canvas.coords(canvas.text), canvas.itemcget(canvas.text, 'text'))) ` at the end of _show_selection and everything is the same. I also added ` self._selection = ('16', 'I004', '#2')` before the added call in `__init__`, to match the line in `_pressed`. I even added `self._canvas.place_forget(), from looking in _setup_selection. – Terry Jan Reedy May 11 '16 at 22:06
  • 1
    I also tries ` self._canvas.event_generate('', x=50, y=73) self._calendar.event_generate('', x=50, y=73) `. I put ` print(evt.x, evt.y, evt.widget)` at the top of `_pressed` to verify that it is called on startup with essentially the same event. Still does not work. – Terry Jan Reedy May 11 '16 at 22:19

2 Answers2

1

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" 
Moe Jan
  • 369
  • 1
  • 4
  • 16
0

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()
Oblivion
  • 1,669
  • 14
  • 14