Skip to content

SkyKingL/TaskExplorer

Repository files navigation

点击我,观看视频——初步设计(没有小图标)的展示

可以的话帮忙点个star奥

目录

1 总体设计

1.1 设计流程

1.2 模块设计

2 详细设计

2.1 用户界面设计

2.1.1 主窗口设计

2.1.2 子窗口设计

2.2 数据获取

2.2.1 用户状态数据

2.2.2 应用程序及进程数据

2.2.3 数据的插入

2.3 实时更新

2.4 菜单

2.4.1 文件(F)——运行新任务、退出管理器

2.4.2 选项(O)——置于顶层

2.4.3 查看(V)——更新速度、立即刷新

2.4.4 查看(V)——小图标、详细列表

2.4.5 关机(U)——关机

2.4.6 关机(U)——注销

2.4.7 帮助(H)——关于

2.5 性能页的设计

2.5.1 GPU性能图

2.5.2 CPU性能图

2.5.3 网络数据

2.6右键菜单——切换进程、结束进程

 


1 总体设计

1.1设计流程

针对选定的题目——实现Windows系统任务管理器,我们经过多次讨论,最终选定python为开发语言。选择原因:一是小组6个成员中大部分同学在大创中使用的语言是python,对该语言比较熟悉;二是经过调研,我们发现python中有一些库正好可以是实现系统监控,性能分析,进程管理。

设计流程:首先使用Qt Designer进行UI界面设计,设计完成后将.ui文件转换为.py文件供python程序调用。然后根据题目要求,主要采用控件点击事件触发函数运⾏的⽅式进行响应设计。

1.2模块设计

我们对Windows10下的任务管理器进行了研究,再比对题目中要求我们实现的功能,从而确定了我们任务管理器的功能模块设计,其中有一些改进和创新。

 

 

图1.1 原任务管理器功能模块

 

 

图1.2 改进后的任务管理器功能模块

改进之处:实时状态监控中加入了GPU状态、网络状态和磁盘使用状态,同时在功能设计中增加了对数据更新速度的控制。

 

2详细设计

2.1用户界面设计

2.1.1主窗口设计

我们使用Qt Designer进行了用户界面设计。Qt Designer是PyQt程序UI界面的实现工具,使用Qt Designer可以拖拽、点击完成GUI界面设计,并且设计完成的.ui程序可以转换成.py文件供python程序调用。下面是对所使用控件的介绍:

QMenuBar、QMenu、QAction: QMenuBar就是所有窗口的菜单栏,在此基础上添加不同的QMenu和QAction;QMenu是菜单栏里面菜单,可以显示文本和图标,但是并不负责执行操作,有点类似label的作用;QAction是Qt 将用户与界面进行交互的元素抽象为一种“动作”。所设计菜单的逻辑结构:文件(F)——运行新任务、退出管理器,选项(O)——置于顶层,查看(V)——立即刷新、更新速度(子菜单:高、正常、低)、关机(U)——关机、注销,帮助(H)——关于。

QTabWidget:一种带标签页的窗口,在这种类型的窗口中可以存储多个子窗口,每个子窗口的显示可以通过对应的标签进行切换。用此控件实现了应用程序及进程、用户、性能这三页的切换。

QTableWidget:一种表格控件,类似于我们经常使用的 Excel 表格,可以将数据以表格的方式展示给用户,在应用程序及进程页和用户页使用了该控件。应用程序及进程页的逻辑结构:映像名称、PID、状态、优先级、内存使用、线程数;用户页的逻辑结构:用户名称、CPU、内存、磁盘。

QLabel:用于显示文本或图像,不提供用户交互功能。用于显示进程数、CPU使用率、更新速度等文本信息。

PlotWidget: 在该控件上可通过pyqtgraph.PlotGraph.plot()函数作图。在性能页,使用它完成了CPU利用率和GPU利用率实时图像的绘制。

除了对界面中各控件的布局设计,我们还对控件进行了命名,为了在编码过程中更好得调用控件,如运行新任务——New_Task、CPU使用率——CPU_Use、关机——Shut_Down。同时为了提升美观,对一些控件插入了特别的样式。

界面设计结果如下:

图2.1 主窗口(1)

图2.2 主窗口(2)

 

图2.3 主窗口(3)

 

而后使用在pycharm上配置好的pyuic工具,将.ui文件转化为.py文件,即通过TE.ui转换生成TU_ui.py。

 

2.1.2子窗口设计

一些功能需要弹出子窗口,如运行新任务和切换进程这两个模块。同样是先用QT Designer设计,而后由.ui文件生成.py文件。下面是对部分所使用控件的介绍:

QLineEdit:允许用户使用一组有用的编辑功能输入和编辑单行纯文本,包括撤消和重做、剪切和粘贴以及拖放。在运行新任务和切换进程中都需要用户进行输入操作,故用到此控件。

QPushButton:是实际开发中最常使用的一种按钮。

界面设计结果如下:

图2.4 子窗口(1)

图2.5 子窗口(2)

2.2数据获取

