欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

tk界面最小化到托盘后线程停止工作问题要如何解决呢?

程序员文章站 2022-04-14 14:57:37
我写了一个闹钟工具,最小化到托盘后,无法继续倒计时,到点后也无法提醒。请各位大神看看问题要如何解决呢?下面是代码:1、闹钟的代码,文件名alarm.pyimport ctypesimport threadingimport timeimport tkinter.messagebox as messageboxfrom tkinter import *from datetime import datetimefrom systrayicon import SysTrayIconclas...

我写了一个闹钟工具,最小化到托盘后,无法继续倒计时,到点后也无法提醒。请各位大神看看问题要如何解决呢?下面是代码:

1、闹钟的代码,文件名alarm.py

import ctypes import threading import time import tkinter.messagebox as messagebox from tkinter import * from datetime import datetime from systrayicon import SysTrayIcon class MyThread(threading.Thread): """
    启动线程执行函数列表
    用法如下:
    func_list = [f1, f2]
    args_list = [(1, 2), ('abbc', )]
    t1 = MyThread(func_list, args_list)
    t1.daemon = True
    t1.start()
    """ def __init__(self, func_list, args_list): """
        初始化线程成员变量
        :param func_list: [func1, func2, ..., func_n]
        :param args_list: [(a1_1, a1_2, ...), (a2_1, ...), ..., (an_1,)]
        """ if len(func_list) < len(args_list): return threading.Thread.__init__(self) self.__flag = threading.Event() # 用于暂停线程的标识 self.__flag.set() # 设置为True self.__running = threading.Event() # 用于停止线程的标识 self.__running.set() # 将running设置为True self.func_list = func_list
        self.args_list = args_list
        self.results = [] def run(self): if not self.func_list: return while self.__running.isSet(): if self.__flag.wait(): # 为True时立即返回, 为False时阻塞直到__flag为True后返回 break for idx, func in enumerate(self.func_list): res = func(*self.args_list[idx]) self.results.append(res) self.stop() def pause(self): self.__flag.clear() # 设置为False, 让线程阻塞 def resume(self): self.__flag.set() # 设置为True, 让线程停止阻塞 def stop(self): self.__flag.set() # 将线程从暂停状态恢复, 如何已经暂停的话 self.__running.clear() # 设置为False class AlarmClock(Tk): def __init__(self): super().__init__() self.SysTrayIcon = None self.title('我的闹钟') self.width = 280 self.height = 260 self.set_time = '' self.minsize(width=self.width, height=self.height) width = self.winfo_screenwidth() # 获得屏幕宽度 height = self.winfo_screenheight() # 获得屏幕高度 # 居中显示 self.geometry('%dx%d+%d+%d' % (self.width, self.height, \ (width - self.width) / 2, (height - self.height) / 2)) # 设置控件 Label(self, text='现在时间:', font=('Times', 15)).place(x=10, y=10) now = datetime.now() # 获取本地时间 self.now_time = str(now).split('.')[0] self.var_nowtime = StringVar(value=self.now_time) Label(self, textvariable=self.var_nowtime, font=('Times', 15), bg='#D6EAF8').place(x=40, y=50) # 闹钟的时间设置部分用了三个entry控件输入时分秒 Label(self, text='设置闹钟:', font=('Times', 15)).place(x=10, y=90) hour, minute = now.hour, now.minute if minute == 59: hour = hour+1 if hour+1 != 24 else 0 minute = -1 self.var_hour = StringVar(value='%02d' % hour) self.var_min = StringVar(value='%02d' % (minute+1)) self.var_sec = StringVar(value='00') self.entry_hour = Entry(self, textvariable=self.var_hour, font=('Times', 15), bg='#D6EAF8', width=3, justify='center') self.entry_hour.place(x=60, y=130) Label(self, text=':', font=('Times', 15)).place(x=105, y=130) self.entry_min = Entry(self, textvariable=self.var_min, font=('Times', 15), bg='#D6EAF8', width=3, justify='center') self.entry_min.place(x=120, y=130) Label(self, text=':', font=('Times', 15)).place(x=165, y=130) self.entry_sec = Entry(self, textvariable=self.var_sec, font=('Times', 15), bg='#D6EAF8', width=3, justify='center') self.entry_sec.place(x=180, y=130) # 因为只能设定一个闹钟,所以再次设定的话要重置 self.btn_replace = Button(self, text='重置', font=('Times', 12), width=5, command=self.reset) self.btn_replace.place(x=70, y=180) # 开关按钮控制闹钟的开和关,初始是未启动的状态,点击变成已启动 self.btn_begin = Button(self, text='启动', font=('Times', 12), width=5, command=self.beginning) self.btn_begin.place(x=150, y=180) self.info_label = Label(self, text=':', font=('Times', 12)) timer_thread = MyThread([self.timer], [(),]) timer_thread.daemon = True timer_thread.start() def minimize(self): self.bind('<Unmap>', lambda event: self.hide_window() if self.state() == 'iconic' else False) #窗口最小化判断,可以说是调用最重要的一步 def hide_window(self, icon = 'alarm.ico', hover_text = "Alarm"): '''隐藏窗口至托盘区,调用SysTrayIcon的重要函数''' #托盘图标右键菜单, 格式: ('name', None, callback),下面也是二级菜单的例子 #24行有自动添加‘退出’,不需要的可删除 #menu_options = (('更改 图标', None, s.switch_icon),  #                ('二级 菜单', None, (('更改 图标', None, s.switch_icon), ))) menu_options = () self.withdraw() #隐藏tk窗口 if self.SysTrayIcon: self.SysTrayIcon.update() #已经有托盘图标时调用 update 来更新显示托盘图标 else: self.SysTrayIcon = SysTrayIcon(icon, #图标 hover_text, #光标停留显示文字 menu_options, #右键菜单 on_quit=self.exit, #退出调用 tk_window=self, #Tk窗口 ) def show(self): self.wm_deiconify() def exit(self, _sysTrayIcon = None): self.destroy() def beginning(self): flag = self.btn_begin['text'] # 定义一个flag来表示闹钟的状态 # 如果是打开状态就什么也不做 if flag == '已启动': return # 如果是关闭状态,就把text变为已启动,三个输入框变为不可编辑 else: self.btn_begin['text'] = '已启动' self.entry_hour['state'] = DISABLED
            self.entry_min['state'] = DISABLED
            self.entry_sec['state'] = DISABLED # 获取三个输入框内的时间 self.set_time = '%02d:%02d:%02d' % (int(self.entry_hour.get()), int(self.entry_min.get()), int(self.entry_sec.get())) info = '您%s的闹钟已经设置成功!' % self.set_time
            self.info_label.place(x=20, y=220) self.info_label.config(text=info) messagebox.showinfo(title='设置成功', message=info) self.iconify() def reset(self): '''
        重置按钮把三个输入框内容变为00,同时变为可编辑状态,开关按钮变为启动
        ''' now = datetime.now() self.set_time = '' self.var_hour.set('%02d' % now.hour) self.var_min.set('%02d' % (now.minute+1)) self.var_sec.set('00') self.btn_begin['text'] = '启动' self.entry_hour['state'] = NORMAL
        self.entry_min['state'] = NORMAL
        self.entry_sec['state'] = NORMAL #messagebox.showinfo(title='重置成功', message='重置成功') self.info_label.config(text='') def timer(self): while True: self.now_time = str(datetime.now()).split('.')[0] self.var_nowtime.set(self.now_time) self.update() now = datetime.now() now_time = '%02d:%02d:%02d' % (now.hour, now.minute, now.second) if self.btn_begin['text'] == '已启动': # 设定时间和本地时间一致则弹出提示框 if self.set_time == self.now_time.split(' ')[-1]: self.show() messagebox.showinfo(title='闹钟提醒', message='[%s]时间到了!' % self.set_time) self.reset() time.sleep(1) if __name__ == '__main__': alarm = AlarmClock() alarm.wm_attributes('-topmost', 1) # TODO 最小化后需要如何继续执行线程? #alarm.minimize() alarm.mainloop() 

