一、实验目的
1、 了解和使用操作系统程序开发环境;
2、 熟悉常用的功能菜单命令;
3、 掌握图形化程序开发方法;
4、 深入理解操作系统中的时钟中断的含义及其具体使用。
二、实验内容:
1、安装Linux虚拟机,熟悉Linux系统下各种操作命令,包括软件安装命令,程序编译运行命令等。
2、在屏幕指定窗口区域绘制一辆小车(或载入一幅小车图片,去掉背景),让小车沿窗口左上角往右下角匀速移动。
具体要求如下:
(1)小车一定要是从左上角到右下角;
(2)移动是基本匀速的,且能通过按钮调节其速度,速度设置至少在2种以上;
(3)点击关闭按钮后,程序转入后台执行,程序对应图标出现在任务栏托盘区;
(4)右键点击托盘区程序对应图标,弹出菜单,点击“恢复”,则程序重新被切换到前台运行,点击“退出”,程序退出内存,停止运行;
(5)程序转入后台运行时,小车仍然在移动。只是不可见而已。
三、实验要求
1、复习教材有关内容。
2、掌握VC控制台应用程序的开发、调试步骤。
3、完成实验报告。
四、实验代码
结果展示
代码思路
创建界面使用的是tkinter库的窗口,先设置主窗口的各个参数,然后配置按钮的属性值,以及显现的小车图像。
要想使小车图像png运动起来,可以通过移动图像的坐标,然后在创建的窗口上使用while循环不断调用中断刷新窗口即可。注意设计边界条件,当小车运动到边界时重头开始运动。
然后通过keyboard库的add_hotkey函数监听键盘点击事件,然后触发绑定函数,更新图形的移动速度。
最后绑定窗口点击关闭事件,右击显示“恢复”和退出菜单栏以及对应的函数都在class SysTrayIcon中。
全部代码
import win32api, win32con, win32gui_struct, win32gui import os, tkinter as tk import keyboard import pygame from multiprocessing import Process import os, time import psutil # 设置小车运行速度 speed = 0.01 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, } # 注册窗口类。 wc = win32gui.WNDCLASS() wc.hInstance = win32gui.GetModuleHandle(None) wc.lpszClassName = s.window_class_name wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW; wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW) wc.hbrBackground = win32con.COLOR_WINDOW wc.lpfnWndProc = message_map # 也可以指定wndproc. s.classAtom = win32gui.RegisterClass(wc) def activation(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(title='软件已后台!', msg='点击重新打开', time=500) win32gui.PumpMessages() def refresh(s, title='', msg='', time=500): '''刷新托盘图标 title 标题 msg 内容,为空的话就不显示提示 time 提示显示时间''' 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, # 句柄、托盘图标ID win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP | win32gui.NIF_INFO, # 托盘图标可以使用的功能的标识 win32con.WM_USER + 20, hicon, s.hover_text, # 回调消息ID、托盘图标句柄、图标字符串 msg, time, title, # 提示内容、提示显示时间、提示标题 win32gui.NIIF_INFO # 提示用到的图标 ) win32gui.Shell_NotifyIcon(message, s.notify_id) 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 _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 restart(s, hwnd, msg, wparam, lparam): s.refresh() 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) # 终止应用程序。 # 判断是否后台来跳过tk操作 if exit and s.on_quit: s.on_quit() # 需要传递自身过去时用 s.on_quit(s) else: # print('0000000000') s.root.deiconify() # 显示tk窗口 def notify(s, hwnd, msg, wparam, lparam): '''鼠标事件,双击框体图标''' ''' 可能的鼠标事件: WM_MOUSEMOVE #光标经过图标 WM_LBUTTONDOWN #左键按下 WM_LBUTTONUP #左键弹起 WM_LBUTTONDBLCLK #双击左键 WM_RBUTTONDOWN #右键按下 WM_RBUTTONUP #右键弹起 WM_RBUTTONDBLCLK #双击右键 WM_MBUTTONDOWN #滚轮按下 WM_MBUTTONUP #滚轮弹起 WM_MBUTTONDBLCLK #双击滚轮 ''' 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 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.title("A car is running") # 设置窗口宽高固定 s.root.resizable(0, 0) canvas = tk.Canvas(s.root,width=500, height=500, bg='white') # s.root.protocol("WM_DELETE_WINDOW", callbackClose) s.root.bind("<Unmap>", lambda event: s.Hidden_window() if s.root.state() == 'iconic' else False) # 窗口最小化判断,可以说是调用最重要的一步 s.root.protocol('WM_DELETE_WINDOW', s.callbackClose) # 点击Tk窗口关闭时直接调用s.exit,不使用默认关闭 s.root.resizable(0, 0) # 锁定窗口大小不能改变 # 正方形 # s.root.geometry("500x500") tk.Button(s.root, width=5, height=5, text='lowspeed', anchor='c', bg='green', fg='red', padx=5, pady=5, borderwidth=10, relief='ridge', compound='bottom', command=s.lowspeed).pack() tk.Button(s.root, width=5, height=5, text='midspeed', anchor='c', bg='green', fg='red', padx=5, pady=5, borderwidth=10, relief='ridge', compound='bottom', command=s.midspeed).pack() tk.Button(s.root, width=5, height=5, text='highspeed', anchor='c', bg='green', fg='red', padx=5, pady=5, borderwidth=10, relief='ridge', compound='bottom', command=s.highspeed).pack() # 配置 # 要打开的图像 image1 = "car.png" # 初始坐标 x0 = 50.0;y0 = 50.0 # 列表将包含所有的x和y坐标.到目前为止,他们只包含初始坐标 x = [x0];y = [y0] # 每次移动的速度或距离 vx = 1.0 # x 速度; vy = 1.0 # y 速度 # 边界,这里要考虑到图片的大小,要预留一半的长和宽 x_max = 450.0 y_max = 450.0 # 运行步数 range_min = 1;range_max = 2000 while (True): # 创建500次的x和y坐标 for t in range(range_min, range_max): keyboard.add_hotkey('1', s.highspeed) keyboard.add_hotkey('2', s.midspeed) keyboard.add_hotkey('3', s.lowspeed) # 新坐标等于旧坐标加每次移动的距离 new_x = x[t - 1] + vx new_y = y[t - 1] + vy # 如果已经越过边界,重头开始运动 if new_x >= x_max: new_x = x0 if new_y >= y_max: new_y = y0 # 添加新的值到列表 x.append(new_x) y.append(new_y) # 开始使用tk绘图 canvas.pack() photo1 = tk.PhotoImage(file=image1) width1 = photo1.width() height1 = photo1.height() # 每次的移动 for t in range(range_min, range_max): canvas.create_image(x[t], y[t], image=photo1, tag="pic") canvas.update() # 暂停 update time,然后删除图像 time.sleep(speed) canvas.delete("all") s.root.mainloop() def callbackClose(self): self.Hidden_window() def highspeed(self): global speed # print("高速被点击") speed = 0.01 def midspeed(self): global speed speed = 0.05 def lowspeed(self): global speed speed = 0.3 def switch_icon(s, _sysTrayIcon): s.root.deiconify() # 显示tk窗口 Main = _Main() Main.main() # pause.resume() # 恢复子进程 # print('\n子进程已恢复运行') # _sysTrayIcon.destroy(s.root) def show_msg(s, title='标题', msg='内容', time=500): s.SysTrayIcon.refresh(title=title, msg=msg, time=time) def Hidden_window(s, icon='', 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 not s.SysTrayIcon: s.SysTrayIcon = SysTrayIcon( icon, # 图标 hover_text, # 光标停留显示文字 menu_options, # 右键菜单 on_quit=s.exit, # 退出调用 tk_window=s.root, # Tk窗口 ) s.SysTrayIcon.activation() def exit(s, _sysTrayIcon=None): s.root.destroy() print('exit...') if __name__ == '__main__': Main = _Main() Main.main() # 创建并启动子进程 # p = Process(target=_Main.main, args=('test',)) # p.start() # pid = p.pid # 获取子进程的pid # pause = psutil.Process(pid) # 传入子进程的pid
完善:代码解读
类SysTrayIcon
,用于创建并管理系统托盘图标及其右键菜单
初始化函数 __init__
def __init__(s, icon, hover_text, menu_options, on_quit, tk_window=None, default_menu_index=None, window_class_name=None):
初始化函数用于创建一个SysTrayIcon
对象。参数如下:
icon
: 图标文件路径。hover_text
: 鼠标停留在图标上方时显示的文字。menu_options
: 右键菜单,格式为元组的嵌套,其中每个元组表示一个菜单项,包括菜单项文本、图标、回调函数。on_quit
: 传递退出函数,在执行退出时一并运行。tk_window
: 传递Tk窗口,用于单击图标时显示窗口。default_menu_index
: 不显示的右键菜单序号。window_class_name
: 窗口类名。
激活函数 activation
def activation(s):
此函数用于激活任务栏图标,不用每次都重新创建新的托盘图标。在任务栏上创建一个窗口,并在任务栏上显示图标。
刷新函数 refresh
def refresh(s, title='', msg='', time=500):
refresh
函数用于刷新托盘图标。参数如下:
title
: 提示标题。msg
: 提示内容,如果为空则不显示提示。time
: 提示显示时间。
显示右键菜单函数 show_menu
def show_menu(s):
此函数用于显示右键菜单。
添加菜单项ID _add_ids_to_menu_options
def _add_ids_to_menu_options(s, menu_options):
该函数用于为每个菜单项添加唯一的ID。这些ID将用于处理菜单项的操作。
重启函数 restart
def restart(s, hwnd, msg, wparam, lparam):
此函数用于重新启动托盘图标。
销毁函数 destroy
def destroy(s, hwnd=None, msg=None, wparam=None, lparam=None, exit=1):
destroy
函数用于销毁托盘图标。如果 exit
为1,则执行退出操作。
通知函数 notify
def notify(s, hwnd, msg, wparam, lparam):
notify
函数处理鼠标事件,例如双击框体图标。
创建右键菜单函数 create_menu
def create_menu(s, menu, menu_options):
此函数用于创建右键菜单。它递归地创建子菜单项并设置其属性。
准备菜单图标函数 prep_menu_icon
def prep_menu_icon(s, icon):
prep_menu_icon
函数用于准备菜单图标。
命令处理函数 command
def command(s, hwnd, msg, wparam, lparam):
command
函数处理来自菜单的命令。
执行菜单项操作函数 execute_menu_option
def execute_menu_option(s, id):
execute_menu_option
函数执行菜单项操作,根据ID来执行相应的操作,例如退出应用程序。
下面是对_Main
类的代码进行详细解读,按照您提供的格式:
类 _Main
,用于创建Demo窗口和控制交互
初始化函数 __init__
def __init__(s):
初始化函数用于创建_Main
对象。这个类负责创建Demo窗口和控制与系统托盘图标的交互。在初始化函数中,有以下属性:
s.SysTrayIcon
: 用于判断是否已经打开系统托盘图标。初始值为None
。
主函数 main
def main(s):
主函数负责创建Demo窗口和控制Demo窗口的交互逻辑。以下是主要功能和部分重要变量的解释:
- 创建一个Tk窗口:
s.root
,用于展示Demo窗口。 - 设置窗口标题为 “A car is running”。
- 锁定窗口大小不可改变。
- 创建一个白色的画布
canvas
用于绘制图形。 - 设置窗口最小化时的回调函数,即
s.Hidden_window
函数。 - 设置点击窗口关闭按钮的回调函数,即
s.callbackClose
函数。 - 创建三个按钮用于控制速度,分别是 “lowspeed”, “midspeed”, 和 “highspeed”。
- 配置要加载的图像文件路径为 “car.png”。
- 定义初始坐标
x0
和y0
。 - 创建列表
x
和y
,用于存储坐标。 - 定义速度变量
vx
和vy
。 - 定义边界坐标
x_max
和y_max
。 - 定义运行步数范围,
range_min
和range_max
。
接下来,通过循环不断更新图形的位置,并在画布上绘制,然后删除之前的图形,以实现图形的移动效果。在不断更新图形的过程中,监测是否按下了键盘上的 “1”, “2”, 或 “3” 键,分别调用 s.highspeed
, s.midspeed
, 或 s.lowspeed
函数改变速度。
最后,使用tkinter
库的mainloop
函数来显示Demo窗口。
回调函数 callbackClose
def callbackClose(self):
回调函数 callbackClose
用于关闭Demo窗口,它会调用 s.Hidden_window
函数,将Demo窗口隐藏到系统托盘中。
设置速度函数
def highspeed(self): def midspeed(self): def lowspeed(self):
这三个函数分别设置不同的速度。它们通过修改全局变量 speed
来改变图形的移动速度。
switch_icon
函数
def switch_icon(s, _sysTrayIcon):
switch_icon
函数用于切换图标。它会显示Demo窗口并启动主函数 main
。
show_msg
函数
def show_msg(s, title='标题', msg='内容', time=500):
show_msg
函数用于显示消息提示框。它接受标题、消息内容和显示时间作为参数,并通过 SysTrayIcon
的 refresh
函数来刷新消息提示。
Hidden_window
函数
def Hidden_window(s, icon='', hover_text="SysTrayIcon.py Demo"):
Hidden_window
函数用于将Demo窗口隐藏到系统托盘中,调用 SysTrayIcon
的重要函数。它创建系统托盘图标右键菜单 menu_options
,其中包括 “恢复” 和 “退出” 选项,以及一个二级菜单 “恢复(二级)”。然后,它隐藏Tk窗口,创建或激活系统托盘图标,并设置相应的图标、光标停留文字和右键菜单。最后,它调用系统托盘图标的 activation
函数来激活系统托盘图标。
exit
函数
def exit(s, _sysTrayIcon=None):
exit
函数用于退出应用程序,它会销毁Tk窗口并打印 “exit…”。