A simple calendar using tktable.

   1 import platform
   2 import calendar
   3 from datetime import datetime
   4 
   5 import ttk
   6 import tktable
   7 
   8 LINUX = platform.system() == 'Linux'
   9 
  10 def get_calendar(locale, fwday):
  11     if locale is None:
  12         return calendar.TextCalendar(fwday)
  13     else:
  14         return calendar.LocaleTextCalendar(fwday, locale)
  15 
  16 
  17 class ArrowButton(ttk.Button):
  18     arrow_layout = lambda self, direc: (
  19         [('Button.focus', {'children': [('Button.%sarrow' % direc, None)]})]
  20         )
  21 
  22     def __init__(self, master, **kw):
  23         direction = kw.pop('direction', 'left')
  24         style = ttk.Style(master)
  25 
  26         # XXX urgh
  27         if LINUX:
  28             style.layout('L.TButton', self.arrow_layout('left'))
  29             style.layout('R.TButton', self.arrow_layout('right'))
  30             kw['style'] = 'L.TButton' if direction == 'left' else 'R.TButton'
  31         else:
  32             kw['text'] = u'\u25C0' if direction == 'left' else u'\u25B6'
  33             kw['style'] = 'Arrow.TButton'
  34             style.configure(kw['style'], width=2, padding=0)
  35         # urgh end
  36 
  37         ttk.Button.__init__(self, master, **kw)
  38 
  39 
  40 class Calendar(ttk.Frame, object):
  41     def __init__(self, master=None, **kw):
  42         ttk.Frame.__init__(self, master)
  43 
  44         params = {'locale': None, 'titlebg': 'blue', 'titlefg': 'white',
  45             'calendarbg': 'white'}
  46         params.update(kw)
  47         for arg, val in params.iteritems():
  48             setattr(self, "_%s" % arg, val)
  49 
  50         date = datetime.now()
  51         self._year, self._month = date.year, date.month
  52 
  53         self._setup_style()
  54         self._build_topbar()
  55 
  56         # calendar
  57         self._cal = get_calendar(self._locale, calendar.SUNDAY)
  58         self._tclarray = tktable.ArrayVar(self)
  59         cols = self._cal.formatweekheader(3).split()
  60         self.table = tktable.Table(self, variable=self._tclarray,
  61             highlightthickness=4, highlightcolor=self._calendarbg,
  62             highlightbackground=self._calendarbg,
  63             cols=len(cols) + 1, rows=7, background=self._calendarbg,
  64             titlerows=1, titlecols=1, roworigin=-1, colorigin=-1,
  65             bd=0, cursor='arrow', resizeborders='none', colwidth=5,
  66             state='disabled', browsecommand=self._set_selection)
  67         self.table.pack(side='bottom')
  68         self.table.bind('<Map>', self._set_minsize)
  69 
  70         self._setup_table(cols)
  71         # update calendar
  72         self._yeardates = self._year
  73 
  74 
  75     def next_month(self):
  76         if self._month == 12:
  77             self._month = 1
  78             self._year += 1
  79             self._yeardates = self._year
  80         else:
  81             self._month += 1
  82             self._adjust_calendar(self._month)
  83 
  84 
  85     def prev_month(self):
  86         if self._month == 1:
  87             self._month = 12
  88             self._year -= 1
  89             self._yeardates = self._year
  90         else:
  91             self._month -= 1
  92             self._adjust_calendar(self._month)
  93 
  94 
  95     def next_year(self):
  96         self._year += 1
  97         self._yeardates = self._year
  98 
  99 
 100     def prev_year(self):
 101         self._year -= 1
 102         self._yeardates = self._year
 103 
 104 
 105 
 106     def _setup_style(self):
 107         style = ttk.Style(self)
 108         if LINUX:
 109             style.theme_use('clam')
 110 
 111     def _build_topbar(self):
 112         bar = ttk.Frame(self, relief='raised', padding=4)
 113         bar.pack(side='top', fill='x')
 114         lbtn = ArrowButton(bar, direction='left', command=self.prev_month)
 115         rbtn = ArrowButton(bar, direction='right', command=self.next_month)
 116         self._monthlbl = ttk.Label(bar, text=calendar.month_name[self._month],
 117             width=len(max(calendar.month_name)), anchor='center')
 118         lbtn.grid(row=0, column=0, sticky='w')
 119         self._monthlbl.grid(row=0, column=1, padx=6)
 120         rbtn.grid(row=0, column=2, sticky='w')
 121 
 122         spacer = ttk.Label(bar, text='')
 123         spacer.grid(row=0, column=3, sticky='ew')
 124 
 125         lbtn2 = ArrowButton(bar, direction='left', command=self.prev_year)
 126         rbtn2 = ArrowButton(bar, direction='right', command=self.next_year)
 127         self._yearlbl = ttk.Label(bar, text=self._year)
 128         lbtn2.grid(row=0, column=4, sticky='e')
 129         self._yearlbl.grid(row=0, column=5, padx=6, sticky='e')
 130         rbtn2.grid(row=0, column=6, sticky='e')
 131 
 132         bar.grid_columnconfigure(3, weight=1)
 133 
 134     def _setup_table(self, cols):
 135         table = self.table
 136         table.tag_configure('title', bg=self._titlebg, fg=self._titlefg)
 137 
 138         array = self._tclarray
 139         for indx, col in enumerate(cols):
 140             table_indx = '-1,%d' % indx
 141             array[table_indx] = col
 142 
 143     def _adjust_calendar(self, month_now):
 144         array = self._tclarray
 145         table = self.table
 146         month_0 = month_now - 1
 147         # remove the 'not_this_month' tag from items that were using it and
 148         # possibly won't be redisplayed now.
 149         table.tag_delete('not_this_month')
 150         table.tag_configure('not_this_month', fg='grey70')
 151         # XXX clear selection
 152         table.selection_clear('all')
 153 
 154         # update values in calendar
 155         self._monthlbl['text'] = calendar.month_name[month_now]
 156         for week_indx, week in enumerate(self._yeardates[month_0]):
 157             array['%d,-1' % week_indx] = week[0].strftime('%U')
 158             for day_indx, date in enumerate(week):
 159                 table_indx = '%d,%d' % (week_indx, day_indx)
 160                 array[table_indx] = date.day
 161                 if date.month != month_now:
 162                     table.tag_cell('not_this_month', table_indx)
 163 
 164         # erase data in rows that weren't overrwritten
 165         for row in range(len(self._yeardates[month_0]), 6):
 166             for i in range(-1, 7):
 167                 array.unset('%d,%d' % (row, i))
 168 
 169     def _set_minsize(self, event):
 170         self.master.wm_minsize(self.winfo_width(), self.winfo_height())
 171 
 172     def _get_year_dates(self):
 173         return self.__year_dates
 174 
 175     def _set_year_dates(self, year):
 176         self.__year_dates = [
 177             self._cal.monthdatescalendar(year, i)
 178                 for i in range(calendar.January, calendar.January+12)
 179             ]
 180         self._yearlbl['text'] = year
 181         self._adjust_calendar(self._month)
 182 
 183     def _get_selected(self):
 184         week, day = self.__selected
 185         date = self._yeardates[self._month - 1][week][day]
 186         return (date.year, date.month, date.day)
 187 
 188     def _set_selection(self, event):
 189         if event.r == -1 or event.c == -1 or not self._tclarray.get(event.C):
 190             return
 191 
 192         self.__selected = (event.r, event.c)
 193         self.event_generate('<<date-selected>>')
 194 
 195     _yeardates = property(_get_year_dates, _set_year_dates)
 196     selected = property(_get_selected, _set_selection)
 197 
 198 
 199 def sample():
 200     def print_date(event):
 201         print event.widget.selected
 202 
 203     cal = Calendar(titlebg='#2077ed', titlefg='white')
 204     cal.pack()
 205     cal.bind('<<date-selected>>', print_date)
 206     cal.mainloop()
 207 
 208 if __name__ == "__main__":
 209     sample()

tktablecalendar.png tktablecalendar_vista2.png

To run this you will ned the tktable wrapper: http://tkinter.unpy.net/wiki/TkTableWrapper

And the the ttk wrapper for python: http://pypi.python.org/pypi/pyttk, or optionally apply this patch: tkcalendar_nottk.diff to run it without ttk

tkinter: TkTableCalendar (last edited 2010-07-26 11:59:11 by localhost)