【问题标题】:Custom Tkinter Dropdown自定义 Tkinter 下拉菜单
【发布时间】:2020-05-11 16:22:09
【问题描述】:

我正在尝试使用 tkinter 在 python 中创建一个简单的自定义日历小部件。我想在选择时将其创建为下拉列表。我已经创建了整个代码的基础。我想知道是否有一种方法可以将我的自定义日历创建为类似于选项菜单的下拉菜单。我希望它在我选择日期时下拉,并在我更新日期时自动关闭。

注意:我知道使用 tkcalendar 之类的东西会更容易,但我不喜欢那个界面,更愿意创建自己的界面。

编辑:我现在使用网格忘记隐藏/显示日历。唯一的问题是它将其下方的所有小部件向下移动。我希望它覆盖在下面的小部件上。

代码:

import tkinter as tk
import datetime
from calendar import monthrange

class CalendarDatePicker:
    def __init__(self, frame, font=("Times New Roman", 12), bg="white", fg="black", bg_selected="light gray", fg_selected="black"):
        self.__font = font
        self.__bg = bg
        self.__fg = fg
        self.__bg_selected = bg_selected
        self.__fg_selected = fg_selected
        self.__frame = tk.Frame(frame, bg=self.__bg)
        self.__selected_date = None
        self.__top_frame = tk.Frame(self.__frame, bg=self.__bg)

        self.__date_frame = tk.Frame(self.__frame, bg=self.__bg)
        self.__dates = {}
        today = datetime.datetime.today()
        self.__dates["year"] = int(today.year)
        self.__dates["month"] = today.strftime("%B")
        self.__dates["date"] = today.day
        self.__dates["selected"] = today
        self.__update()

    def __toggle(self, show=False):
        if show:
            self.__top_frame.grid(row=1, column=0, pady=5, padx=2)
            self.__date_frame.grid(row=2, column=0, pady=5, padx=2)
        else: 
            self.__top_frame.grid_remove()
            self.__date_frame.grid_remove()
        self.__frame.update()    

    def __shift_year(self, shift):
        self.__dates["year"] = int(self.__dates["year"]) + shift
        self.__CurYear.destroy()
        self.__update()

    def __shift_month(self, shift):
        mn = int(datetime.datetime.strptime(self.__dates["month"], "%B").month) + shift
        if mn == 0:
            mn = 12
            self.__dates["year"] = int(self.__dates["year"])-1
            self.__CurYear.destroy()
        elif mn == 13:
            mn = 1
            self.__dates["year"] = int(self.__dates["year"])+1
            self.__CurYear.destroy()
        self.__dates["month"] = datetime.datetime(2000, mn, 1).strftime("%B")
        self.__CurMonth.destroy()
        self.__update()

    def __set_date(self, date):
        if date.strip() != '':
            self.__dates["date"] = date
            self.__dates["selected"] = datetime.datetime(int(self.__dates["year"]),int(datetime.datetime.strptime(self.__dates["month"], "%B").month),int(self.__dates["date"]))
            self.__toggle(False)
            self.__update()

    def get(self, date_format="%Y-%m-%d"):
        return self.__dates["selected"].strftime(date_format)

    def create(self):
        return self.__frame

    def __update(self):
        if self.__selected_date is not None:
            self.__selected_date.destroy()
        self.__selected_date = tk.Label(self.__frame, width=17, bg=self.__bg, fg=self.__fg, font=("Times New Roman", 16, "bold"), text=self.get("%d %B, %Y"), relief='solid', bd=1)
        self.__selected_date.bind('<Button-1>', lambda event, show=True: self.__toggle(show))
        self.__selected_date.grid(row=0, column=0)
        today = self.__dates["selected"]
        self.__YearShiftPrev = tk.Label(self.__top_frame, fg=self.__fg, text="<", relief="solid", bd=1, bg=self.__bg)
        self.__YearShiftPrev.bind('<Button-1>', lambda event, x=-1: self.__shift_year(x))
        self.__YearShiftPrev.grid(row=0, column=1, padx=2)
        self.__CurYear = tk.Label(self.__top_frame, fg=self.__fg, width=6, text=self.__dates["year"], bg=self.__bg)
        self.__CurYear.grid(row=0, column=2, padx=2)
        self.__YearShiftNext = tk.Label(self.__top_frame, fg=self.__fg, text=">", relief="solid", bd=1, bg=self.__bg)
        self.__YearShiftNext.bind('<Button-1>', lambda event, x=1: self.__shift_year(x))
        self.__YearShiftNext.grid(row=0, column=3, padx=2)
        self.__MonthShiftPrev = tk.Label(self.__top_frame, fg=self.__fg, text="<", relief="solid", bd=1, bg=self.__bg)
        self.__MonthShiftPrev.bind('<Button-1>', lambda event, x=-1: self.__shift_month(x))
        self.__MonthShiftPrev.grid(row=0, column=5, padx=2)
        self.__CurMonth = tk.Label(self.__top_frame, fg=self.__fg, width=10, text=self.__dates["month"], bg=self.__bg)
        self.__CurMonth.grid(row=0, column=6, padx=2)
        self.__MonthShiftNext = tk.Label(self.__top_frame, fg=self.__fg, text=">", relief="solid", bd=1, bg=self.__bg)
        self.__MonthShiftNext.bind('<Button-1>', lambda event, x=1: self.__shift_month(x))
        self.__MonthShiftNext.grid(row=0, column=7, padx=2)
        day, total_days = monthrange(int(self.__dates["year"]), int(datetime.datetime.strptime(self.__dates["month"], "%B").month))
        for ind, text in enumerate(["M", "T", "W", "T", "F", "S", "S"]):
            lbl = tk.Label(self.__date_frame, fg=self.__fg, text=text, width=3, bg=self.__bg, relief="solid", bd=1)
            lbl.grid(row=0, column=ind%7, pady=1, padx=1)
        date = 1
        cur_month = today.year == self.__dates["year"] and today.strftime("%B") == self.__dates["month"]
        for i in range(42):
            row, col = 1+i//7, i%7
            if col == day and date <= total_days:
                day = (day+1)%7
                text = str(date)
                date += 1
            else:
                text = ' '
            bg, fg = (self.__bg_selected, self.__fg_selected) if (text==str(self.__dates["date"]) and cur_month) else (self.__bg, self.__fg)
            lbl = tk.Label(self.__date_frame, width=3, text=text, bg=bg, fg=fg, relief="solid", bd=1)
            lbl.bind("<Button-1>", lambda event, date=text: self.__set_date(date)) 
            lbl.grid(row=row, column=col, pady=1, padx=1)
        self.__frame.update()

