Tkcalendar: Right align calendar dropdown with the DateEntry
It is possible to right-align the dropdown by rewriting the drop_down()
method of the DateEntry
. The dropdown is a Toplevel which is positioned on the screen with
self._top_cal.geometry('+%i+%i' % (x, y))
where (x, y) is the top-left corner of the dropdown. So, for a left-aligned dropdown
x = self.winfo_rootx() # the left side of the entry
Now to get a right-aligned dropdown, we need to change x into
x = self.winfo_rootx() + self.winfo_width() - self._top_cal.winfo_reqwidth()
namely (position of the right side of the entry) - (width of the dropdown).
Full code:
from tkcalendar import DateEntryimport tkinter as tkclass MyDateEntry(DateEntry): def __init__(self, master=None, align='left', **kw): DateEntry.__init__(self, master, **kw) self.align = align def drop_down(self): """Display or withdraw the drop-down calendar depending on its current state.""" if self._calendar.winfo_ismapped(): self._top_cal.withdraw() else: self._validate_date() date = self.parse_date(self.get()) if self.align == 'left': # usual DateEntry x = self.winfo_rootx() else: # right aligned drop-down x = self.winfo_rootx() + self.winfo_width() - self._top_cal.winfo_reqwidth() y = self.winfo_rooty() + self.winfo_height() if self.winfo_toplevel().attributes('-topmost'): self._top_cal.attributes('-topmost', True) else: self._top_cal.attributes('-topmost', False) self._top_cal.geometry('+%i+%i' % (x, y)) self._top_cal.deiconify() self._calendar.focus_set() self._calendar.selection_set(date)root = tk.Tk()tk.Label(root, text='left align').grid(row=0, column=0)tk.Label(root, text='right align').grid(row=0, column=1)MyDateEntry(root).grid(row=1, column=0)MyDateEntry(root, align='right').grid(row=1, column=1)root.mainloop()
EDIT: you can also detect if the drop-down will be out of screen and automatically adjust the drop-down position to avoid that:
def drop_down(self): """Display or withdraw the drop-down calendar depending on its current state.""" if self._calendar.winfo_ismapped(): self._top_cal.withdraw() else: self._validate_date() date = self.parse_date(self.get()) h = self._top_cal.winfo_reqheight() w = self._top_cal.winfo_reqwidth() x_max = self.winfo_screenwidth() y_max = self.winfo_screenheight() # default: left-aligned drop-down below the entry x = self.winfo_rootx() y = self.winfo_rooty() + self.winfo_height() if x + w > x_max: # the drop-down goes out of the screen # right-align the drop-down x += self.winfo_width() - w if y + h > y_max: # the drop-down goes out of the screen # bottom-align the drop-down y -= self.winfo_height() + h if self.winfo_toplevel().attributes('-topmost'): self._top_cal.attributes('-topmost', True) else: self._top_cal.attributes('-topmost', False) self._top_cal.geometry('+%i+%i' % (x, y)) self._top_cal.deiconify() self._calendar.focus_set() self._calendar.selection_set(date)
Note that this solution will not work properly when using several monitors since tkinter detects only one big rectangular screen.