pythonuser-interfacetkintertkcalendar

Behaviour of DateEntry in Tkinter Window


I have a button that adds a DateEntry widget to a scrollable frame. Unfortunately, the frame is at the bottom of the window, so after adding a few rows, the dropdown calendar hides behind the task bar. Is there any way to change it, so the calendar opens above the field, rather than below?enter image description here

Here is some test code

from tkinter import *
from tkinter import ttk
from tkcalendar import Calendar, DateEntry

master = Tk()

global rowNumForShiftReport
rowNumForShiftReport=0
shiftDateWidgetList=[]

def myfunction(event):
    canvas2.configure(scrollregion=canvas2.bbox("all"), width=100, height=100)

def addEntry2():
    global rowNumForShiftReport


    rowNumForShiftReport = rowNumForShiftReport + 1
    shiftDateWidgetList.append(DateEntry(frame2, state='readonly', width=15))
    shiftDateWidgetList[-1].grid(row=rowNumForShiftReport, column=0)
    rowNumForShiftReport+1


master.geometry('400x400')
btn_addField2 = ttk.Button(master, text="Add Entry",command=addEntry2)
btn_addField2.grid(row=0, column=1)
#lotFrame2 = Frame(master)
actualLabelFrame=ttk.LabelFrame(master, text="Shift Report", height=300, width=300)
actualLabelFrame.grid(row=0, column=0)
canvas2 = Canvas(actualLabelFrame,width=160)
frame2 = Frame(canvas2,width=160)
frame2.bind("<Configure>", myfunction)
canvas2.create_window((0, 0), window=frame2, anchor='nw')
scrollBar2 = ttk.Scrollbar(actualLabelFrame, orient="vertical", command=canvas2.yview)
canvas2.configure(yscrollcommand=scrollBar2.set)
scrollBar2.grid(row=0, column=2, sticky=N + S)
canvas2.grid(row=0, column=1)



mainloop()


Solution

  • You can achieve the goal by extending DateEntry and override its drop_down() function as below:

    class MyDateEntry(DateEntry):
        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())
                x = self.winfo_rootx()
                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)
                # - patch begin: make sure the drop-down calendar is visible
                if x+self._top_cal.winfo_width() > self.winfo_screenwidth():
                    x = self.winfo_screenwidth() - self._top_cal.winfo_width()
                if y+self._top_cal.winfo_height() > self.winfo_screenheight()-30:
                    y = self.winfo_rooty() - self._top_cal.winfo_height()
                # - patch end
                self._top_cal.geometry('+%i+%i' % (x, y))
                self._top_cal.deiconify()
                self._calendar.focus_set()
                self._calendar.selection_set(date)        
    

    Then replace all DateEntry(...) by MyDateEntry(...) in your code.

    Note that it is based on tkcalendar v1.6.1.