赞
踩
学习python期间,发现 tkinter没有自带的日期选择控件。决定自己的写一个日期控件,费尽周转,终于写了一个自己满意的日期控件。本着人人为我,我为人人的原则,欢迎大家转发,评论,及提出宝贵的建议和意见。严重反感复制别人作品来获取下载积分和关注等行为。
1.创建一个自定义DatePicker类,实现下拉日期选择
- import tkinter
- from tkinter import ttk
- import calendar
- import datetime
-
-
- class DatePicker(ttk.Entry):
-
- def __init__(self, master=None, **kw):
- self._separator = kw.pop('separator', '-')
- if self._separator not in ['-','/']:
- self._separator = '-'
-
- super().__init__(master, **kw)
-
- self.set_state(kw.get('state', ''))
-
- self.style = ttk.Style(self)
- self._setup_style()
- self.configure(style='DatePicker')
-
- self._year = tkinter.IntVar()
- self._year.set(datetime.date.today().year)
- self._month = tkinter.IntVar()
- self._month.set(datetime.date.today().month)
- self._day = tkinter.IntVar()
- self._day.set(datetime.date.today().day)
- self._set_text()
-
- self._frame = tkinter.Toplevel(self, background='gray', borderwidth=1)
- self._frame.withdraw() # 隐藏窗口
- self._frame.overrideredirect(True) # 显示下拉时,不带标题栏 (Toplevel是一个窗口)
- self._set_frame()
-
- self.bind('<Motion>', self._on_motion)
- self.bind('<Leave>', lambda e: self.state(['!active']))
- self.bind('<Button-1>', self._show_down_frame)
- self._top_frame.bind('<FocusOut>', self._on_focus_out)
-
-
- def _setup_style(self):
- """
- 设置样式(复制TCombobox样式)
- :return:
- """
- self.style.layout('DatePicker', self.style.layout('TCombobox'))
- conf = self.style.configure('TCombobox')
- if conf:
- self.style.configure('DatePicker', **conf)
- maps = self.style.map('TCombobox')
- if maps:
- self.style.map('DatePicker', **maps)
-
-
- def _on_motion(self, event):
- x, y = event.x, event.y
- if 'disabled' not in self.state():
- if self.identify(x, y) == 'Combobox.rightdownarrow':
- self.state(['active'])
- self.configure(cursor='arrow')
- else:
- self.state(['!active'])
- self.configure(cursor='xterm')
-
-
- def _show_down_frame(self, event):
- """
- 显示下拉
- :param event:
- :return:
- """
- if ('disabled' in self.state()) or (self.identify(event.x, event.y) != 'Combobox.rightdownarrow'):
- return
-
- if self._sp_year.winfo_ismapped(): # _btn被绘制(下拉打开状态)
- self._frame.withdraw() # 隐藏下拉窗口(关闭下拉)
- else:
- x = self.winfo_rootx()
- y = self.winfo_rooty()+ self.winfo_height()
-
- self._frame.geometry('+%i+%i' % (x,y))
- self._frame.deiconify() # 打开下拉(显示窗口)
- self._sp_year.focus_set()
-
-
- def _on_focus_out(self, event):
- if self.focus_get() == None and 'active' in self.state(): #下拉打开再次点击下拉
- pass
- else:
- self._frame.withdraw()
-
-
- def _set_frame(self):
- """
- 设置下拉界面
- :return:
- """
- self._top_frame = ttk.Frame(self._frame)
- self._top_frame.pack(fill='x')
-
- self._sp_year = ttk.Spinbox(self._top_frame, from_=1900, to=5000, width=6, textvariable=self._year)
- self._sp_year.grid(row=0, column=0, padx=2, pady=1)
- ttk.Label(self._top_frame, text='年').grid(row=0, column=1, padx=2)
-
- self._sp_month = ttk.Spinbox(self._top_frame, from_=1, to=12, width=4, textvariable=self._month)
- self._sp_month.grid(row=0, column=2, padx=3, pady=1)
- ttk.Label(self._top_frame, text='月').grid(row=0, column=3, padx=2)
-
-
- _middle_frame = ttk.Frame(self._frame)
- _middle_frame.pack(fill='x')
- self._initial_labels(_middle_frame)
-
- _bottom_frame = ttk.Frame(self._frame)
- _bottom_frame.pack(fill='x')
- _label_today = ttk.Label(_bottom_frame, text='今天: '+ str(datetime.date.today()))
- _label_today.pack(anchor='e', ipadx=8)
- _label_today.bind('<Motion>', self._on_label_today_motion)
- _label_today.bind('<Leave>', self._on_label_today_leave)
- _label_today.bind('<Button-1>', self._on_label_today_click)
-
- self._sp_year.configure(command=self._on_change)
- self._sp_year.bind('<KeyRelease>', self._on_spinbox_press)
- self._sp_year.bind('<Return>', self._on_year_return)
-
- self._sp_month.configure(command=self._on_change)
- self._sp_month.bind('<KeyRelease>', self._on_spinbox_press)
- self._sp_month.bind('<Return>', self._on_month_return)
-
-
- def _on_label_today_motion(self,event):
- event.widget.configure(foreground='DeepSkyBlue')
-
-
- def _on_label_today_leave(self, event):
- event.widget.configure(foreground=self.cget('background'))
-
-
- def _on_label_today_click(self, event):
- self._year.set(datetime.date.today().year)
- self._month.set(datetime.date.today().month)
- self._day.set(datetime.date.today().day)
-
- self._on_change() # 通过_on_change()重新设置self._day_list
- self._set_text()
- self._frame.withdraw()
-
-
- def _on_year_return(self,event):
- if self._year.get() > int(event.widget.cget('to')):
- self._year.set(event.widget.cget('to'))
- if self._year.get() < int(event.widget.cget('from')):
- self._year.set(event.widget.cget('from'))
-
- self._sp_month.focus()
- self._on_change()
-
-
- def _on_month_return(self,event):
- if self._month.get() > int(event.widget.cget('to')):
- self._month.set(event.widget.cget('to'))
- if self._month.get() < int(event.widget.cget('from')):
- self._month.set(event.widget.cget('from'))
-
- self._sp_year.focus()
- self._on_change()
-
-
- def _on_spinbox_press(self, event):
- txt = event.widget.get()
- if event.keysym:
- if not txt.isdigit():
- event.widget.set(''.join(i for i in txt if i.isdigit()))
-
-
- def _initial_labels(self, parent):
- """
- 初始化日期标签
- :param parent:
- :return:
- """
- week = ('一', '二', '三', '四', '五', '六', '日')
- for i, item in enumerate(week):
- ttk.Label(parent, text=item.center(3)).grid(row=0, column=i)
-
- self._day_list = []
- self._label_list = []
- self._set_day_list()
- for i, item in enumerate(self._day_list):
- label = ttk.Label(parent, text=str(item[2]).rjust(2))
- label.grid(row= i // 7 + 1, column=item[3], padx=1)
- label.hint = item #添加一个属性
- label['foreground'] ='Black'
- if item[1] != self._month.get():
- label['foreground'] = 'Gray'
- label['background'] = self.cget('background')
- if item[2] == self._day.get() and item[1] == self._month.get():
- label['background'] = 'DeepSkyBlue'
-
- label.bind('<Button-1>', self._on_label_click)
- label.bind('<Motion>', self._on_label_motion)
- label.bind('<Leave>', self._on_label_leave)
- self._label_list.append(label)
-
-
- def _set_day_list(self):
- """
- 设置日期列表
- :return:
- """
- year = self._year.get()
- month = self._month.get()
- for day in calendar.Calendar().itermonthdays4(year, month):
- self._day_list.append(day)
-
-
- if len(self._day_list) < 42:
- month = month + 1
- if month == 13:
- year = year +1
- month = 1
-
- for day2 in calendar.Calendar().itermonthdays4(year, month):
- if day2 in self._day_list:
- continue
-
- self._day_list.append(day2)
- if len(self._day_list) == 42:
- break
-
- ########################错误代码(修改于2023-10-09)######################
- # if len(self._day_list) == 28: #本月只有28天且一号为星期一
- # for day in calendar.Calendar().itermonthdays4(self._year.get(), self._month.get() + 1):
- # self._day_list.append(day)
- # if len(self._day_list) == 35:
- # break
-
-
- def _on_change(self):
- self._day_list.clear()
- self._set_day_list()
-
- for i, label in enumerate(self._label_list):
- label.hint = self._day_list[i]
- label['text'] = self._day_list[i][2]
-
- label['foreground'] ='Black'
- if self._day_list[i][1] != self._month.get():
- label['foreground'] = 'Gray'
-
- label['background'] = self.cget('background')
- if self._day_list[i][1] == self._month.get() and self._day_list[i][2] == self._day.get():
- label['background'] = 'DeepSkyBlue'
-
-
- def _set_text(self):
- """
- 设置日期至文本框
- :return:
- """
- readonly = False
- if 'readonly' in self.state():
- readonly = True
- self.state(['!readonly'])
-
- txt = self._separator.join([str(self._year.get()), str(self._month.get()), str(self._day.get())])
- self.delete(0, 'end')
- self.insert(0, txt)
- if readonly:
- self.state(['readonly'])
-
-
- def _on_label_click(self, event):
- if event.widget.hint is not None:
- for label in self._label_list:
- if label != event.widget: # 刷新背景色(修改默认日期的背景色)
- label.configure(background=self.cget('background'))
-
- self._year.set(event.widget.hint[0])
- self._month.set(event.widget.hint[1])
- self._day.set(event.widget.hint[2])
-
- self._set_text()
- self._frame.withdraw()
-
-
- def _on_label_motion(self, event):
- if event.widget.hint is not None:
- event.widget.configure(background= 'SkyBlue')
- if event.widget.hint[1] == self._month.get() and event.widget.hint[2] == self._day.get():
- event.widget.configure(background='DeepSkyBlue')
-
-
- def _on_label_leave(self, event):
- if event.widget.hint is not None:
- event.widget.configure(background= self.cget('background'))
- if event.widget.hint[1] == self._month.get() and event.widget.hint[2] == self._day.get():
- event.widget.configure(background='DeepSkyBlue')
-
-
- def _set_date(self, text):
- """
- 设置日期
- :param text: 被设置的日期文本
- :return:
- """
- try:
- formatstr = self._separator.join(['%Y','%m','%d'])
- cur_date = datetime.datetime.strptime(text, formatstr)
- self._year.set(cur_date.date().year)
- self._month.set(cur_date.date().month)
- self._day.set(cur_date.date().day)
- self._set_text()
- except Exception:
- raise ValueError("%s不是一个合法的日期" % text)
-
- def _get_date(self):
- return self.get()
-
-
- def set_state(self, *args):
- """
- 设置状态
- :param args:
- :return:
- """
- if args:
- if ('disabled' in args) or ('readonly' in args):
- self.configure(cursor='arrow')
- elif ('!disabled' in args) or ('!readonly' in args):
- self.configure(cursor='xterm')
- self.state(args)
-
- date = property(_get_date, _set_date)

2. 测试主界面
- import tkinter as tk
- import datepicker
-
-
- class GUI:
-
- def __init__(self):
- self.root = tk.Tk()
- self.root.title('演示')
- self.root.geometry("300x230+300+150")
- self.interface()
-
- def interface(self):
- """"界面编写位置"""
- self.Label0 = tk.Label(self.root, text="日 期")
- self.Label0.grid(row=0, column=0, padx=2)
-
- self.date = datepicker.DatePicker(self.root, width='10')
- self.date.grid(row=0, column=1, padx=2)
-
- self.Button = tk.Button(self.root, text="获取日期", width=7, command=self.show)
- self.Button.grid(row=0, column=2, padx=2)
-
- self.text = tk.Text(self.root, width=30, height=10)
- self.text.grid(row=1, column=0, columnspan=3)
-
-
- def show(self):
- # 获取日期
- self.text.delete(0.0,'end')
- self.text.insert(1.0, f"日期1:{self.date.date}\n")
- self.text.insert(1.0, f"日期2:{self.date.date.replace('-', '/')}\n")
-
-
- if __name__ == '__main__':
- a = GUI()
- a.root.mainloop()

3. 运行
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。