Python tkinter with ttk calendar -
i using this code create simple calendar on tkinter. when put calendar on main root window, calendar appear fine. so, decided put button create tkinter toplevel window , put 1 more calendar on toplevel window.but time failed display calendar , instead gave me error," tclerror: can't pack .18913120 inside .18912200.18912400"
. explain why getting error message.
this sample code:
import calendar import sys try: import tkinter import tkfont except importerror: # py3k import tkinter tkinter import tkinter.font tkfont import ttk def get_calendar(locale, fwday): # instantiate proper calendar class if locale none: return calendar.textcalendar(fwday) else: return calendar.localetextcalendar(fwday, locale) class calendar(ttk.frame): # xxx todo: cget , configure datetime = calendar.datetime.datetime timedelta = calendar.datetime.timedelta def __init__(self, master=none, **kw): """ widget-specific options locale, firstweekday, year, month, selectbackground, selectforeground """ # remove custom options kw before initializating ttk.frame fwday = kw.pop('firstweekday', calendar.monday) year = kw.pop('year', self.datetime.now().year) month = kw.pop('month', self.datetime.now().month) locale = kw.pop('locale', none) sel_bg = kw.pop('selectbackground', '#ecffc4') sel_fg = kw.pop('selectforeground', '#05640e') self._date = self.datetime(year, month, 1) self._selection = none # no date selected ttk.frame.__init__(self, master, **kw) self._cal = get_calendar(locale, fwday) self.__setup_styles() # creates custom styles self.__place_widgets() # pack/grid used widgets self.__config_calendar() # adjust calendar columns , setup tags # configure canvas, , proper bindings, selecting dates self.__setup_selection(sel_bg, sel_fg) # store items ids, used insertion later self._items = [self._calendar.insert('', 'end', values='') _ in range(6)] # insert dates in empty calendar self._build_calendar() # set minimal size widget self._calendar.bind('<map>', self.__minsize) def __setitem__(self, item, value): if item in ('year', 'month'): raise attributeerror("attribute '%s' not writeable" % item) elif item == 'selectbackground': self._canvas['background'] = value elif item == 'selectforeground': self._canvas.itemconfigure(self._canvas.text, item=value) else: ttk.frame.__setitem__(self, item, value) def __getitem__(self, item): if item in ('year', 'month'): return getattr(self._date, item) elif item == 'selectbackground': return self._canvas['background'] elif item == 'selectforeground': return self._canvas.itemcget(self._canvas.text, 'fill') else: r = ttk.tclobjs_to_py({item: ttk.frame.__getitem__(self, item)}) return r[item] def __setup_styles(self): # custom ttk styles style = ttk.style(self.master) arrow_layout = lambda dir: ( [('button.focus', {'children': [('button.%sarrow' % dir, none)]})] ) style.layout('l.tbutton', arrow_layout('left')) style.layout('r.tbutton', arrow_layout('right')) def __place_widgets(self): # header frame , widgets hframe = ttk.frame(self) lbtn = ttk.button(hframe, style='l.tbutton', command=self._prev_month) rbtn = ttk.button(hframe, style='r.tbutton', command=self._next_month) self._header = ttk.label(hframe, width=15, anchor='center') # calendar self._calendar = ttk.treeview(show='', selectmode='none', height=7) # pack widgets hframe.pack(in_=self, side='top', pady=4, anchor='center') lbtn.grid(in_=hframe) self._header.grid(in_=hframe, column=1, row=0, padx=12) rbtn.grid(in_=hframe, column=2, row=0) self._calendar.pack(in_=self, expand=1, fill='both', side='bottom') def __config_calendar(self): cols = self._cal.formatweekheader(3).split() self._calendar['columns'] = cols self._calendar.tag_configure('header', background='grey90') self._calendar.insert('', 'end', values=cols, tag='header') # adjust columns width font = tkfont.font() maxwidth = max(font.measure(col) col in cols) col in cols: self._calendar.column(col, width=maxwidth, minwidth=maxwidth, anchor='e') def __setup_selection(self, sel_bg, sel_fg): self._font = tkfont.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('<buttonpress-1>', self._pressed) def __minsize(self, evt): width, height = self._calendar.master.geometry().split('x') height = height[:height.index('+')] self._calendar.master.minsize(width, height) def _build_calendar(self): year, month = self._date.year, self._date.month # update header text (month, year) header = self._cal.formatmonthname(year, month, 0) self._header['text'] = header.title() # update calendar shown dates cal = self._cal.monthdayscalendar(year, month) indx, item in enumerate(self._items): week = cal[indx] if indx < len(cal) else [] fmt_week = [('%02d' % day) if day else '' day in week] self._calendar.item(item, values=fmt_week) def _show_selection(self, text, bbox): """configure canvas new selection.""" x, y, width, height = bbox textw = self._font.measure(text) canvas = self._canvas canvas.configure(width=width, height=height) canvas.coords(canvas.text, width - textw, height / 2 - 1) canvas.itemconfigure(canvas.text, text=text) canvas.place(in_=self._calendar, x=x, y=y) # callbacks def _pressed(self, evt): """clicked somewhere in calendar.""" x, y, widget = evt.x, evt.y, evt.widget item = widget.identify_row(y) column = widget.identify_column(x) if not column or not item in self._items: # clicked in weekdays row or outside columns return item_values = widget.item(item)['values'] if not len(item_values): # row empty month return text = item_values[int(column[1]) - 1] if not text: # date empty return bbox = widget.bbox(item, column) if not bbox: # calendar not visible yet return # update , show selection text = '%02d' % text self._selection = (text, item, column) self._show_selection(text, bbox) def _prev_month(self): """updated calendar show previous month.""" self._canvas.place_forget() 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 show next month.""" self._canvas.place_forget() 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 # properties @property def selection(self): """return datetime representing current selected date.""" if not self._selection: return none year, month = self._date.year, self._date.month return self.datetime(year, month, int(self._selection[0])) def myfunction(): root2=tkinter.toplevel(root) ttkcal = calendar(root2,firstweekday=calendar.sunday) ttkcal.pack(expand=1, fill='both') root=tkinter.tk() frame=tkinter.frame(root) frame.pack(side="left") button=tkinter.button(root,text="top level",command=myfunction) button.pack(side="right") ttkcal = calendar(frame,firstweekday=calendar.sunday) ttkcal.pack(expand=1, fill='both') root.mainloop()
author of widget used 1 unnecessary action: widget set minimal size of root window, , assumed window in program (it isn't bug, it's feature).
1.in method def __place_widgets(self)
:
self._calendar = ttk.treeview(show='', selectmode='none', height=7)
should be:
self._calendar = ttk.treeview(self, show='', selectmode='none', height=7)
2.in constructor remove line:
self._calendar.bind('<map>', self.__minsize)
3.delete __minsize
method
4.if want set minimal size root2
use code:
def myfunction(): root2=tkinter.toplevel(root) ttkcal = calendar(root2,firstweekday=calendar.sunday) ttkcal.pack(expand=1, fill='both') root2.update() root2.minsize(root2.winfo_reqwidth(), root2.winfo_reqheight())
Comments
Post a Comment