2.2.1用户状态数据

  • 关键数据结构

主要的package为getpass,psutil,time,gpu,pyQt5。

  1. getuser() #获取电脑的用户名
  2. cpu_percent() #获取CPU使用率
  3. virtual_memory() #获取内存信息
  4. virtual_memory().total #总体内存
  5. virtual_memory().used#已使用内存
  6. virtual_memory().free#空闲内存
  7. mewrate= float(mem.used/mem.total) #内存使⽤率
  8. disk_usage('/').percent #磁盘使⽤率
  9. disk_usage('/').free #磁盘剩余容量
  10. disk_usage('/').used #磁盘已使用容量
  11. disk_usage('/').total #磁盘总容量
  • 设计流图

图2.6 用户数据获取设计流图

 

  • 代码展示

功能介绍:通过getpass,psutil,time,gpu等package,获取当前所运行的电脑用户信息,包括用户名,内存信息,磁盘信息等,将获取到的信息发送至所设计的用户UI接口,对接数据,进行信息的展示。

# Refresh_user.py > UserThread

from common import *

import getpass, psutil, time

from PyQt5.QtCore import QThread, pyqtSignal, QObject

from gpu import gpu

 

class UserThread(QObject):

    # 通过类成员对象定义信号

    update_user = pyqtSignal(tuple)

 

    # 处理业务逻辑 user

    def run_user(self):

        while True:

            usep = psutil.cpu_percent(interval=1)

            Va.cpu_data.append(usep)

            Va.gpu_data.append(gpu.gpu_memory_rate)

 

            key_info, net_in, net_out = self.get_rate(self.get_key)

            a = str(getpass.getuser())

            b = ' cpu 使⽤率:' + str(usep) + '%'

            mem = psutil.virtual_memory()

            mewrate = float(mem.used / mem.total)

            c = ' 总体内存:' + str(round(mem.total / 1024.0 / 1024.0 / 1024.0, 2)) + \

                'G\n 使⽤内存:' + str(round(mem.used / 1024.0 / 1024.0 / 1024.0, 2)) + \

                'G\n 空闲内存:' + str(round(mem.free / 1024.0 / 1024.0 / 1024.0, 2)) + \

                'G\n 使⽤率:' + str(round(mewrate * 100, 1)) + '%'

            d = ' 硬盘使⽤率:' + str(round(psutil.disk_usage('/').percent, 1)) + \

                '%\n 剩余容量:' + str(round(psutil.disk_usage('/').free / 1024.0 / 1024.0 / 1024.0, 2)) + \

                'G\n 使⽤容量:' + str(round(psutil.disk_usage('/').used / 1024.0 / 1024.0 / 1024.0, 2)) + \

                'G\n 总容量:' + str(round(psutil.disk_usage('/').total / 1024.0 / 1024.0 / 1024.0, 2)) + 'G'

 

            x = (a, b, c, d)

            self.update_user.emit(x)

            time.sleep(Va.speed)

 

  • 运行结果展示

图2.7 用户页运行结果

 

2.2.2应用程序及进程数据

  • 关键数据结构
  1. pids()//获得所有进程的 PID 值
  2. pid()//获得当前进程的PID
  3. name()//获得当前进程的名字
  4. status()//获得当前进程的状态
  5. nice()//获得当前进程的优先级
  6. memory_info()//获得当前进程的占用内存大小
  7. num_threads()//获得当前进程的⼦线程个数

 

  • 设计流图

图2.8 应用程序及进程数据获取设计流图

 

  • 代码展示

# Refresh_Process.py > ProcessThread

from PyQt5.QtCore import QThread, pyqtSignal, QObject

from PyQt5.QtGui import QPixmap

import win32gui

from ctypes import windll

import time

import psutil

from common import Va

 

class ProcessThread(QObject):

    # 通过类成员对象定义信号

    update_process = pyqtSignal(tuple)

    # 用于每次更新后控制写入行的位置

    # 处理业务逻辑 process

    def run_process(self):

        while True:

            for jci in psutil.pids():

                # windows有一些进程访问权限不够,做个容错处理

                try:

                    process = psutil.Process(jci)

                    a = str(process.name()) # 映像名称

                    b = str(process.pid)    # PID

                    c = str(process.status())   # 状态

                    d = str(process.nice()) # 优先级

                    d = self.ex(d)

                    e = str(round(process.memory_info().rss / 1024. / 1024.,2)) + 'MB' # 内存大小

                    f = str(process.num_threads())  # 线程数

                    x = (a, b, c, d, e, f)

                    self.update_process.emit(x)

                except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):

                    pass

            x = ("end", "end")

            self.update_process.emit(x)

            time.sleep(Va.speed)

 

    def ex(self, d):

        if d == "Priority.ABOVE_NORMAL_PRIORITY_CLASS":

            d = "较高"

        elif d == "Priority.BELOW_NORMAL_PRIORITY_CLASS":

            d = "较低"

        elif d == "Priority.HIGH_PRIORITY_CLASS":

            d = "最高"

        elif d == "Priority.IDLE_PRIORITY_CLASS":

            d = "空闲"

        elif d == "Priority.NORMAL_PRIORITY_CLASS":

            d = "正常"

        elif d == "Priority.PROCESS_MODE_BACKGROUND_BEGIN":

            d = "开始后台模式"

        elif d == "PROCESS_MODE_BACKGROUND_END":

            d = "结束后台模式"

        else:

            d = "实时"

        return d

 

  • 原理介绍

