操作系统实验一:时钟中断程序设计

简介: 操作系统实验一:时钟中断程序设计

一、实验目的

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”。
  • 定义初始坐标 x0y0
  • 创建列表 xy,用于存储坐标。
  • 定义速度变量 vxvy
  • 定义边界坐标 x_maxy_max
  • 定义运行步数范围,range_minrange_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 函数用于显示消息提示框。它接受标题、消息内容和显示时间作为参数,并通过 SysTrayIconrefresh 函数来刷新消息提示。

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…”。

目录
相关文章
|
12天前
|
存储 程序员
操作系统的运行机制、中断和异常、系统调用
操作系统的运行机制、中断和异常、系统调用
21 1
|
21天前
|
存储 Linux Windows
【操作系统】实验十 设备驱动程序设计
【操作系统】实验十 设备驱动程序设计
14 0
|
21天前
【操作系统】实验九 写一个设备驱动程序
【操作系统】实验九 写一个设备驱动程序
9 1
|
21天前
【操作系统】实验八 proc文件系统
【操作系统】实验八 proc文件系统
11 1
|
21天前
|
Linux API
【操作系统】实验七 显示进程列表
【操作系统】实验七 显示进程列表
12 1
|
21天前
|
存储 机器学习/深度学习 算法
【操作系统】实验六 分析源代码
【操作系统】实验六 分析源代码
18 1
|
21天前
|
Linux 编译器 API
【操作系统】实验五 添加内核模块
【操作系统】实验五 添加内核模块
11 1
|
21天前
|
Linux 开发工具 C语言
【操作系统】实验四 增加Linux系统调用
【操作系统】实验四 增加Linux系统调用
35 1
|
21天前
|
NoSQL Ubuntu Linux
【操作系统】实验三 编译 Linux 内核
【操作系统】实验三 编译 Linux 内核
31 1
|
21天前
|
存储 Linux 网络安全
【操作系统】实验二 Proc文件系统
【操作系统】实验二 Proc文件系统
16 1