2、托盘类的代码,文件名systrayicon.py

import win32api, win32con, win32gui_struct, win32gui import os, tkinter as tk class SysTrayIcon (object): '''SysTrayIcon类用于显示任务栏图标''' QUIT = 'QUIT' SPECIAL_ACTIONS = [QUIT] FIRST_ID = 5320 def __init__(s, icon, hover_text, menu_options, on_quit, tk_window = None, default_menu_index=None, window_class_name=None): '''
        icon         需要显示的图标文件路径
        hover_text   鼠标停留在图标上方时显示的文字
        menu_options 右键菜单,格式: (('a', None, callback), ('b', None, (('b1', None, callback),)))
        on_quit      传递退出函数,在执行退出时一并运行
        tk_window    传递Tk窗口,s.root,用于单击图标显示窗口
        default_menu_index 不显示的右键菜单序号
        window_class_name  窗口类名
        ''' s.icon = icon
        s.hover_text = hover_text
        s.on_quit = on_quit
        s.root = tk_window

        menu_options = menu_options + (('退出', None, s.QUIT),) s._next_action_id = s.FIRST_ID
        s.menu_actions_by_id = set() s.menu_options = s._add_ids_to_menu_options(list(menu_options)) s.menu_actions_by_id = dict(s.menu_actions_by_id) del s._next_action_id

        s.default_menu_index = (default_menu_index or 0) s.window_class_name = window_class_name or "SysTrayIconPy" message_map = {win32gui.RegisterWindowMessage("TaskbarCreated"): s.restart, win32con.WM_DESTROY: s.destroy, win32con.WM_COMMAND: s.command, win32con.WM_USER+20 : s.notify,} # 注册窗口类。 window_class = win32gui.WNDCLASS() window_class.hInstance = win32gui.GetModuleHandle(None) window_class.lpszClassName = s.window_class_name
        window_class.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW; window_class.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW) window_class.hbrBackground = win32con.COLOR_WINDOW
        window_class.lpfnWndProc = message_map #也可以指定wndproc. s.classAtom = win32gui.RegisterClass(window_class) s.update() def update(s): '''显示任务栏图标,不用每次都重新创建新的托盘图标''' # 创建窗口。 hinst = win32gui.GetModuleHandle(None) style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU
        s.hwnd = win32gui.CreateWindow(s.classAtom, s.window_class_name, style, 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, 0, 0, hinst, None) win32gui.UpdateWindow(s.hwnd) s.notify_id = None s.refresh_icon() win32gui.PumpMessages() def _add_ids_to_menu_options(s, menu_options): result = [] for menu_option in menu_options: option_text, option_icon, option_action = menu_option if callable(option_action) or option_action in s.SPECIAL_ACTIONS: s.menu_actions_by_id.add((s._next_action_id, option_action)) result.append(menu_option + (s._next_action_id,)) else: result.append((option_text, option_icon, s._add_ids_to_menu_options(option_action), s._next_action_id)) s._next_action_id += 1 return result def refresh_icon(s): # 尝试找到自定义图标 hinst = win32gui.GetModuleHandle(None) if os.path.isfile(s.icon): icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
            hicon = win32gui.LoadImage(hinst, s.icon, win32con.IMAGE_ICON, 0, 0, icon_flags) else: # 找不到图标文件 - 使用默认值 hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION) if s.notify_id: message = win32gui.NIM_MODIFY else: message = win32gui.NIM_ADD
        s.notify_id = (s.hwnd, 0, win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, win32con.WM_USER+20, hicon, s.hover_text) win32gui.Shell_NotifyIcon(message, s.notify_id) def restart(s, hwnd, msg, wparam, lparam): s.refresh_icon() def destroy(s, hwnd = None, msg = None, wparam = None, lparam = None, exit = 1): nid = (s.hwnd, 0) win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, nid) win32gui.PostQuitMessage(0) # 终止应用程序。 if exit and s.on_quit: s.on_quit() #需要传递自身过去时用 s.on_quit(s) else: s.root.deiconify() #显示tk窗口 def notify(s, hwnd, msg, wparam, lparam): if lparam==win32con.WM_LBUTTONDBLCLK:# 双击左键 pass elif lparam==win32con.WM_RBUTTONUP: # 右键弹起 s.show_menu() elif lparam==win32con.WM_LBUTTONUP: # 左键弹起 s.destroy(exit = 0) return True """
        可能的鼠标事件:
          WM_MOUSEMOVE      #光标经过图标
          WM_LBUTTONDOWN    #左键按下
          WM_LBUTTONUP      #左键弹起
          WM_LBUTTONDBLCLK  #双击左键
          WM_RBUTTONDOWN    #右键按下
          WM_RBUTTONUP      #右键弹起
          WM_RBUTTONDBLCLK  #双击右键
          WM_MBUTTONDOWN    #滚轮按下
          WM_MBUTTONUP      #滚轮弹起
          WM_MBUTTONDBLCLK  #双击滚轮
        """ def show_menu(s): menu = win32gui.CreatePopupMenu() s.create_menu(menu, s.menu_options) pos = win32gui.GetCursorPos() win32gui.SetForegroundWindow(s.hwnd) win32gui.TrackPopupMenu(menu, win32con.TPM_LEFTALIGN, pos[0], pos[1], 0, s.hwnd, None) win32gui.PostMessage(s.hwnd, win32con.WM_NULL, 0, 0) def create_menu(s, menu, menu_options): for option_text, option_icon, option_action, option_id in menu_options[::-1]: if option_icon: option_icon = s.prep_menu_icon(option_icon) if option_id in s.menu_actions_by_id: item, extras = win32gui_struct.PackMENUITEMINFO(text=option_text, hbmpItem=option_icon, wID=option_id) win32gui.InsertMenuItem(menu, 0, 1, item) else: submenu = win32gui.CreatePopupMenu() s.create_menu(submenu, option_action) item, extras = win32gui_struct.PackMENUITEMINFO(text=option_text, hbmpItem=option_icon, hSubMenu=submenu) win32gui.InsertMenuItem(menu, 0, 1, item) def prep_menu_icon(s, icon): #加载图标。 ico_x = win32api.GetSystemMetrics(win32con.SM_CXSMICON) ico_y = win32api.GetSystemMetrics(win32con.SM_CYSMICON) hicon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, ico_x, ico_y, win32con.LR_LOADFROMFILE) hdcBitmap = win32gui.CreateCompatibleDC(0) hdcScreen = win32gui.GetDC(0) hbm = win32gui.CreateCompatibleBitmap(hdcScreen, ico_x, ico_y) hbmOld = win32gui.SelectObject(hdcBitmap, hbm) brush = win32gui.GetSysColorBrush(win32con.COLOR_MENU) win32gui.FillRect(hdcBitmap, (0, 0, 16, 16), brush) win32gui.DrawIconEx(hdcBitmap, 0, 0, hicon, ico_x, ico_y, 0, 0, win32con.DI_NORMAL) win32gui.SelectObject(hdcBitmap, hbmOld) win32gui.DeleteDC(hdcBitmap) return hbm def command(s, hwnd, msg, wparam, lparam): id = win32gui.LOWORD(wparam) s.execute_menu_option(id) def execute_menu_option(s, id): menu_action = s.menu_actions_by_id[id] if menu_action == s.QUIT: win32gui.DestroyWindow(s.hwnd) else: menu_action(s) class _Main: #调用SysTrayIcon的Demo窗口 def __init__(s): s.SysTrayIcon = None # 判断是否打开系统托盘图标 def main(s): #tk窗口 s.root = tk.Tk() s.root.bind("<Unmap>", lambda event: s.Hidden_window() if s.root.state() == 'iconic' else False) #窗口最小化判断,可以说是调用最重要的一步 s.root.protocol('WM_DELETE_WINDOW', s.exit) #点击Tk窗口关闭时直接调用s.exit,不使用默认关闭 s.root.resizable(0,0) #锁定窗口大小不能改变 s.root.mainloop() def switch_icon(s, _sysTrayIcon, icon = 'D:\\2.ico'): #点击右键菜单项目会传递SysTrayIcon自身给引用的函数,所以这里的_sysTrayIcon = s.sysTrayIcon #只是一个改图标的例子,不需要的可以删除此函数 _sysTrayIcon.icon = icon
        _sysTrayIcon.refresh_icon() def Hidden_window(s, icon = 'D:\\1.ico', hover_text = "SysTrayIcon.py Demo"): '''隐藏窗口至托盘区,调用SysTrayIcon的重要函数''' #托盘图标右键菜单, 格式: ('name', None, callback),下面也是二级菜单的例子 #24行有自动添加‘退出’,不需要的可删除 menu_options = (('更改 图标', None, s.switch_icon), ('二级 菜单', None, (('更改 图标', None, s.switch_icon), ))) s.root.withdraw() #隐藏tk窗口 if s.SysTrayIcon: s.SysTrayIcon.update() #已经有托盘图标时调用 update 来更新显示托盘图标 else: s.SysTrayIcon = SysTrayIcon(icon, #图标 hover_text, #光标停留显示文字 menu_options, #右键菜单 on_quit = s.exit, #退出调用 tk_window = s.root, #Tk窗口 ) def exit(s, _sysTrayIcon = None): s.root.destroy() print ('exit...') if __name__ == '__main__': Main = _Main() Main.main() 

本文地址:https://blog.csdn.net/weixin_44152831/article/details/108846799