该代码实现的功能是应用进程及进程页数据的获取,主要用到的是psutil包。首先定义ProcessThread(QObject)类,方便在其他文件中被调用从而实现功能。ProcessThread类有一个类成员update_process,通过类成员对象定义信号,用于每次更新后控制写入行的位置。接着定义了一个类方法run_process,用以处理业务逻辑 process。在run_process方法中,首先通过psutil.pids()函数获取所有进程的pid。接着for循环所有进程,依次调用process.name()、process.pid()、process.status()、process.nice()、process.memory_info()、process.num_threads()函数,获得当前进程的PID、映像名称、状态优先级、占用内存大小(MB)、⼦线程个数,然后递交。其中,在获取当前进程优先级时,返回的信息并不直观(如Priority.HIGH_PRIORITY_CLASS等,具体见下图),所以在下面定义了一个ex(self, d)方法,用于将函数返回的信息转化成直观的优先级信息。由于windows有一些进程访问权限不够,所以在编写代码时进行了容错处理,如果进程因权限不够获取不了,那就放弃获取。在for循环结束后插入(end,end)元组,用于后续统计进程总个数用。

图2.9 优先级信息的映射

 

  • 效果展示

图2.10 应用程序及进程页运行结果

 

2.2.3 数据的插入

以应用程序及用户数据的插入表中为例。

  • 关键数据结构

主要的Python模块是PyQt5,psutil,win32gui。涉及的主要函数或数据:

  1. msg

QTableWidget接收的数据结构,其为一个进程的一组信息,在Python中使用tuple结构存储

  1. PyQt5.QWidgets.QTableWidgetItem(...)

根据Qt信号与槽机制,将接收的信号数据msg,转为QTableWidgetItem对象,以便将数据在QTableWidget中显示。其一个重载构造函数可以同时接收QICon类的对象和字符串,在GUI上能同时显示图标和字符串

  1. PyQt5.QWidgets .QTableWidget.setItem(...)

将QTableWidgetItem放置在表格指定位置上展示。

  1. psutil.Process(...)

根据进程pid获取Process类对象,该类将进程的一些信息,如pid、进程名称、状态等作为类的数据成员或成员函数,需要时调用相关数据成员或成员函数即可。

  1. psutil.Process.cmdline(...)

Windows系统运行进程,使用命令行进行命令批处理,从而能打开软件,创建进程并运行。该函数能获得进程在命令行的命令,返回值是列表,第一个元素是进程的exe文件的绝对路径,其余元素是exe的命令行参数。

  1. win32gui.ExtractIconEx(...)

该函数根据exe文件绝对路径,获取exe的大小图标句柄。

  1. PyQt5.QtWinExtras.QtWin.fromHICON(...)

该函数根据图标句柄,返回QPixmap类对象。

  1. PyQt5.QtGui.QIcon(...)

QIcon类的一个重载构造函数,能够接收QPixmap类对象作为参数,从而定义一个QICon对象

 

  • 设计流图

图2.11 数据插入设计流图

  • 代码展示

功能介绍:通过PyQt5、psutil、win32gui等Python模块,将进程有关的一些信息,比如pid、进程名称、小图标等信息放在GUI上显示,同时统计显示出的进程数量。

run_ui.py > MainWindow > Insert_Process函数

    # 应用程序及进程页

    def Insert_Process(self, msg):

        processpage = self.ui.tableWidget

        processpage.setRowCount(150)

        j = self.cnt

        #用一个特殊元组表示已经获取所有进程

        if msg[0] == "end":

            # 统计结束,P_Num标签写入进程数

            self.ui.P_num.setText("进程数: " + str(self.cnt))

            self.cnt = 0

            return

          # 详细列表显示

        if Va.xtb == 0:

            for i in range(1, 6):

                processpage.setColumnHidden(i, False)

            for i in range(6):

                if i == 0:

                    processpage.setColumnWidth(i, 180)

                else:

                    processpage.setColumnWidth(i, 100)

        else:   # 只显示小图标

            for i in range(1, 6):

                processpage.setColumnHidden(i,True)

            processpage.setColumnWidth(0, 700)

 

        for i in range(6):

 

            data = QTableWidgetItem(msg[i]) if i != 0 else \

                   QTableWidgetItem(QIcon(getPixmap(int(msg[1]), large=False)), msg[i])

           

            processpage.setItem(j, i, data)

            # data.setTextColor("green")  # 设置单元格文本颜色

            data.setForeground(QBrush(Qt.GlobalColor.darkGreen))

            data.setTextAlignment(QtCore.Qt.AlignCenter)  # 设置单元格居中

           

        processpage.horizontalHeader().setSectionResizeMode(QHeaderView.Fixed)  # 设置表格所有列固定宽度

 

        processpage.resizeRowsToContents()  # 使行高跟随内容改变

        processpage.verticalHeader().setVisible(False)  # 隐藏垂直标题

        # processpage.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)  # 设置表格所有列按比例随窗口自动缩放

        self.cnt = self.cnt + 1