window = tk.Tk()
calendar = CalendarDatePicker(window, bg="black", fg="white", bg_selected="white", fg_selected="black")
calendar.create().pack()
for i in range(10):
    tk.Label(window, text=i).pack()

变化:

def __toggle(self, show=False):
    if show:
        self.__top_frame.grid(row=1, column=0, pady=5, padx=2)
        self.__date_frame.grid(row=2, column=0, pady=5, padx=2)
    else: 
        self.__top_frame.grid_remove()
        self.__date_frame.grid_remove()
    self.__frame.update()    

self.__toggle(False)__set_date 函数的末尾被调用。 self.__toggle(True) id 在__update 函数下的第一个标签上调用。我已经绑定它来调用那里的函数。 我基本上所做的是添加了一种隐藏/显示日历部分的方法。但是现在当我切换它时,所有标签(1-9)都移到了下面。我希望它覆盖小部件。或者更准确地覆盖它们并在切换方法上发现它们。

我对此进行了更多调查,发现一个解决方案是使用.place 而不是.grid。但是在实际程序中,我想将它与grid 管理器一起使用。有什么方法可以在不使用.pack 的情况下做到这一点?

【问题讨论】:

  • “我想让它下拉”:你想下拉哪一部分? “当我选择日期时”bind('&lt;Button-1&gt;'selected_date = tk.Label
  • 我希望当我点击日期时,它会显示整个日期选择器。当我再次选择日期时,它应该隐藏日期选择器并更新标签。
  • 继续阅读grid_forget()
  • 好的,我查过了。它非常有用。现在唯一的问题是当我使用它时,它将其下方的所有小部件移动到框架下方。我希望日历在下面的小部件上进行排序。我已经使用 grid_forget 更新了代码
  • “它移动了它下面的所有小部件”:验证了您的示例,它工作正常。 Edit 你的问题并展示你是怎么做的.bind(... 以及你在哪里打电话给toggle(False)

标签: python python-3.x tkinter


【解决方案1】:

当我在下拉所有小部件而不是重叠它们时遇到类似的问题时,我也尝试使用place(),因为如果你想重叠小部件,这是每个人都说的。然而,将place() 用于任何复杂的事情都是一系列全新的问题。我通过使用下拉菜单的顶级窗口解决了这个问题,就像你使用工具提示一样。我以这种方式创建了自己的全功能组合框,因为我想要无法添加到 ttk.Combobox 的功能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-10-12
    • 1970-01-01
    • 1970-01-01
    • 2021-08-22
    • 2018-11-30
    • 2011-02-06
    • 2018-04-17
    相关资源
    最近更新 更多