可以的话帮忙点个star奥
目录
针对选定的题目——实现Windows系统任务管理器,我们经过多次讨论,最终选定python为开发语言。选择原因:一是小组6个成员中大部分同学在大创中使用的语言是python,对该语言比较熟悉;二是经过调研,我们发现python中有一些库正好可以是实现系统监控,性能分析,进程管理。
设计流程:首先使用Qt Designer进行UI界面设计,设计完成后将.ui文件转换为.py文件供python程序调用。然后根据题目要求,主要采用控件点击事件触发函数运⾏的⽅式进行响应设计。
我们对Windows10下的任务管理器进行了研究,再比对题目中要求我们实现的功能,从而确定了我们任务管理器的功能模块设计,其中有一些改进和创新。
图1.1 原任务管理器功能模块
图1.2 改进后的任务管理器功能模块
改进之处:实时状态监控中加入了GPU状态、网络状态和磁盘使用状态,同时在功能设计中增加了对数据更新速度的控制。
我们使用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。
一些功能需要弹出子窗口,如运行新任务和切换进程这两个模块。同样是先用QT Designer设计,而后由.ui文件生成.py文件。下面是对部分所使用控件的介绍:
QLineEdit:允许用户使用一组有用的编辑功能输入和编辑单行纯文本,包括撤消和重做、剪切和粘贴以及拖放。在运行新任务和切换进程中都需要用户进行输入操作,故用到此控件。
QPushButton:是实际开发中最常使用的一种按钮。
界面设计结果如下:
图2.4 子窗口(1)
图2.5 子窗口(2)
- 关键数据结构
主要的package为getpass,psutil,time,gpu,pyQt5。
- getuser() #获取电脑的用户名
- cpu_percent() #获取CPU使用率
- virtual_memory() #获取内存信息
- virtual_memory().total #总体内存
- virtual_memory().used#已使用内存
- virtual_memory().free#空闲内存
- mewrate= float(mem.used/mem.total) #内存使⽤率
- disk_usage('/').percent #磁盘使⽤率
- disk_usage('/').free #磁盘剩余容量
- disk_usage('/').used #磁盘已使用容量
- 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 用户页运行结果
- 关键数据结构
- pids()//获得所有进程的 PID 值
- pid()//获得当前进程的PID
- name()//获得当前进程的名字
- status()//获得当前进程的状态
- nice()//获得当前进程的优先级
- memory_info()//获得当前进程的占用内存大小
- 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 应用程序及进程页运行结果
以应用程序及用户数据的插入表中为例。
- 关键数据结构
主要的Python模块是PyQt5,psutil,win32gui。涉及的主要函数或数据:
- msg
QTableWidget接收的数据结构,其为一个进程的一组信息,在Python中使用tuple结构存储
- PyQt5.QWidgets.QTableWidgetItem(...)
根据Qt信号与槽机制,将接收的信号数据msg,转为QTableWidgetItem对象,以便将数据在QTableWidget中显示。其一个重载构造函数可以同时接收QICon类的对象和字符串,在GUI上能同时显示图标和字符串
- PyQt5.QWidgets .QTableWidget.setItem(...)
将QTableWidgetItem放置在表格指定位置上展示。
- psutil.Process(...)
根据进程pid获取Process类对象,该类将进程的一些信息,如pid、进程名称、状态等作为类的数据成员或成员函数,需要时调用相关数据成员或成员函数即可。
- psutil.Process.cmdline(...)
Windows系统运行进程,使用命令行进行命令批处理,从而能打开软件,创建进程并运行。该函数能获得进程在命令行的命令,返回值是列表,第一个元素是进程的exe文件的绝对路径,其余元素是exe的命令行参数。
- win32gui.ExtractIconEx(...)
该函数根据exe文件绝对路径,获取exe的大小图标句柄。
- PyQt5.QtWinExtras.QtWin.fromHICON(...)
该函数根据图标句柄,返回QPixmap类对象。
- 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
- 原理
实现数据的实时更新,使用了信号与槽机制。信号槽机制与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
- 关键数据结构
- exit() #退出当前进行的程序任务
- connect() #鼠标点击触发事件
- 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 退出管理器效果展示
(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()
(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 更新速度效果展示
(1)关键数据结构
主要Python模块——PyQt5。涉及的主要函数和变量:
- Va.xtb
用来设置隐藏列还是显示列。点击菜单项“小图标”时,xtb设为1。
- PyQt5.QWidgets.QTableWidget.setColumnHidden(...)
将QTableWidget的某一列,设置为显示或者隐藏
- 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 查看——详细列表效果展示
(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)——关机效果展示
(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)——注销效果展示
(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)——关于效果展示
(1) 关键数据结构
主要的package为:psutil,time,gpu,pyqt5,pynvml
- pynvml库获取电脑GPU的各种信息并做监视:
- nvmlDeviceGetMemoryInfo(handle) # 通过handle获取GPU 的信息
- 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)
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)
(1) 关键数据结构
主要的package为:psutil,time,pyqt5,主要函数:
- 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 性能页效果展示
(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 切换进程效果展示