2.3实时更新

  • 原理

实现数据的实时更新,使用了信号与槽机制。信号槽机制与Windows下消息机制类似,消息机制是基于回调函数,Qt中用信号与槽来代替函数指针,使程序更安全简洁。信号和槽机制是 Qt 的核心机制,可以让编程人员将互不相关的对象绑定在一起,实现对象之间的通信。

信号:当对象改变其状态时,信号就由该对象发射 (emit) 出去,而且对象只负责发送信号,它不知道另一端是谁在接收这个信号。这样就做到了真正的信息封装,能确保对象被当作一个真正的软件组件来使用。

槽:用于接收信号,而且槽只是普通的对象成员函数。一个槽并不知道是否有任何信号与自己相连接。而且对象并不了解具体的通信机制。

信号与槽的连接:所有从 QObject 或其子类 ( 例如 Qwidget ) 派生的类都能够包含信号和槽。因为信号与槽的连接是通过 QObject 的 connect() 成员函数来实现的。

在本工程中使用了pygtSignal接口实现信号与槽机制,其介绍如下图2.11。

图2.12 pygtSignal接口的使用

 

  • 代码展示

以应用程序及进程页的实时更新为例

# Refresh_Process.py > ProcessThread函数

class ProcessThread(QObject):

# 通过类成员对象定义信号

    update_process = pyqtSignal(tuple)

    # 用于每次更新后控制写入行的位置

    # 处理业务逻辑 process

    def run_process(self):

        while True:

            for jci in psutil.pids():

                # windows有一些进程访问权限不够,做个容错处理

                try:

                    process = psutil.Process(jci)

                    a = str(process.name()) # 映像名称

                    b = str(process.pid)    # PID

                    c = str(process.status())   # 状态

                    d = str(process.nice()) # 优先级

                    d = self.ex(d)

                    e = str(round(process.memory_info().rss / 1024. / 1024.,2)) + 'MB' # 内存大小

                    f = str(process.num_threads())  # 线程数

                    x = (a, b, c, d, e, f)

                    self.update_process.emit(x)

                except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):

                    pass

            x = ("end", "end")

            self.update_process.emit(x)

            time.sleep(Va.speed)

 

# run_ui.py

# 在def __init__(self)中调用

    def refresh_process(self):

        # 创建线程

        self.backend_1 = ProcessThread()

        # 连接信号

        self.cnt = 0

        self.backend_1.update_process.connect(self.Insert_Process)

        self.thread_1 = QThread()

        self.backend_1.moveToThread(self.thread_1)

        # 开始线程

        self.thread_1.started.connect(self.backend_1.run_process)

        self.thread_1.start()

# 应用程序及进程页

    def Insert_Process(self, msg):

        processpage = self.ui.tableWidget

        processpage.setRowCount(150)

        j = self.cnt

        #用一个特殊元组表示已经获取所有进程

        if msg[0] == "end":

            # 统计结束,P_Num标签写入进程数

            self.ui.P_num.setText("进程数: " + str(self.cnt))

            self.cnt = 0

            return

        if Va.xtb == 0:

            for i in range(1, 6):

                processpage.setColumnHidden(i, False)

            for i in range(6):

                if i == 0:

                    processpage.setColumnWidth(i, 180)

                else:

                    processpage.setColumnWidth(i, 100)

        else:

            for i in range(1, 6):

                processpage.setColumnHidden(i,True)

            processpage.setColumnWidth(0, 700)

 

        for i in range(6):

 

            data = QTableWidgetItem(msg[i]) if i != 0 else \

                   QTableWidgetItem(QIcon(getPixmap(int(msg[1]), large=False)), msg[1])

           

            processpage.setItem(j, i, data)

            # data.setTextColor("green")  # 设置单元格文本颜色

            data.setForeground(QBrush(Qt.GlobalColor.darkGreen))

            data.setTextAlignment(QtCore.Qt.AlignCenter)  # 设置单元格居中

           

        processpage.horizontalHeader().setSectionResizeMode(QHeaderView.Fixed)  # 设置表格所有列固定宽度

        processpage.resizeRowsToContents()  # 使行高跟随内容改变

        processpage.verticalHeader().setVisible(False)  # 隐藏垂直标题

        self.cnt = self.cnt + 1

 

2.4 菜单

2.4.1 文件(F)——运行新任务、退出管理器

  • 关键数据结构
  1. exit() #退出当前进行的程序任务
  2. connect() #鼠标点击触发事件
  3. ShellExecute() #打开外部程序或文件,执行所选择新任务

 

  • 运行流程图

图2.13 文件菜单运行流程图

 

  • 代码展示

