Python打造轻量级系统监控悬浮窗:从零到一实现CPU、内存、网速实时可视化

Python打造轻量级系统监控悬浮窗:从零到一实现CPU、内存、网速实时可视化
1. 为什么需要系统监控悬浮窗作为一个经常需要关注电脑性能的开发者我发现自己总是在任务管理器和各种监控软件之间来回切换。这不仅浪费时间还容易打断工作流。于是我开始寻找一种更优雅的解决方案——一个始终显示在桌面上的轻量级监控悬浮窗。这种悬浮窗有几个明显的优势实时可见不用频繁切换窗口就能看到关键指标低干扰可以调整透明度不影响其他工作自定义性强可以只显示你关心的数据资源占用低相比完整版的任务管理器要轻量得多我试过市面上的一些现成工具但要么功能太多太臃肿要么缺少我想要的功能点。于是决定用Python自己开发一个正好也分享一下开发过程中的经验和踩过的坑。2. 开发环境准备2.1 基础工具安装首先确保你已经安装了Python建议3.7版本。我实测过3.9和3.10都能完美运行。然后通过pip安装必要的库pip install PyQt5 psutil pyinstaller这里解释下这几个库的作用PyQt5用来创建图形界面psutil获取系统信息CPU、内存、网络等pyinstaller最后打包成exe用注意如果你遇到安装问题可以尝试使用清华镜像源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple PyQt5 psutil pyinstaller2.2 开发工具选择我推荐使用VS Code或PyCharm这类现代IDE它们对Qt开发有很好的支持。特别是VS Code的Python插件能提供很好的代码提示和调试体验。如果你打算长期做Qt开发也可以安装Qt Designer来可视化设计界面。不过我们这个项目比较简单直接代码写界面就够用了。3. 创建基础悬浮窗3.1 初始化无边框窗口我们先创建一个最基本的透明无边框窗口from PyQt5.QtWidgets import QApplication, QLabel, QWidget from PyQt5.QtCore import Qt import sys class MonitorWindow(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): # 设置窗口无边框和置顶 self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) # 设置窗口透明 self.setAttribute(Qt.WA_TranslucentBackground) # 设置初始大小和位置 self.setGeometry(100, 100, 200, 100) # 添加一个标签显示文字 self.label QLabel(Hello Monitor, self) self.label.setStyleSheet(color: white; font-size: 16px;) self.label.move(10, 10) if __name__ __main__: app QApplication(sys.argv) win MonitorWindow() win.show() sys.exit(app.exec_())这段代码创建了一个最简单的悬浮窗目前还只能显示静态文字。几个关键点FramelessWindowHint去掉了窗口边框WindowStaysOnTopHint让窗口始终在最上层WA_TranslucentBackground实现透明背景3.2 实现窗口拖拽功能因为去掉了标题栏我们需要自己实现窗口拖拽功能def mousePressEvent(self, event): if event.button() Qt.LeftButton: self.drag_position event.globalPos() - self.frameGeometry().topLeft() event.accept() def mouseMoveEvent(self, event): if hasattr(self, drag_position) and event.buttons() Qt.LeftButton: self.move(event.globalPos() - self.drag_position) event.accept()这段代码记录了鼠标按下时的位置并在移动时计算窗口应该移动到的位置。实测下来非常流畅和普通窗口的拖拽体验一致。4. 获取系统监控数据4.1 使用psutil获取基础信息psutil库是我们获取系统信息的主力工具它提供了跨平台的系统信息获取接口import psutil # 获取CPU使用率 cpu_percent psutil.cpu_percent(interval1) # 获取内存信息 mem psutil.virtual_memory() mem_percent mem.percent mem_used mem.used / (1024 ** 3) # 转换为GB # 获取网络速度 net_io psutil.net_io_counters() bytes_sent net_io.bytes_sent bytes_recv net_io.bytes_recv这里有个小技巧获取CPU使用率时设置interval1可以让第一次调用立即返回有意义的值否则第一次调用通常会返回0。4.2 计算实时网速计算网速需要记录前后两次的数据然后计算差值class NetSpeedMonitor: def __init__(self): self.last_bytes_sent 0 self.last_bytes_recv 0 def get_speed(self): current psutil.net_io_counters() sent_speed current.bytes_sent - self.last_bytes_sent recv_speed current.bytes_recv - self.last_bytes_recv self.last_bytes_sent current.bytes_sent self.last_bytes_recv current.bytes_recv return sent_speed, recv_speed使用时需要定期调用get_speed()比如每秒一次。返回值的单位是字节/秒可以自行转换为KB/s或MB/s。5. 实现数据实时更新5.1 使用QThread避免界面卡顿直接在主线程中循环获取数据会导致界面卡顿我们需要使用QThreadfrom PyQt5.QtCore import QThread, pyqtSignal class MonitorThread(QThread): data_updated pyqtSignal(dict) # 定义信号 def run(self): net_monitor NetSpeedMonitor() while True: # 获取各项数据 cpu_percent psutil.cpu_percent(interval1) mem psutil.virtual_memory() sent_speed, recv_speed net_monitor.get_speed() # 发射信号 self.data_updated.emit({ cpu: cpu_percent, mem: mem.percent, sent: sent_speed, recv: recv_speed }) self.msleep(1000) # 1秒更新一次5.2 在主窗口中使用线程class MonitorWindow(QWidget): def __init__(self): super().__init__() self.initUI() self.start_monitor_thread() def start_monitor_thread(self): self.monitor_thread MonitorThread() self.monitor_thread.data_updated.connect(self.update_data) self.monitor_thread.start() def update_data(self, data): # 更新界面显示 text fCPU: {data[cpu]}%\n text fMEM: {data[mem]}%\n text f↑ {data[sent]/1024:.1f}KB/s\n text f↓ {data[recv]/1024:.1f}KB/s self.label.setText(text)这样我们就实现了一个不卡界面的实时监控悬浮窗。每秒更新一次数据对系统资源的消耗几乎可以忽略不计。6. 高级功能实现6.1 贴边自动隐藏仿照QQ的贴边隐藏效果我们需要检测窗口位置def check_window_position(self): screen_rect QApplication.desktop().availableGeometry() window_rect self.geometry() # 判断是否靠近屏幕边缘 if window_rect.left() 0: self.hide_to_side(left) elif window_rect.right() screen_rect.right(): self.hide_to_side(right) elif window_rect.top() 0: self.hide_to_side(top) def hide_to_side(self, side): # 使用动画实现平滑隐藏 animation QPropertyAnimation(self, bgeometry) animation.setDuration(300) # 300毫秒动画 if side left: animation.setEndValue(QRect(-self.width()5, self.y(), self.width(), self.height())) elif side right: x QApplication.desktop().width() - 5 animation.setEndValue(QRect(x, self.y(), self.width(), self.height())) elif side top: animation.setEndValue(QRect(self.x(), -self.height()5, self.width(), self.height())) animation.start()对应的还需要实现鼠标移入时显示窗口的功能def enterEvent(self, event): if self.isHidden() or self.geometry().width() 10: self.show_normal() def show_normal(self): animation QPropertyAnimation(self, bgeometry) animation.setDuration(300) animation.setEndValue(QRect(self.normal_x, self.y(), self.width(), self.height())) animation.start()6.2 右键菜单功能添加一个简单的右键菜单def contextMenuEvent(self, event): menu QMenu(self) exit_action menu.addAction(退出) color_action menu.addAction(更换颜色) opacity_action menu.addAction(调整透明度) action menu.exec_(self.mapToGlobal(event.pos())) if action exit_action: self.close() elif action color_action: self.change_color() elif action opacity_action: self.adjust_opacity() def change_color(self): color QColorDialog.getColor() if color.isValid(): self.label.setStyleSheet(fcolor: {color.name()}; font-size: 16px;) def adjust_opacity(self): opacity, ok QInputDialog.getDouble(self, 透明度, 输入透明度(0.1-1.0), self.windowOpacity(), 0.1, 1.0, 1) if ok: self.setWindowOpacity(opacity)7. 打包发布7.1 使用PyInstaller打包pyinstaller -F -w -i monitor.ico monitor.py参数说明-F打包成单个exe文件-w不显示命令行窗口-i指定图标文件7.2 减小打包体积Python打包出来的exe通常比较大可以尝试以下方法减小体积使用UPX压缩pyinstaller -F -w --upx-dirupx_dir -i monitor.ico monitor.py创建虚拟环境只安装必要的包使用--exclude-module排除不需要的模块不过实测下来即使用尽各种优化方法一个简单的Qt程序打包后也至少有10MB左右。这是Python GUI程序的通病如果对体积特别敏感可能需要考虑其他语言实现。8. 最终效果与优化建议完成后的悬浮窗应该具备以下功能实时显示CPU、内存使用率显示上下行网速支持拖拽移动贴边自动隐藏右键菜单调整外观我在实际使用中发现几个优化点数据格式化网速超过1MB/s时自动切换单位显示预警功能CPU或内存超过阈值时改变文字颜色历史记录显示最近几分钟的资源使用曲线多显示器支持正确处理跨显示器的情况这些进阶功能实现起来也不复杂有兴趣的读者可以自行扩展。完整代码我已经上传到GitHub包含详细注释和几个预设的样式主题。