Python 代码:复制
# -*- coding = utf-8 -*- # Author : Abner # @Software : PyCharm import calendar import tkinter as tk from tkinter import ttk from tkinter import messagebox datetime = calendar.datetime.datetime timedelta = calendar.datetime.timedelta class Calendar: def __init__(self, position=None, point=None): self.selection_state = None # 用存放选中的日期(为日期序号,非具体日期) self.date_sele = None # 选中的具体日期 self.toplevel = tk.Toplevel() self.toplevel.bind("<FocusOut>", self._exit) # 组件绑定事件函数,焦点在组件外触发函数 self.toplevel.overrideredirect(1) # 无工具栏模式 self.toplevel.attributes('-alpha', 0.9) # 设置透明度 self.toplevel.withdraw() self.root_frame = ttk.Frame(self.toplevel) # 提前执行函数———————————————————————————— self.update_time() self.button_style() self.creat_frame() self.first_floor() self.second_floor() self.third_floor() self.fourth_floor() self.root_frame.pack(expand=1, fill='both') self.toplevel.update() # 问题终结者 update # 根据传入唤醒按键信息,设置窗口位置及大小 —————————————————————————— width, height = 260, 280 if point == 'LU': self.toplevel.geometry('%dx%d+%d+%d' % (width, height, position[0] - width, position[1] - height)) elif point == 'RU': self.toplevel.geometry('%dx%d+%d+%d' % (width, height, position[0], position[1] - height)) elif point == 'LD': self.toplevel.geometry('%dx%d+%d+%d' % (width, height, position[0] - width, position[1])) elif point == 'RD': self.toplevel.geometry('%dx%d+%d+%d' % (width, height, position[0], position[1])) self.toplevel.deiconify() self.toplevel.focus() self.toplevel.wait_window() def update_time(self): date = datetime.now() self.date_year = date.year self.date_month = date.month self.date_day = date.day self.date_hour = date.hour self.date_minute = date.minute self.date_second = date.second def button_style(self): # 这个设置按键样式的方法,一直没搞清楚,如果大家有专栏介绍的,可以推荐下 style = ttk.Style(self.root_frame) 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 creat_frame(self): self.first_frame = ttk.Frame(self.root_frame) self.first_frame.pack(pady=5) self.second_frame = ttk.Frame(self.root_frame) self.second_frame.pack(pady=5) self.third_frame = ttk.Frame(self.root_frame) self.third_frame.pack(pady=5) self.fourth_frame = ttk.Frame(self.root_frame) self.fourth_frame.pack(pady=5) def first_floor(self): # 年月选择栏 btn_left = ttk.Button(self.first_frame, style='L.TButton', command=self._prev_month) btn_left.grid(in_=self.first_frame, row=0, column=0, padx=5) btn_right = ttk.Button(self.first_frame, style='R.TButton', command=self._next_month) btn_right.grid(in_=self.first_frame, row=0, column=5, padx=5) self.combox_year = ttk.Combobox(self.first_frame, values=[str(year) for year in range(self.date_year, self.date_year - 11, -1)], width=5, state='readonly') self.combox_year.current(0) self.combox_year.grid(row=0, column=1, padx=5) lab_year = tk.Label(self.first_frame, text='年') lab_year.grid(row=0, column=2) self.combox_month = ttk.Combobox(self.first_frame, values=[str('%02d' % x) for x in range(1, 13)], width=5, state='readonly') self.combox_month.current(self.date_month - 1) # 0~11 self.combox_month.grid(row=0, column=3, padx=5) lab_month = tk.Label(self.first_frame, text='月') lab_month.grid(row=0, column=4) # 函数绑定下拉框 self.combox_year.bind('<<ComboboxSelected>>', self._update) self.combox_month.bind('<<ComboboxSelected>>', self._update) def second_floor(self): # 日历栏 year = int(self.combox_year.get()) month = int(self.combox_month.get()) self.tk_cv = tk.Canvas(self.second_frame, bg='white', width=225, height=160) self.tk_cv.pack() # 标题 cols = ['日', '一', '二', '三', '四', '五', '六'] for i in range(len(cols)): coord = 10 + i * 30, 10, 40 + i * 30, 30 rec_x = self.tk_cv.create_rectangle(coord, fill='red', outline='white') self.tk_cv.create_text(25 + i * 30, 20, text=cols[i]) def on_click(event): x, y = (event.x - 10) // 30, (event.y - 30) // 20 try: if self.selection_state is None: self.tk_cv.itemconfig(rect[y][x], fill='lightgreen') self.selection_state = rect[y][x] elif self.selection_state != rect[y][x]: self.tk_cv.itemconfig(self.selection_state, fill='white') self.tk_cv.itemconfig(rect[y][x], fill='lightgreen') self.selection_state = rect[y][x] elif self.selection_state and self.selection_state == rect[y][x]: self.tk_cv.itemconfig(rect[y][x], fill='white') self.selection_state = None except: pass def date_get(): c = calendar.TextCalendar(calendar.SUNDAY) # 括号内内容表示以哪天为起始 cal = c.monthdayscalendar(year, month) # 只由日组成的一组二维数组,每七天一组列表内嵌成一组列表 cal_dates = c.monthdatescalendar(year, month) # 由日期组成的二维数组 return cal, cal_dates xy = [10 + i * 30 for i in range(7)] dates_num, dates = date_get() rect = [[0] * 7 for _ in range(len(dates_num))] dates = sum(dates, []) dates_states = list(map(lambda x: [0, x], dates)) for i in range(len(dates_num)): for j, x in enumerate(xy): if dates_num[i][j] != 0: rect[i][j] = self.tk_cv.create_rectangle(x, 30 + i * 20, x + 30, 50 + i * 20, tags=('imgButton1')) self.tk_cv.itemconfig(rect[i][j], outline='white') # , fill='white', ) self.tk_cv.create_text(x + 15, 40 + i * 20, text='%02d' % dates_num[i][j], tags=('imgButton1')) if dates_num[i][j] == self.date_day: self.tk_cv.itemconfig(rect[i][j], fill='lightgreen') # 高亮显示当天日期 self.selection_state = rect[i][j] self.date_state = dict(zip(sum(rect, []), dates_states)) # {15: [0, datetime.date(2022, 8, 1)],...} self.tk_cv.tag_bind('imgButton1', '<Button-1>', on_click) def third_floor(self): # 时间栏 self.combox_hour = ttk.Combobox(self.third_frame, values=[str('%02d' % x) for x in range(24)], width=3, state='readonly') self.combox_hour.current(self.date_hour) self.combox_hour.grid(row=0, column=0, padx=5) lab_hour = tk.Label(self.third_frame, text='时') lab_hour.grid(row=0, column=1) self.combox_minu = ttk.Combobox(self.third_frame, values=[str('%02d' % x) for x in range(60)], width=3, state='readonly') self.combox_minu.current(self.date_minute) self.combox_minu.grid(row=0, column=2, padx=5) lab_minute = tk.Label(self.third_frame, text='分') lab_minute.grid(row=0, column=3) self.combox_sec = ttk.Combobox(self.third_frame, values=[str('%02d' % x) for x in range(60)], width=3, state='readonly') self.combox_sec.current(self.date_second) self.combox_sec.grid(row=0, column=4, padx=5) lab_sec = tk.Label(self.third_frame, text='秒') lab_sec.grid(row=0, column=5) def fourth_floor(self): # 确认与取消 btn_submit = ttk.Button(self.fourth_frame, text='提 交', command=lambda: self._exit(confirm=1)) btn_submit.grid(row=0, column=0, padx=10) btn_cancel = ttk.Button(self.fourth_frame, text='取 消', command=lambda: self._exit(confirm=2)) btn_cancel.grid(row=0, column=1, padx=10) def _submit(self): # 正常模式下的提交代码 if self.selection_state is None: self.date_sele = '未选择!' else: clock = '{}:{}:{}'.format(self.combox_hour.get(), self.combox_minu.get(), self.combox_sec.get()) self.date_sele = self.date_state[self.selection_state][1].strftime('%Y-%m-%d') + ' ' + clock def _update(self, *args): self.update_time() self.tk_cv.pack_forget() self.second_floor() self.combox_hour.current(self.date_hour) self.combox_minu.current(self.date_minute) self.combox_sec.current(self.date_second) def _prev_month(self): date = datetime(int(self.combox_year.get()), int(self.combox_month.get()), 1) # 数字1代表第几天 date = date - timedelta(days=1) date = datetime(date.year, date.month, 1) self.combox_year.set(date.year) self.combox_month.set(date.month) self._update() def _next_month(self): date = datetime(int(self.combox_year.get()), int(self.combox_month.get()), 1) # 数字1代表第几天 date = date + timedelta(days=calendar.monthrange(date.year, date.month)[1] + 1) date = datetime(date.year, date.month, 1) self.combox_year.set(date.year) self.combox_month.set(date.month) self._update() def date_selection(self, *args): if self.date_sele is None: return '未选择!' else: return self.date_sele def _exit(self, confirm=1): try: # 点击combox等内容时,会出现focusout导致窗口关闭,加一个if判断避免 if 'toplevel' not in str(self.toplevel.focus_displayof()) or confirm == 2: self.toplevel.destroy() elif confirm == 1: self._submit() self.toplevel.destroy() except: pass if __name__ == '__main__': # 主窗口 root = tk.Tk() root.title('时间计算器') root_width, root_height = 400, 110 screen_width = root.winfo_screenwidth() # 显示器大小 screen_height = root.winfo_screenheight() x, y = (screen_width - root_width) / 2, (screen_height - root_height) / 2 # 窗口默认起始坐标 root.geometry('%dx%d+%d+%d' % (root_width, root_height, x, y)) root.resizable(0, 0) point_x, point_y = 0, 0 # 唤醒按键的起始坐标 widget_width, widget_height = 0, 0 # 唤醒按键的宽高 # ----------------------------------- def set_position(event): global point_x, point_y, widget_width, widget_height # 根据点击事件获取组件的geometry,也可以用正则获得值 widget_geometry = event.widget.winfo_geometry().replace('x', '+') widget_width = int(widget_geometry.split('+')[0]) widget_height = int(widget_geometry.split('+')[1]) point_x, point_y = event.widget.winfo_rootx(), event.widget.winfo_rooty() def set_date(time_type): available_x = screen_width - point_x - widget_width # 宽度差 position = None # 传给日历组件的起始坐标信息 point = None # 传给日历组件的置放位置信息 if available_x >= 260 and point_y >= 280: # 日历组件默认宽高:260,280 position = (point_x + widget_width, point_y) point = 'RU' print('RU') elif available_x >= 260 and point_y < 280: position = (point_x + widget_width, point_y + widget_height) point = 'RD' print('RD') elif available_x < 260 and point_y >= 280: position = (point_x, point_y) point = 'LU' print('LU') elif available_x < 260 and point_y < 280: position = (point_x, point_y + widget_height) point = 'LD' print('LD') if time_type == 'start': date = Calendar(position=position, point=point).date_selection() start_time.set(date) elif time_type == 'end': date = Calendar(position=position, point=point).date_selection() end_time.set(date) elif time_type == 'diff': if len(start_time.get()) < 7 or len(end_time.get()) < 7: messagebox.showwarning(message='请先选择日期!') else: start = datetime.strptime(start_time.get(), '%Y-%m-%d %H:%M:%S') end = datetime.strptime(end_time.get(), '%Y-%m-%d %H:%M:%S') diff = (end - start) if end < start: diff_time.set('开始时间大于结束时间!') else: diff_time.set(diff) # 显示部分—————————————————————————————— start_time = tk.StringVar() start_time.set('请选择日期!') end_time = tk.StringVar() end_time.set('请选择日期!') diff_time = tk.StringVar() diff_time.set('请选择日期!') lab_start_time = ttk.Label(textvariable=start_time, font=25) lab_start_time.grid(row=0, column=1, padx=5, pady=5) lab_end_time = ttk.Label(textvariable=end_time, font=25) lab_end_time.grid(row=1, column=1, padx=5, pady=5) lab_diff_time = ttk.Label(textvariable=diff_time, font=25) lab_diff_time.grid(row=2, column=1, padx=5, pady=5) # 按键部分—————————————————————————————— # 如果有更好的方法将两个函数合并一个,绑定给按键的话,欢迎评论或私信,感激不尽 btn_start_time = ttk.Button(text='开始时间:', command=lambda: set_date(time_type='start')) btn_start_time.bind("<Button-1>", lambda event: set_position(event)) btn_start_time.grid(row=0, column=0, padx=5, pady=5) btn_end_time = ttk.Button(text='结束时间:', command=lambda: set_date(time_type='end')) btn_end_time.bind("<Button-1>", lambda event: set_position(event)) btn_end_time.grid(row=1, column=0, padx=5, pady=5) btn_diff_time = ttk.Button(text='开始计算:', command=lambda: set_date(time_type='diff')) btn_diff_time.grid(row=2, column=0, padx=5, pady=5) root.mainloop()