功能介绍:点击菜单目录下的“文件(F)”,可以选择“运行新任务”,打开电脑中的外部程序或文件;选择“退出管理器”即退出管理器程序任务。

图2.14 文件菜单

 

# run_ui.py

# 文件(F)——退出管理器

self.ui.Exit.triggered.connect(File.exitui) #exitui(),调用时没加括号,说明仅调用功能,不返回结果

# 文件(F)——运行新任务

self.ui.New_Task.triggered.connect(self.serve_win) #serve_win(),调用时没加括号,说明仅调用功能,不返回结果

 

# 运行新任务的交互窗口

def serve_win(self):

  # self.form2 = QtWidgets.QWidget()

self.form2 = Serve_Window()

self.form2.show()

 

class File:

    def exitui(self):

        time.sleep(1)

        print('退出管理器...')

        sys.exit()

 

# RunTaskWin.py

import win32api

from PySide2.QtWidgets import *

from win_ui import Ui_Form

# 进程管理

class Serve_Window(QWidget):

    def __init__(self):

        # 基本初始化

        super().__init__()

        self.ui = Ui_Form()

        self.ui.setupUi(self)

        self.setWindowTitle("运行新任务")

        # 给需要使用的控件声明好所需函数

        self.ui.run.clicked.connect(self.startp)

        # 浏览文件目录

        self.ui.lookup.clicked.connect(self.lookup)

 

    def lookup(self):

        filePath, _ = QFileDialog.getOpenFileName(

            self,  # 父窗口对象

            "选择你要运行的文件",  # 标题

            r"data",  # 起始目录

            "(*.*)"  # 选择类型过滤项,过滤内容在括号中 #*.*所有文件

        )

        self.ui.enter.setText(filePath)

 

    # 运行新任务(新建进程)

    def startp(self):

        task = self.ui.enter.text()

        print(task)

        # 不为空

        if(len(task)!=0 and task.isspace()==False):

            win32api.ShellExecute(0, 'open', task, '', '', 1)

            # 正常执行后退出窗口

            self.close()

 

  • 效果展示

图2.15 运行新任务效果展示

图2.16 退出管理器效果展示

2.4.2 选项(O)——置于顶层

(1) 原理

使用self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)可以设置窗口置顶,使用self.setWindowFlags(QtCore.Qt.Widget)可以取消窗口置顶。

 

(2) 代码展示

run_ui.py > MainWindow > __init__函数

# 选项(O)——置于顶层
# self.flag 是True——非置顶,是False——置顶
self.flag = True
self.ui.Always_Front.triggered.connect(self.show_front)

run_ui.py > MainWindow > show_front函数

def show_front(self):
    # 置于顶层
    if(self.flag):
        self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
        self.flag = False
    # 取消置顶
    else:
        self.setWindowFlags(QtCore.Qt.Widget)
        self.flag = True
    self.show()

2.4.3 查看(V)——更新速度、立即刷新

(1)原理

所有的更新都在Refresh_Process.py和Refresh_user.py中,其中time.sleep(Va.speed)即每次获取数据后的延迟。实现对Va.speed的变化即实现更新速度的变化。其中Va来自于common.py,该类中的数据作为全局变量为各模块服务。而立即刷新的实现即使得Va.speed在短时间内特别小,刷新之后需恢复原速度。

(2)代码展示

run_ui.py > MainWindow > show_front函数

common.py > Va

# 一些全局参数
class Va:
    speed = 2
    gx = "正常"
    lastspeed = 0 # 立即刷新后需回复原速度

speedflag = 0 # 为1表示已执行过行立即刷新
    pid = 1000000
    cpu_data = []
    gpu_data = []
    bytes_sent = 0
    bytes_recv = 0
    xtb = 0 # 是否小图标的flag

run_ui.py > MainWindow > quick_refresh函数

# 立即刷新

def quick_refresh(self):
    Va.speed = 0.5

run_ui.py > MainWindow > Insert_user函数

if Va.speed != 0.5:
    Va.lastspeed = Va.speed # 记录上一次不是立即刷新的速度,同时保证已经刷新过
elif Va.speed == 0.5 and Va.speedflag ==0:
    Va.speedflag = 1 #如果是立即刷新对应的速度,这次刷新执行后就可以将速度调整回来
    return
elif Va.speed == 0.5 and Va.speedflag == 1:
    Va.speed = Va.lastspeed
    Va.speedflag = 0



# 把“更新速度”标签也更新
if Va.speed == 1:
    Va.gx = "高"
elif Va.speed == 2:
    Va.gx = "正常"
elif Va.speed == 3:
    Va.gx = "低"
self.ui.speed.setText("更新速度:" + Va.gx)

 

(3)设计流图

(4)运行结果

图2.17 更新速度效果展示

 

 

2.4.4 查看(V)——小图标、详细列表

(1)关键数据结构

主要Python模块——PyQt5。涉及的主要函数和变量:

  1. Va.xtb

用来设置隐藏列还是显示列。点击菜单项“小图标”时,xtb设为1。

  1. PyQt5.QWidgets.QTableWidget.setColumnHidden(...)

