Python tkinter 下拉日历控件代码


Posted in Python onMarch 04, 2020

tkinter 下拉日历控件

网上找的不完善的Tk日历进行修改的,可以快捷的找到并返回日期。

效果如下图,上面的是控件,下面的是调用demo窗口

Python tkinter 下拉日历控件代码

如下所示:

# -*- coding: utf-8 -*- 
import calendar
import tkinter as tk
import tkinter.font as tkFont
from tkinter import ttk

datetime = calendar.datetime.datetime
timedelta = calendar.datetime.timedelta

class Calendar:

  def __init__(s, point = None, position = None):
    # point  提供一个基点,来确定窗口位置
    # position 窗口在点的位置 'ur'-右上, 'ul'-左上, 'll'-左下, 'lr'-右下
    #s.master = tk.Tk()
    s.master = tk.Toplevel()
    s.master.withdraw()
    fwday = calendar.SUNDAY

    year = datetime.now().year
    month = datetime.now().month
    locale = None
    sel_bg = '#ecffc4'
    sel_fg = '#05640e'

    s._date = datetime(year, month, 1)
    s._selection = None # 设置为未选中日期

    s.G_Frame = ttk.Frame(s.master)

    s._cal = s.__get_calendar(locale, fwday)

    s.__setup_styles()    # 创建自定义样式
    s.__place_widgets()   # pack/grid 小部件
    s.__config_calendar()  # 调整日历列和安装标记
    # 配置画布和正确的绑定,以选择日期。
    s.__setup_selection(sel_bg, sel_fg)

    # 存储项ID,用于稍后插入。
    s._items = [s._calendar.insert('', 'end', values='') for _ in range(6)]

    # 在当前空日历中插入日期
    s._update()

    s.G_Frame.pack(expand = 1, fill = 'both')
    s.master.overrideredirect(1)
    s.master.update_idletasks()
    width, height = s.master.winfo_reqwidth(), s.master.winfo_reqheight()
    if point and position:
      if  position == 'ur': x, y = point[0], point[1] - height
      elif position == 'lr': x, y = point[0], point[1]
      elif position == 'ul': x, y = point[0] - width, point[1] - height
      elif position == 'll': x, y = point[0] - width, point[1]
    else: x, y = (s.master.winfo_screenwidth() - width)/2, (s.master.winfo_screenheight() - height)/2
    s.master.geometry('%dx%d+%d+%d' % (width, height, x, y)) #窗口位置居中
    s.master.after(300, s._main_judge)
    s.master.deiconify()
    s.master.focus_set()
    s.master.wait_window() #这里应该使用wait_window挂起窗口,如果使用mainloop,可能会导致主程序很多错误

  def __get_calendar(s, locale, fwday):
    # 实例化适当的日历类
    if locale is None:
      return calendar.TextCalendar(fwday)
    else:
      return calendar.LocaleTextCalendar(fwday, locale)

  def __setitem__(s, item, value):
    if item in ('year', 'month'):
      raise AttributeError("attribute '%s' is not writeable" % item)
    elif item == 'selectbackground':
      s._canvas['background'] = value
    elif item == 'selectforeground':
      s._canvas.itemconfigure(s._canvas.text, item=value)
    else:
      s.G_Frame.__setitem__(s, item, value)

  def __getitem__(s, item):
    if item in ('year', 'month'):
      return getattr(s._date, item)
    elif item == 'selectbackground':
      return s._canvas['background']
    elif item == 'selectforeground':
      return s._canvas.itemcget(s._canvas.text, 'fill')
    else:
      r = ttk.tclobjs_to_py({item: ttk.Frame.__getitem__(s, item)})
      return r[item]

  def __setup_styles(s):
    # 自定义TTK风格
    style = ttk.Style(s.master)
    arrow_layout = lambda dir: (
      [('Button.focus', {'children': [('Button.%sarrow' % dir, None)]})]
    )
    style.layout('L.TButton', arrow_layout('left'))
    style.layout('R.TButton', arrow_layout('right'))

  def __place_widgets(s):
    # 标头框架及其小部件
    Input_judgment_num = s.master.register(s.Input_judgment) # 需要将函数包装一下,必要的
    hframe = ttk.Frame(s.G_Frame)
    gframe = ttk.Frame(s.G_Frame)
    bframe = ttk.Frame(s.G_Frame)
    hframe.pack(in_=s.G_Frame, side='top', pady=5, anchor='center')
    gframe.pack(in_=s.G_Frame, fill=tk.X, pady=5)
    bframe.pack(in_=s.G_Frame, side='bottom', pady=5)

    lbtn = ttk.Button(hframe, style='L.TButton', command=s._prev_month)
    lbtn.grid(in_=hframe, column=0, row=0, padx=12)
    rbtn = ttk.Button(hframe, style='R.TButton', command=s._next_month)
    rbtn.grid(in_=hframe, column=5, row=0, padx=12)
    
    s.CB_year = ttk.Combobox(hframe, width = 5, values = [str(year) for year in range(datetime.now().year, datetime.now().year-11,-1)], validate = 'key', validatecommand = (Input_judgment_num, '%P'))
    s.CB_year.current(0)
    s.CB_year.grid(in_=hframe, column=1, row=0)
    s.CB_year.bind('<KeyPress>', lambda event:s._update(event, True))
    s.CB_year.bind("<<ComboboxSelected>>", s._update)
    tk.Label(hframe, text = '年', justify = 'left').grid(in_=hframe, column=2, row=0, padx=(0,5))

    s.CB_month = ttk.Combobox(hframe, width = 3, values = ['%02d' % month for month in range(1,13)], state = 'readonly')
    s.CB_month.current(datetime.now().month - 1)
    s.CB_month.grid(in_=hframe, column=3, row=0)
    s.CB_month.bind("<<ComboboxSelected>>", s._update)
    tk.Label(hframe, text = '月', justify = 'left').grid(in_=hframe, column=4, row=0)

    # 日历部件
    s._calendar = ttk.Treeview(gframe, show='', selectmode='none', height=7)
    s._calendar.pack(expand=1, fill='both', side='bottom', padx=5)

    ttk.Button(bframe, text = "确 定", width = 6, command = lambda: s._exit(True)).grid(row = 0, column = 0, sticky = 'ns', padx = 20)
    ttk.Button(bframe, text = "取 消", width = 6, command = s._exit).grid(row = 0, column = 1, sticky = 'ne', padx = 20)
    
    
    tk.Frame(s.G_Frame, bg = '#565656').place(x = 0, y = 0, relx = 0, rely = 0, relwidth = 1, relheigh = 2/200)
    tk.Frame(s.G_Frame, bg = '#565656').place(x = 0, y = 0, relx = 0, rely = 198/200, relwidth = 1, relheigh = 2/200)
    tk.Frame(s.G_Frame, bg = '#565656').place(x = 0, y = 0, relx = 0, rely = 0, relwidth = 2/200, relheigh = 1)
    tk.Frame(s.G_Frame, bg = '#565656').place(x = 0, y = 0, relx = 198/200, rely = 0, relwidth = 2/200, relheigh = 1)

  def __config_calendar(s):
    # cols = s._cal.formatweekheader(3).split()
    cols = ['日','一','二','三','四','五','六']
    s._calendar['columns'] = cols
    s._calendar.tag_configure('header', background='grey90')
    s._calendar.insert('', 'end', values=cols, tag='header')
    # 调整其列宽
    font = tkFont.Font()
    maxwidth = max(font.measure(col) for col in cols)
    for col in cols:
      s._calendar.column(col, width=maxwidth, minwidth=maxwidth,
        anchor='center')

  def __setup_selection(s, sel_bg, sel_fg):
    def __canvas_forget(evt):
      canvas.place_forget()
      s._selection = None

    s._font = tkFont.Font()
    s._canvas = canvas = tk.Canvas(s._calendar, background=sel_bg, borderwidth=0, highlightthickness=0)
    canvas.text = canvas.create_text(0, 0, fill=sel_fg, anchor='w')

    canvas.bind('<Button-1>', __canvas_forget)
    s._calendar.bind('<Configure>', __canvas_forget)
    s._calendar.bind('<Button-1>', s._pressed)

  def _build_calendar(s):
    year, month = s._date.year, s._date.month

    # update header text (Month, YEAR)
    header = s._cal.formatmonthname(year, month, 0)

    # 更新日历显示的日期
    cal = s._cal.monthdayscalendar(year, month)
    for indx, item in enumerate(s._items):
      week = cal[indx] if indx < len(cal) else []
      fmt_week = [('%02d' % day) if day else '' for day in week]
      s._calendar.item(item, values=fmt_week)

  def _show_select(s, text, bbox):
    """为新的选择配置画布。"""
    x, y, width, height = bbox

    textw = s._font.measure(text)

    canvas = s._canvas
    canvas.configure(width = width, height = height)
    canvas.coords(canvas.text, (width - textw)/2, height / 2 - 1)
    canvas.itemconfigure(canvas.text, text=text)
    canvas.place(in_=s._calendar, x=x, y=y)

  def _pressed(s, evt = None, item = None, column = None, widget = None):
    """在日历的某个地方点击。"""
    if not item:
      x, y, widget = evt.x, evt.y, evt.widget
      item = widget.identify_row(y)
      column = widget.identify_column(x)

    if not column or not item in s._items:
      # 在工作日行中单击或仅在列外单击。
      return

    item_values = widget.item(item)['values']
    if not len(item_values): # 这个月的行是空的。
      return

    text = item_values[int(column[1]) - 1]
    if not text: # 日期为空
      return

    bbox = widget.bbox(item, column)
    if not bbox: # 日历尚不可见
      s.master.after(20, lambda : s._pressed(item = item, column = column, widget = widget))
      return

    # 更新,然后显示选择
    text = '%02d' % text
    s._selection = (text, item, column)
    s._show_select(text, bbox)

  def _prev_month(s):
    """更新日历以显示前一个月。"""
    s._canvas.place_forget()
    s._selection = None

    s._date = s._date - timedelta(days=1)
    s._date = datetime(s._date.year, s._date.month, 1)
    s.CB_year.set(s._date.year)
    s.CB_month.set(s._date.month)
    s._update()

  def _next_month(s):
    """更新日历以显示下一个月。"""
    s._canvas.place_forget()
    s._selection = None

    year, month = s._date.year, s._date.month
    s._date = s._date + timedelta(
      days=calendar.monthrange(year, month)[1] + 1)
    s._date = datetime(s._date.year, s._date.month, 1)
    s.CB_year.set(s._date.year)
    s.CB_month.set(s._date.month)
    s._update()

  def _update(s, event = None, key = None):
    """刷新界面"""
    if key and event.keysym != 'Return': return
    year = int(s.CB_year.get())
    month = int(s.CB_month.get())
    if year == 0 or year > 9999: return
    s._canvas.place_forget()
    s._date = datetime(year, month, 1)
    s._build_calendar() # 重建日历

    if year == datetime.now().year and month == datetime.now().month:
      day = datetime.now().day
      for _item, day_list in enumerate(s._cal.monthdayscalendar(year, month)):
        if day in day_list:
          item = 'I00' + str(_item + 2)
          column = '#' + str(day_list.index(day)+1)
          s.master.after(100, lambda :s._pressed(item = item, column = column, widget = s._calendar))

  def _exit(s, confirm = False):
    """退出窗口"""
    if not confirm: s._selection = None
    s.master.destroy()

  def _main_judge(s):
    """判断窗口是否在最顶层"""
    try:
      #s.master 为 TK 窗口
      #if not s.master.focus_displayof(): s._exit()
      #else: s.master.after(10, s._main_judge)

      #s.master 为 toplevel 窗口
      if s.master.focus_displayof() == None or 'toplevel' not in str(s.master.focus_displayof()): s._exit()
      else: s.master.after(10, s._main_judge)
    except:
      s.master.after(10, s._main_judge)

    #s.master.tk_focusFollowsMouse() # 焦点跟随鼠标

  def selection(s):
    """返回表示当前选定日期的日期时间。"""
    if not s._selection: return None

    year, month = s._date.year, s._date.month
    return str(datetime(year, month, int(s._selection[0])))[:10]

  def Input_judgment(s, content):
    """输入判断"""
    # 如果不加上==""的话,就会发现删不完。总会剩下一个数字
    if content.isdigit() or content == "":
      return True
    else:
      return False

