2 Simple calendar using ttk Treeview together with calendar and datetime
10 def get_calendar(locale
, fwday
):
11 # instantiate proper calendar class
13 return calendar
.TextCalendar(fwday
)
15 return calendar
.LocaleTextCalendar(fwday
, locale
)
17 class Calendar(ttk
.Frame
):
18 # XXX ToDo: cget and configure
20 datetime
= calendar
.datetime
.datetime
21 timedelta
= calendar
.datetime
.timedelta
23 def __init__(self
, master
=None, **kw
):
25 WIDGET-SPECIFIC OPTIONS
27 locale, firstweekday, year, month, selectbackground,
30 # remove custom options from kw before initializating ttk.Frame
31 fwday
= kw
.pop('firstweekday', calendar
.MONDAY
)
32 year
= kw
.pop('year', self
.datetime
.now().year
)
33 month
= kw
.pop('month', self
.datetime
.now().month
)
34 locale
= kw
.pop('locale', None)
35 sel_bg
= kw
.pop('selectbackground', '#ecffc4')
36 sel_fg
= kw
.pop('selectforeground', '#05640e')
38 self
._date
= self
.datetime(year
, month
, 1)
39 self
._selection
= None # no date selected
41 ttk
.Frame
.__init
__(self
, master
, **kw
)
43 self
._cal
= get_calendar(locale
, fwday
)
45 self
.__setup
_styles
() # creates custom styles
46 self
.__place
_widgets
() # pack/grid used widgets
47 self
.__config
_calendar
() # adjust calendar columns and setup tags
48 # configure a canvas, and proper bindings, for selecting dates
49 self
.__setup
_selection
(sel_bg
, sel_fg
)
51 # store items ids, used for insertion later
52 self
._items
= [self
._calendar
.insert('', 'end', values
='')
54 # insert dates in the currently empty calendar
55 self
._build
_calendar
()
57 # set the minimal size for the widget
58 self
._calendar
.bind('<Map>', self
.__minsize
)
60 def __setitem__(self
, item
, value
):
61 if item
in ('year', 'month'):
62 raise AttributeError("attribute '%s' is not writeable" % item
)
63 elif item
== 'selectbackground':
64 self
._canvas
['background'] = value
65 elif item
== 'selectforeground':
66 self
._canvas
.itemconfigure(self
._canvas
.text
, item
=value
)
68 ttk
.Frame
.__setitem
__(self
, item
, value
)
70 def __getitem__(self
, item
):
71 if item
in ('year', 'month'):
72 return getattr(self
._date
, item
)
73 elif item
== 'selectbackground':
74 return self
._canvas
['background']
75 elif item
== 'selectforeground':
76 return self
._canvas
.itemcget(self
._canvas
.text
, 'fill')
78 r
= ttk
.tclobjs_to_py({item
: ttk
.Frame
.__getitem
__(self
, item
)})
81 def __setup_styles(self
):
83 style
= ttk
.Style(self
.master
)
84 arrow_layout
= lambda dir: (
85 [('Button.focus', {'children': [('Button.%sarrow' % dir, None)]})]
87 style
.layout('L.TButton', arrow_layout('left'))
88 style
.layout('R.TButton', arrow_layout('right'))
90 def __place_widgets(self
):
91 # header frame and its widgets
92 hframe
= ttk
.Frame(self
)
93 lbtn
= ttk
.Button(hframe
, style
='L.TButton', command
=self
._prev
_month
)
94 rbtn
= ttk
.Button(hframe
, style
='R.TButton', command
=self
._next
_month
)
95 self
._header
= ttk
.Label(hframe
, width
=15, anchor
='center')
97 self
._calendar
= ttk
.Treeview(show
='', selectmode
='none', height
=7)
100 hframe
.pack(in_
=self
, side
='top', pady
=4, anchor
='center')
101 lbtn
.grid(in_
=hframe
)
102 self
._header
.grid(in_
=hframe
, column
=1, row
=0, padx
=12)
103 rbtn
.grid(in_
=hframe
, column
=2, row
=0)
104 self
._calendar
.pack(in_
=self
, expand
=1, fill
='both', side
='bottom')
106 def __config_calendar(self
):
107 cols
= self
._cal
.formatweekheader(3).split()
108 self
._calendar
['columns'] = cols
109 self
._calendar
.tag_configure('header', background
='grey90')
110 self
._calendar
.insert('', 'end', values
=cols
, tag
='header')
111 # adjust its columns width
113 maxwidth
= max(font
.measure(col
) for col
in cols
)
115 self
._calendar
.column(col
, width
=maxwidth
, minwidth
=maxwidth
,
118 def __setup_selection(self
, sel_bg
, sel_fg
):
119 self
._font
= tkFont
.Font()
120 self
._canvas
= canvas
= Tkinter
.Canvas(self
._calendar
,
121 background
=sel_bg
, borderwidth
=0, highlightthickness
=0)
122 canvas
.text
= canvas
.create_text(0, 0, fill
=sel_fg
, anchor
='w')
124 canvas
.bind('<ButtonPress-1>', lambda evt
: canvas
.place_forget())
125 self
._calendar
.bind('<Configure>', lambda evt
: canvas
.place_forget())
126 self
._calendar
.bind('<ButtonPress-1>', self
._pressed
)
128 def __minsize(self
, evt
):
129 width
, height
= self
._calendar
.master
.geometry().split('x')
130 height
= height
[:height
.index('+')]
131 self
._calendar
.master
.minsize(width
, height
)
133 def _build_calendar(self
):
134 year
, month
= self
._date
.year
, self
._date
.month
136 # update header text (Month, YEAR)
137 header
= self
._cal
.formatmonthname(year
, month
, 0)
138 self
._header
['text'] = header
.title()
140 # update calendar shown dates
141 cal
= self
._cal
.monthdayscalendar(year
, month
)
142 for indx
, item
in enumerate(self
._items
):
143 week
= cal
[indx
] if indx
< len(cal
) else []
144 fmt_week
= [('%02d' % day
) if day
else '' for day
in week
]
145 self
._calendar
.item(item
, values
=fmt_week
)
147 def _show_selection(self
, text
, bbox
):
148 """Configure canvas for a new selection."""
149 x
, y
, width
, height
= bbox
151 textw
= self
._font
.measure(text
)
153 canvas
= self
._canvas
154 canvas
.configure(width
=width
, height
=height
)
155 canvas
.coords(canvas
.text
, width
- textw
, height
/ 2 - 1)
156 canvas
.itemconfigure(canvas
.text
, text
=text
)
157 canvas
.place(in_
=self
._calendar
, x
=x
, y
=y
)
161 def _pressed(self
, evt
):
162 """Clicked somewhere in the calendar."""
163 x
, y
, widget
= evt
.x
, evt
.y
, evt
.widget
164 item
= widget
.identify_row(y
)
165 column
= widget
.identify_column(x
)
167 if not column
or not item
in self
._items
:
168 # clicked in the weekdays row or just outside the columns
171 item_values
= widget
.item(item
)['values']
172 if not len(item_values
): # row is empty for this month
175 text
= item_values
[int(column
[1]) - 1]
176 if not text
: # date is empty
179 bbox
= widget
.bbox(item
, column
)
180 if not bbox
: # calendar not visible yet
183 # update and then show selection
185 self
._selection
= (text
, item
, column
)
186 self
._show
_selection
(text
, bbox
)
188 def _prev_month(self
):
189 """Updated calendar to show the previous month."""
190 self
._canvas
.place_forget()
192 self
._date
= self
._date
- self
.timedelta(days
=1)
193 self
._date
= self
.datetime(self
._date
.year
, self
._date
.month
, 1)
194 self
._build
_calendar
() # reconstuct calendar
196 def _next_month(self
):
197 """Update calendar to show the next month."""
198 self
._canvas
.place_forget()
200 year
, month
= self
._date
.year
, self
._date
.month
201 self
._date
= self
._date
+ self
.timedelta(
202 days
=calendar
.monthrange(year
, month
)[1] + 1)
203 self
._date
= self
.datetime(self
._date
.year
, self
._date
.month
, 1)
204 self
._build
_calendar
() # reconstruct calendar
210 """Return a datetime representing the current selected date."""
211 if not self
._selection
:
214 year
, month
= self
._date
.year
, self
._date
.month
215 return self
.datetime(year
, month
, int(self
._selection
[0]))
220 root
.title('Ttk Calendar')
221 ttkcal
= Calendar(firstweekday
=calendar
.SUNDAY
)
222 ttkcal
.pack(expand
=1, fill
='both')
224 if 'win' not in sys
.platform
:
226 style
.theme_use('clam')
230 if __name__
== '__main__':