将QTableWidget的某一列,设置为显示或者隐藏

  1. PyQt5.QWidgets.QTableWidget.setColumnWith(...)

设置QTableWidget的某一列的列宽

(2) 设计流图

图2.18 查看——小图标和详细列表设计流图

(3)代码展示

功能介绍:点击菜单项“小图标”,将进程页面的信息,只留下有关“图标+进程名”的一列,隐藏其他列。而点击详细列表,则将应用程序及进程相关信息全部展示出来。


getPixMap.py

import psutil
from PyQt5.QtGui import QPixmap
from PyQt5.QtWinExtras import QtWin

import win32gui


# function getPixmap
# args: pid(int), large(bool)
#   pid: process id
#   large: return large or small icon handles
# return: PyQt5.QtGui.QPixmap
def getPixmap(pid: int, large=False) -> QPixmap:
    try:
        exe = psutil.Process(pid).cmdline()[0]
        # 使用 win32gui 从进程对应的 exe 文件提取图标
        # large 为大图标句柄列表,small 为小图标句柄列表
        piconLarge, piconSmall = win32gui.ExtractIconEx(exe, 0)
    except:
        return QPixmap("mr.png")

    if large:
        # 没有图标的进程,需要设置个统一的默认图标
        if len(piconLarge) == 0:
            return QPixmap("icon.png")

        # 释放小图标句柄
        for s in piconSmall:
            win32gui.DestroyIcon(s)
        # 获得大图标的QPixmap
        pixmap = QtWin.fromHICON(piconLarge[0])
        # 释放大图标句柄,避免长时间内未释放的图标句柄过多
        for l in piconLarge:
            win32gui.DestroyIcon(l)
        return pixmap
       
    if len(piconSmall) == 0:
        return QPixmap("mr.png")

    # 释放大图标句柄
    for l in piconLarge:
        win32gui.DestroyIcon(l)
    # 获得小图标的QPixmap
    pixmap = QtWin.fromHICON(piconSmall[0])
    # 释放小图标句柄
    for s in piconSmall:
        win32gui.DestroyIcon(s)
    return pixmap

 

run_ui.py > MainWindow类 >__init__函数

# 查看(V)——小图标
self.ui.S_Icon.triggered.connect(self.S_Icon)

# 查看(V)——详细列表
self.ui.full_list.triggered.connect(self.full_list)

run_ui.py > MainWindow类 > S_Icon函数

# 小图标
def S_Icon(self):
    Va.xtb = 1

run_ui.py > MainWindow类 > full_list函数

# 详细列表
def full_list(self):
    Va.xtb = 0

run_ui.py > MainWindow类 > Insert_Process函数

# 注:有关隐藏的上下文代码在应用程序及进程页的数据插入部分,只显示相关功能的代码

if Va.xtb == 0:
    for i in range(1, 6):
        processpage.setColumnHidden(i, False)
    for i in range(6):
        if i == 0:
            processpage.setColumnWidth(i, 180)
        else:
            processpage.setColumnWidth(i, 100)
else:
    for i in range(1, 6):
        processpage.setColumnHidden(i,True)
    processpage.setColumnWidth(0, 700)

 

 

(4)效果展示

图2.19 查看——小图标效果展示

图2.20 查看——详细列表效果展示

 

2.4.5 关机(U)——关机

(1) 关键函数

os.system('shutdown /s /t 0')  //设置关机倒计时为0秒

 

(2) 设计流图

图2.21 关机(U)——关机设计流图

(3) 原理介绍

system函数可以将字符串转化成命令在服务器上运行,os.system('shutdown /s /t 0') 语句可以设置关机倒计时为0秒,从而实现关机功能。其中,在用户点击关机后程序会进行询问,默认设置是NO操作,防止用户因为误触而导致意外关机。

 

(4) 代码展示

run_ui.py > MainWindow类 > guanji函数

# 关机——设置弹窗
def guanji(self):
    msgBox = QMessageBox()
    msgBox.setWindowTitle("关机")
    msgBox.setText("确定要关机吗?")
    msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
    msgBox.setDefaultButton(QMessageBox.No) #默认是No
    ret = msgBox.exec_()
    if ret == QMessageBox.Yes:
        # Save was clicked
        print('确定')
        # 立即关机
        os.system('shutdown /s /t 0')
    else:
        # cancel was clicked
        print('取消')

(5) 效果展示

图2.22 关机(U)——关机效果展示

2.4.6 关机(U)——注销

(1) 关键函数

os.system('shutdown /l /t 0') //设置注销倒计时为0秒

 

(2) 设计流图

图2.23 关机(U)—注销设计流图

(3) 原理介绍

注销的原理与关机的原理类似。system函数可以将字符串转化成命令在服务器上运行,os.system('shutdown /l /t 0') 语句可以设置注销倒计时为0秒,从而实现注销功能。其中,在用户点击注销后程序会进行询问,默认设置是NO操作,防止用户因为误触而导致意外注销。

 

(4) 代码展示