if __name__ == '__main__':
  root = tk.Tk()

  width, height = root.winfo_reqwidth() + 50, 50 #窗口大小
  x, y = (root.winfo_screenwidth() - width )/2, (root.winfo_screenheight() - height)/2
  root.geometry('%dx%d+%d+%d' % (width, height, x, y )) #窗口位置居中

  date_str = tk.StringVar()
  date = ttk.Entry(root, textvariable = date_str)
  date.place(x = 0, y = 0, relx = 5/20, rely = 1/6, relwidth = 14/20, relheigh = 2/3)

  #Calendar((x, y), 'ur').selection() 获取日期,x,y为点坐标
  date_str_gain = lambda: [
    date_str.set(date)
    for date in [Calendar((x, y), 'ur').selection()] 
    if date]
  tk.Button(root, text = '日期:', command = date_str_gain).place(x = 0, y = 0, relx = 1/20, rely = 1/6, relwidth = 4/20, relheigh = 2/3)
  root.mainloop()

以上这篇Python tkinter 下拉日历控件代码就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Web服务器框架 Tornado简介
Jul 16 Python
Python中解析JSON并同时进行自定义编码处理实例
Feb 08 Python
在RedHat系Linux上部署Python的Celery框架的教程
Apr 07 Python
Django csrf 验证问题的实现
Oct 09 Python
python pands实现execl转csv 并修改csv指定列的方法
Dec 12 Python
python Django 创建应用过程图示详解
Jul 29 Python
python selenium爬取斗鱼所有直播房间信息过程详解
Aug 09 Python
基于Python+Appium实现京东双十一自动领金币功能
Oct 31 Python
Django 如何使用日期时间选择器规范用户的时间输入示例代码详解
May 22 Python
python怎么自定义捕获错误
Jun 29 Python
使用Python制作一盏 3D 花灯喜迎元宵佳节
Feb 26 Python
Python 实现Mac 屏幕截图详解
Oct 05 Python
Python Tkinter Entry和Text的添加与使用详解
Mar 04 #Python
Python列表倒序输出及其效率详解
Mar 04 #Python
python tkinter之顶层菜单、弹出菜单实例
Mar 04 #Python
python tkinter之 复选、文本、下拉的实现
Mar 04 #Python
自定义Django默认的sitemap站点地图样式
Mar 04 #Python
Python 窗体(tkinter)下拉列表框(Combobox)实例
Mar 04 #Python
Python3中的f-Strings增强版字符串格式化方法
Mar 04 #Python
You might like
php获取域名的google收录示例
2014/03/24 PHP
PHP中iconv函数转码时截断字符问题的解决方法
2015/01/21 PHP
隐性调用php程序的方法
2015/06/13 PHP
Yii2主题(Theme)用法详解
2016/07/23 PHP
php实现的三个常用加密解密功能函数示例
2017/11/06 PHP
JS判断不同分辨率调用不同的CSS样式文件实现思路及测试代码
2013/01/23 Javascript
当前页禁止复制粘贴截屏代码小集
2013/07/24 Javascript
js与css实现弹出层覆盖整个页面的方法
2014/12/13 Javascript
JavaSciprt中处理字符串之sup()方法的使用教程
2015/06/08 Javascript
跟我学习javascript的this关键字
2020/05/28 Javascript
详解JS正则replace的使用方法
2016/03/06 Javascript
Three.js学习之网格
2016/08/10 Javascript
jquery插件锦集【推荐】
2016/12/16 Javascript
浅谈Angular.js中使用$watch监听模型变化
2017/01/10 Javascript
浅谈微信JS-SDK 微信分享接口开发(介绍版)
2018/08/15 Javascript
React 实现车牌键盘的示例代码
2019/12/20 Javascript
微信小程序仿抖音视频之整屏上下切换功能的实现代码
2020/05/24 Javascript
[05:23]DOTA2-DPC中国联赛2月1日Recap集锦
2021/03/11 DOTA
python中global与nonlocal比较
2014/11/21 Python
对于Python的Django框架部署的一些建议
2015/04/09 Python
python删除过期文件的方法
2015/05/29 Python
python编程开发之类型转换convert实例分析
2015/11/13 Python
Python实现学校管理系统
2018/01/11 Python
python引入不同文件夹下的自定义模块方法
2018/10/27 Python
Python-numpy实现灰度图像的分块和合并方式
2020/01/09 Python
pytorch dataloader 取batch_size时候出现bug的解决方式
2020/02/20 Python
python爬虫开发之urllib模块详细使用方法与实例全解
2020/03/09 Python
如何理解Python中包的引入
2020/05/29 Python
python如何导入依赖包
2020/07/13 Python
全球速卖通俄罗斯站:AliExpress俄罗斯
2019/06/17 全球购物
纠纷协议书
2014/04/16 职场文书
保安2014年终工作总结
2014/12/06 职场文书
大学生学年个人总结
2015/02/15 职场文书
2016年敬老月活动总结
2016/04/05 职场文书
nginx安装以及配置的详细过程记录
2021/09/15 Servers
Spring Boot项目如何优雅实现Excel导入与导出功能
2022/06/10 Java/Android