当前位置:   article > 正文

python 自定义日历控件开发_self.configure(style='datepicker')

self.configure(style='datepicker')

       学习python期间,发现 tkinter没有自带的日期选择控件。决定自己的写一个日期控件,费尽周转,终于写了一个自己满意的日期控件。本着人人为我,我为人人的原则,欢迎大家转发,评论,及提出宝贵的建议和意见。严重反感复制别人作品来获取下载积分和关注等行为。

1.创建一个自定义DatePicker类,实现下拉日期选择

  1. import tkinter
  2. from tkinter import ttk
  3. import calendar
  4. import datetime
  5. class DatePicker(ttk.Entry):
  6. def __init__(self, master=None, **kw):
  7. self._separator = kw.pop('separator', '-')
  8. if self._separator not in ['-','/']:
  9. self._separator = '-'
  10. super().__init__(master, **kw)
  11. self.set_state(kw.get('state', ''))
  12. self.style = ttk.Style(self)
  13. self._setup_style()
  14. self.configure(style='DatePicker')
  15. self._year = tkinter.IntVar()
  16. self._year.set(datetime.date.today().year)
  17. self._month = tkinter.IntVar()
  18. self._month.set(datetime.date.today().month)
  19. self._day = tkinter.IntVar()
  20. self._day.set(datetime.date.today().day)
  21. self._set_text()
  22. self._frame = tkinter.Toplevel(self, background='gray', borderwidth=1)
  23. self._frame.withdraw() # 隐藏窗口
  24. self._frame.overrideredirect(True) # 显示下拉时,不带标题栏 (Toplevel是一个窗口)
  25. self._set_frame()
  26. self.bind('<Motion>', self._on_motion)
  27. self.bind('<Leave>', lambda e: self.state(['!active']))
  28. self.bind('<Button-1>', self._show_down_frame)
  29. self._top_frame.bind('<FocusOut>', self._on_focus_out)
  30. def _setup_style(self):
  31. """
  32. 设置样式(复制TCombobox样式)
  33. :return:
  34. """
  35. self.style.layout('DatePicker', self.style.layout('TCombobox'))
  36. conf = self.style.configure('TCombobox')
  37. if conf:
  38. self.style.configure('DatePicker', **conf)
  39. maps = self.style.map('TCombobox')
  40. if maps:
  41. self.style.map('DatePicker', **maps)
  42. def _on_motion(self, event):
  43. x, y = event.x, event.y
  44. if 'disabled' not in self.state():
  45. if self.identify(x, y) == 'Combobox.rightdownarrow':
  46. self.state(['active'])
  47. self.configure(cursor='arrow')
  48. else:
  49. self.state(['!active'])
  50. self.configure(cursor='xterm')
  51. def _show_down_frame(self, event):
  52. """
  53. 显示下拉
  54. :param event:
  55. :return:
  56. """
  57. if ('disabled' in self.state()) or (self.identify(event.x, event.y) != 'Combobox.rightdownarrow'):
  58. return
  59. if self._sp_year.winfo_ismapped(): # _btn被绘制(下拉打开状态)
  60. self._frame.withdraw() # 隐藏下拉窗口(关闭下拉)
  61. else:
  62. x = self.winfo_rootx()
  63. y = self.winfo_rooty()+ self.winfo_height()
  64. self._frame.geometry('+%i+%i' % (x,y))
  65. self._frame.deiconify() # 打开下拉(显示窗口)
  66. self._sp_year.focus_set()
  67. def _on_focus_out(self, event):
  68. if self.focus_get() == None and 'active' in self.state(): #下拉打开再次点击下拉
  69. pass
  70. else:
  71. self._frame.withdraw()
  72. def _set_frame(self):
  73. """
  74. 设置下拉界面
  75. :return:
  76. """
  77. self._top_frame = ttk.Frame(self._frame)
  78. self._top_frame.pack(fill='x')
  79. self._sp_year = ttk.Spinbox(self._top_frame, from_=1900, to=5000, width=6, textvariable=self._year)
  80. self._sp_year.grid(row=0, column=0, padx=2, pady=1)
  81. ttk.Label(self._top_frame, text='年').grid(row=0, column=1, padx=2)
  82. self._sp_month = ttk.Spinbox(self._top_frame, from_=1, to=12, width=4, textvariable=self._month)
  83. self._sp_month.grid(row=0, column=2, padx=3, pady=1)
  84. ttk.Label(self._top_frame, text='月').grid(row=0, column=3, padx=2)
  85. _middle_frame = ttk.Frame(self._frame)
  86. _middle_frame.pack(fill='x')
  87. self._initial_labels(_middle_frame)
  88. _bottom_frame = ttk.Frame(self._frame)
  89. _bottom_frame.pack(fill='x')
  90. _label_today = ttk.Label(_bottom_frame, text='今天: '+ str(datetime.date.today()))
  91. _label_today.pack(anchor='e', ipadx=8)
  92. _label_today.bind('<Motion>', self._on_label_today_motion)
  93. _label_today.bind('<Leave>', self._on_label_today_leave)
  94. _label_today.bind('<Button-1>', self._on_label_today_click)
  95. self._sp_year.configure(command=self._on_change)
  96. self._sp_year.bind('<KeyRelease>', self._on_spinbox_press)
  97. self._sp_year.bind('<Return>', self._on_year_return)
  98. self._sp_month.configure(command=self._on_change)
  99. self._sp_month.bind('<KeyRelease>', self._on_spinbox_press)
  100. self._sp_month.bind('<Return>', self._on_month_return)
  101. def _on_label_today_motion(self,event):
  102. event.widget.configure(foreground='DeepSkyBlue')
  103. def _on_label_today_leave(self, event):
  104. event.widget.configure(foreground=self.cget('background'))
  105. def _on_label_today_click(self, event):
  106. self._year.set(datetime.date.today().year)
  107. self._month.set(datetime.date.today().month)
  108. self._day.set(datetime.date.today().day)
  109. self._on_change() # 通过_on_change()重新设置self._day_list
  110. self._set_text()
  111. self._frame.withdraw()
  112. def _on_year_return(self,event):
  113. if self._year.get() > int(event.widget.cget('to')):
  114. self._year.set(event.widget.cget('to'))
  115. if self._year.get() < int(event.widget.cget('from')):
  116. self._year.set(event.widget.cget('from'))
  117. self._sp_month.focus()
  118. self._on_change()
  119. def _on_month_return(self,event):
  120. if self._month.get() > int(event.widget.cget('to')):
  121. self._month.set(event.widget.cget('to'))
  122. if self._month.get() < int(event.widget.cget('from')):
  123. self._month.set(event.widget.cget('from'))
  124. self._sp_year.focus()
  125. self._on_change()
  126. def _on_spinbox_press(self, event):
  127. txt = event.widget.get()
  128. if event.keysym:
  129. if not txt.isdigit():
  130. event.widget.set(''.join(i for i in txt if i.isdigit()))
  131. def _initial_labels(self, parent):
  132. """
  133. 初始化日期标签
  134. :param parent:
  135. :return:
  136. """
  137. week = ('一', '二', '三', '四', '五', '六', '日')
  138. for i, item in enumerate(week):
  139. ttk.Label(parent, text=item.center(3)).grid(row=0, column=i)
  140. self._day_list = []
  141. self._label_list = []
  142. self._set_day_list()
  143. for i, item in enumerate(self._day_list):
  144. label = ttk.Label(parent, text=str(item[2]).rjust(2))
  145. label.grid(row= i // 7 + 1, column=item[3], padx=1)
  146. label.hint = item #添加一个属性
  147. label['foreground'] ='Black'
  148. if item[1] != self._month.get():
  149. label['foreground'] = 'Gray'
  150. label['background'] = self.cget('background')
  151. if item[2] == self._day.get() and item[1] == self._month.get():
  152. label['background'] = 'DeepSkyBlue'
  153. label.bind('<Button-1>', self._on_label_click)
  154. label.bind('<Motion>', self._on_label_motion)
  155. label.bind('<Leave>', self._on_label_leave)
  156. self._label_list.append(label)
  157. def _set_day_list(self):
  158. """
  159. 设置日期列表
  160. :return:
  161. """
  162. year = self._year.get()
  163. month = self._month.get()
  164. for day in calendar.Calendar().itermonthdays4(year, month):
  165. self._day_list.append(day)
  166. if len(self._day_list) < 42:
  167. month = month + 1
  168. if month == 13:
  169. year = year +1
  170. month = 1
  171. for day2 in calendar.Calendar().itermonthdays4(year, month):
  172. if day2 in self._day_list:
  173. continue
  174. self._day_list.append(day2)
  175. if len(self._day_list) == 42:
  176. break
  177. ########################错误代码(修改于2023-10-09)######################
  178. # if len(self._day_list) == 28: #本月只有28天且一号为星期一
  179. # for day in calendar.Calendar().itermonthdays4(self._year.get(), self._month.get() + 1):
  180. # self._day_list.append(day)
  181. # if len(self._day_list) == 35:
  182. # break
  183. def _on_change(self):
  184. self._day_list.clear()
  185. self._set_day_list()
  186. for i, label in enumerate(self._label_list):
  187. label.hint = self._day_list[i]
  188. label['text'] = self._day_list[i][2]
  189. label['foreground'] ='Black'
  190. if self._day_list[i][1] != self._month.get():
  191. label['foreground'] = 'Gray'
  192. label['background'] = self.cget('background')
  193. if self._day_list[i][1] == self._month.get() and self._day_list[i][2] == self._day.get():
  194. label['background'] = 'DeepSkyBlue'
  195. def _set_text(self):
  196. """
  197. 设置日期至文本框
  198. :return:
  199. """
  200. readonly = False
  201. if 'readonly' in self.state():
  202. readonly = True
  203. self.state(['!readonly'])
  204. txt = self._separator.join([str(self._year.get()), str(self._month.get()), str(self._day.get())])
  205. self.delete(0, 'end')
  206. self.insert(0, txt)
  207. if readonly:
  208. self.state(['readonly'])
  209. def _on_label_click(self, event):
  210. if event.widget.hint is not None:
  211. for label in self._label_list:
  212. if label != event.widget: # 刷新背景色(修改默认日期的背景色)
  213. label.configure(background=self.cget('background'))
  214. self._year.set(event.widget.hint[0])
  215. self._month.set(event.widget.hint[1])
  216. self._day.set(event.widget.hint[2])
  217. self._set_text()
  218. self._frame.withdraw()
  219. def _on_label_motion(self, event):
  220. if event.widget.hint is not None:
  221. event.widget.configure(background= 'SkyBlue')
  222. if event.widget.hint[1] == self._month.get() and event.widget.hint[2] == self._day.get():
  223. event.widget.configure(background='DeepSkyBlue')
  224. def _on_label_leave(self, event):
  225. if event.widget.hint is not None:
  226. event.widget.configure(background= self.cget('background'))
  227. if event.widget.hint[1] == self._month.get() and event.widget.hint[2] == self._day.get():
  228. event.widget.configure(background='DeepSkyBlue')
  229. def _set_date(self, text):
  230. """
  231. 设置日期
  232. :param text: 被设置的日期文本
  233. :return:
  234. """
  235. try:
  236. formatstr = self._separator.join(['%Y','%m','%d'])
  237. cur_date = datetime.datetime.strptime(text, formatstr)
  238. self._year.set(cur_date.date().year)
  239. self._month.set(cur_date.date().month)
  240. self._day.set(cur_date.date().day)
  241. self._set_text()
  242. except Exception:
  243. raise ValueError("%s不是一个合法的日期" % text)
  244. def _get_date(self):
  245. return self.get()
  246. def set_state(self, *args):
  247. """
  248. 设置状态
  249. :param args:
  250. :return:
  251. """
  252. if args:
  253. if ('disabled' in args) or ('readonly' in args):
  254. self.configure(cursor='arrow')
  255. elif ('!disabled' in args) or ('!readonly' in args):
  256. self.configure(cursor='xterm')
  257. self.state(args)
  258. date = property(_get_date, _set_date)

2. 测试主界面

  1. import tkinter as tk
  2. import datepicker
  3. class GUI:
  4. def __init__(self):
  5. self.root = tk.Tk()
  6. self.root.title('演示')
  7. self.root.geometry("300x230+300+150")
  8. self.interface()
  9. def interface(self):
  10. """"界面编写位置"""
  11. self.Label0 = tk.Label(self.root, text="日 期")
  12. self.Label0.grid(row=0, column=0, padx=2)
  13. self.date = datepicker.DatePicker(self.root, width='10')
  14. self.date.grid(row=0, column=1, padx=2)
  15. self.Button = tk.Button(self.root, text="获取日期", width=7, command=self.show)
  16. self.Button.grid(row=0, column=2, padx=2)
  17. self.text = tk.Text(self.root, width=30, height=10)
  18. self.text.grid(row=1, column=0, columnspan=3)
  19. def show(self):
  20. # 获取日期
  21. self.text.delete(0.0,'end')
  22. self.text.insert(1.0, f"日期1:{self.date.date}\n")
  23. self.text.insert(1.0, f"日期2:{self.date.date.replace('-', '/')}\n")
  24. if __name__ == '__main__':
  25. a = GUI()
  26. a.root.mainloop()

3. 运行

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/716331
推荐阅读
相关标签
  

闽ICP备14008679号