run_ui.py > MainWindow类 > zhuxiao函数

# 注销——设置弹窗
def zhuxiao(self):
    msgBox = QMessageBox()
    msgBox.setWindowTitle("注销")
    msgBox.setText("确定要注销吗?")
    msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
    msgBox.setDefaultButton(QMessageBox.No)  # 默认是No
    ret = msgBox.exec_()
    if ret == QMessageBox.Yes:
        # Save was clicked
        print('确定')
        # 立即注销
        # os.system('shutdown /l /t 0')
    else:
        # cancel was clicked
        print('取消')

  • 运行结果展示
 
   

图2.24 关机(U)——注销效果展示

2.4.7 帮助(H)——关于

(1) 关键数据结构

webbrowser.open() #打开对应的网址

(2) 设计流图

图2.25 帮助(H)——关于设计流图

 

 

(3) 代码展示

功能介绍:通过接收用户的鼠标点击事件,将界面跳转到Github官方网站,用户可在此处查找遇到的问题以及想要了解的信息。

 

#帮助(H)——关于

run_ui.py > MainWindow类 > __init__函数

self.ui.About.triggered.connect(self.openurl)

run_ui.py > MainWindow类 > openurl函数

def openurl(self):
    webbrowser.open("https://github.com/SkyKingL/TaskExplorer/tree/master")

 

 

(4)效果展示

点击后跳转至:

图2.26 帮助(H)——关于效果展示

 

2.5性能页的设计

2.5.1 GPU性能图

(1) 关键数据结构

主要的package为:psutil,time,gpu,pyqt5,pynvml

  1. pynvml库获取电脑GPU的各种信息并做监视:
  2. nvmlDeviceGetMemoryInfo(handle) # 通过handle获取GPU 的信息
  3. nvmlDeviceGetName(handle) # 通过handle获取显卡名

 

2.self.ui.GPU_Plot.plot()   #根据现有数组绘制GPU性能折线图,并显示出来

 

(2) 原理解释

通过pynvml,psutil,gpu等package,获取本电脑的显卡名称,显存空闲率和gpu内存读写满速使用率等各种信息,并将获取到的gpu内存读写满速利用率,通过函数以折线图的形式显示出来,和GPU的其它各种信息,一起发送至所涉及的UI接口,进行信息显示。

 

 

 

 

(3) 设计流图

图2.27 GPU性能图绘制设计流图

(4) 代码展示

gpu.py

import pynvml

 

class gpu:

    server_info_list = []

    UNIT = 1024 * 1024

    pynvml.nvmlInit()  # 初始化

    gpu_device_count = pynvml.nvmlDeviceGetCount()  # 获取Nvidia GPU块数

    for gpu_index in range(gpu_device_count):

        handle = pynvml.nvmlDeviceGetHandleByIndex(gpu_index)  # 获取GPU i的handle,后续通过handle来处理

        memery_info = pynvml.nvmlDeviceGetMemoryInfo(handle)  # 通过handle获取GPU 的信息

        server_info_list.append(

            {

                "gpu_id": gpu_index,  # gpu id

                "total": int(memery_info.total / UNIT),  # gpu 总内存

                "used": int(memery_info.used / UNIT),  # gpu使用内存

                "utilization": pynvml.nvmlDeviceGetUtilizationRates(handle).gpu  # 使用率

            }

        )

        gpu_name = str(pynvml.nvmlDeviceGetName(handle))

        gpu_memory_rate = pynvml.nvmlDeviceGetUtilizationRates(handle).memory

pynvml.nvmlShutdown()  # 关闭管理工具

Refresh_user.py > UserThread类 > run_user函数

Va.gpu_data.append(gpu.gpu_memory_rate)


run_ui.py > MainWindow类 > Insert_user函数

# 画性能图 在绘图控件中绘制图形

self.ui.label_2.setText("显卡名:" + gpu.gpu_name)

self.ui.label_3.setText("显存空闲率:" + str(round(gpu.memery_info.free / gpu.memery_info.total * 100, 2)) + '%')

self.ui.GPU_Plot.plot(Va.gpu_data)

2.5.2 CPU性能图

CPU性能图的绘制比较简单,在Refresh_user.py中获取最新CPU数据:

usep = psutil.cpu_percent(interval=1)

Va.cpu_data.append(usep)

再在run_ui.py的Insert_user函数中进行新数据的插入即可:

# 画性能图 在绘图控件中绘制图形
self.ui.CPU_record_Plot.plot(Va.cpu_data)

2.5.3网络数据

(1) 关键数据结构

主要的package为:psutil,time,pyqt5,主要函数:

  1. def get_key(self)  #获取网卡名称以及各网卡的发送和接收速率

 

(2) 设计流图

图2.28 网络数据设计流图

 

(3) 代码展示

通过get_key函数获取了各网卡的名称、接收速率和发送速率,并筛选只要WLAN的网络信息,将得到的信息通过UI接口显示出来   。

Refresh_user.py > UserThread类 > get_key函数

def get_key(self):
    key_info = psutil.net_io_counters(pernic=True).keys()  # 获取网卡名称
    recv = {}
    sent = {}
    for key in key_info:
        recv.setdefault(key, psutil.net_io_counters(pernic=True).get(key).bytes_recv)  # 各网卡接收的字节数
        sent.setdefault(key, psutil.net_io_counters(pernic=True).get(key).bytes_sent)  # 各网卡发送的字节数
    return key_info, recv, sent

Refresh_user.py > UserThread类 > run_user函数

key_info, net_in, net_out = self.get_rate(self.get_key)

for key in key_info:
    # 获取WLAN的网络信息即可
    if key == 'WLAN':
        Va.bytes_sent = round(net_in.get(key), 2)
        Va.bytes_recv = round(net_out.get(key), 2)

run_ui.py > MainWindow类 > Insert_user函数

self.ui.sent.setText(str(round(Va.bytes_sent, 2)) + " KB/s")
self.ui.recv.setText(str(round(Va.bytes_recv, 2)) + " KB/s")

 

(4) 效果展示

图2.29 性能页效果展示

2.6右键菜单——切换进程、结束进程

(1)功能介绍

在应用程序及进程页,使得表格可以拥有右键菜单功能。并设计右键菜单的内容为“切换至”和“结束进程”

 

(2)代码解释——功能设计

# 右键菜单
# 应用程序及进程 tableWidget 允许右键菜单
self.ui.tableWidget.setContextMenuPolicy(Qt.ActionsContextMenu)
send_option_1 = QAction(self.ui.tableWidget)
send_option_1.setText("切换至")
send_option_1.triggered.connect(self.change_win)  # 点击菜单中的“切换进程”执行的函数

# 左键点击后获取表格中数据
self.ui.tableWidget.itemClicked.connect(self.infos)

send_option_2 = QAction(self.ui.tableWidget)
send_option_2.setText("结束进程")
send_option_2.triggered.connect(self.killPid)  # 点击菜单中的“切换进程”执行的函数

(3)原理解释——结束进程

左键点击表格中任意位置后会将其数据捕获。我们点击PID列后再右键,即可在获取PID数据的前提下进行进程的切换和结束。其中有一定局限性,即只有左键点击PID列后才能执行右键菜单中的功能,其他列的点击均无效。针对此局限性已做异常处理,即通过对Va.pid这个全局变量的判断与赋值来使得只有PID列有效。

(4)代码解释——结束进程

# 终止进程
def killPid(self):
    if (Va.pid != 1000000):
        # 防止控制台输出乱码
        os.system("chcp 65001 > nul")
        # taskkill命令强制结束进程
        s = "taskkill /im " + str(Va.pid) + " /f"
        os.system(s)
        Va.pid = 1000000

(5)原理解释——切换进程

切换进程,即杀死原进程再运行新进程,由于程序未设置路径不合法的异常处理,需先运行指定新进程再杀死原进程(指定的新进程有问题就会抛出异常)。切换进程会产生一个新的窗口,则需要写一个类来运行此窗口,由于相似性,这里使用菜单中“运行新任务”已设计好的UI即可。

(6)代码展示

ChangePid.py

import os
import win32api
from PyQt5.QtWidgets import *
from win_ui import Ui_Form
from common import Va
# 进程管理——切换进程
class Change_Win(QWidget):
    def __init__(self):
        # 基本初始化
        super().__init__()
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        self.setWindowTitle("切换进程")
        self.ui.label_2.setText("切换至:")
        # 给需要使用的控件声明好所需函数
        self.ui.run.clicked.connect(self.startp)
        # 浏览文件目录
        self.ui.lookup.clicked.connect(self.lookup)

    def lookup(self):
        filePath, _ = QFileDialog.getOpenFileName(
            self,  # 父窗口对象
            "选择你要运行的文件",  # 标题
            r"data",  # 起始目录
            "(*.*)"  # 选择类型过滤项,过滤内容在括号中 #*.*所有文件
        )
        self.ui.enter.setText(filePath)

    # 切换进程——杀死原进程再运行新进程
    # 由于程序未设置路径不合法的异常处理
    # 先运行指定新进程再杀死原进程(指定的新进程有问题就会抛出异常)
    def startp(self):
        task = self.ui.enter.text()
        print(task)
        # 不为空
        if(len(task)!=0 and task.isspace()==False and Va.pid != 1000000):
            win32api.ShellExecute(0, 'open', task, '', '', 1)
            # 防止控制台输出乱码
            os.system("chcp 65001 > nul")
            # taskkill命令强制结束进程
            s = "taskkill /im " + str(Va.pid) + " /f"
            os.system(s)
            Va.pid = 1000000
            # 正常执行后退出窗口
            self.close()

在run_ui中对应的函数change_win,功能即对此窗口进行实例化后运行:

# 运行切换进程的交互窗口
def change_win(self):
    self.form3 = Change_Win()
    self.form3.show()

 

(7)效果展示

图2.30 结束进程效果展示

 

图2.31 切换进程效果展示

 

 

About

操作系统课设——任务管理器

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages