PyQt5/6 PySide2/6 在任务栏编程,用于显示文字(图片)信息
本文使用PyQt5
演示,其他库如PySide2/6
,稍微改改就能用,因为其核心使用的是Win32gui
来获取一些系统信息
代码结构
本文中全部代码全在test_taskbar.py
这一个文件中编码,步骤中有变动的地方会注释标注,无改动的不会重复显示出来,需要看完整代码的,可直接移步到末尾。
一. 创建测试页面
1. 创建一个用于显示的窗口
首先我们需要创建一个窗口,用于存放可能需要显示的文字,图片信息
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@ File : test_taskbar.py
@ Author : yqbao
@ Version : V1.0.0
@ Description : 任务栏编程
"""
import commctrl
import win32gui
from PyQt5.QtCore import QTimer, Qt
from PyQt5.QtWidgets import QWidget, QLabel, QGridLayout
class TaskbarWidget(QWidget):
def __init__(self):
super().__init__()
self.gridLayout = QGridLayout(self)
self.gridLayout.setContentsMargins(0, -1, 0, -1) # 内边距
self.label = QLabel('Hello!')
self.label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
if __name__ == '__main__':
import sys
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
rc = TaskbarWidget()
rc.show()
sys.exit(app.exec_())
运行后,可以得到下面这样的窗口,窗口大小可自由改变
2. 给窗口增加一些限制
任务栏区域很小,所以适当的增加限制。
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@ File : test_taskbar.py
@ Author : yqbao
@ Version : V1.0.0
@ Description : 任务栏编程
"""
class TaskbarWidget(QWidget):
def __init__(self):
super().__init__()
self.setMaximumSize(100, 30) # 默认任务栏高是 40,于是我们设置窗口最大是 30
self.setWindowFlags(Qt.FramelessWindowHint) # 设置窗口为无边框
... # 忽略省略
此时再次运行,发现窗口变的很小,且没法移动,没法改变大小。但其实我们本身就不需要用鼠标来拖动
二. 将窗口设置到任务托盘区域中
这一步,就需要用到win32gui
中提供的 API 了,用户获取系统页面的信息,供我们使用。其实你会发现,系统任务栏分为多个窗口嵌套,就好比 WEB
中的 多个div
标签嵌套,所以我们必须一层层的找到我们需要的那个子窗口
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@ File : test_taskbar.py
@ Author : yqbao
@ Version : V1.0.0
@ Description : 任务栏编程
"""
class TaskbarWidget(QWidget):
def __init__(self):
super().__init__()
... # 忽略省略
self.init_set_taskbar() # 初始化设置
def init_set_taskbar(self):
"""初始化设置任务栏"""
self.m_h_taskbar = win32gui.FindWindow("Shell_TrayWnd", None) # 任务栏“Shell_TaryWnd”的窗口句柄
self.m_h_bar = m_h_bar = win32gui.FindWindowEx(self.m_h_taskbar, 0, "ReBarWindow32",
None) # 子窗口“ReBarWindow32”的窗口句柄
self.m_h_min = m_h_min = win32gui.FindWindowEx(m_h_bar, 0, "MSTaskSwWClass", None) # 子窗口“MSTaskSwWClass”的窗口句柄
self.b = b = win32gui.GetWindowRect(m_h_bar) # 获取m_hBar窗口尺寸b为[左,上,右,下]的数组
win32gui.MoveWindow(m_h_min, 0, 0, b[2] - b[0] - 75, b[3] - b[1], True) # 调整m_hMin的窗口大小,为我们的程序预留出位置
self.setGeometry(b[2] - b[0] - 75, 5, 75, b[3] - b[1]) # 调整我们自己的窗口到预留位置的大小
win32gui.SetParent(int(self.winId()), m_h_bar) # 将我们自己的窗口设置为m_hBar的子窗口
运行后,如下,我们的窗口已经被设置到了 任务栏中,并使其固定显示在最右侧,靠近托盘区域
三. 解决没法移动的问题
有个问题,那就是一旦我们启动程序,位置就是被固定的,没法手动拖动,当我们右侧托盘区域图标增加或减少时,我们的窗口要么会被遮挡,要么会在右侧留个巨大的空隙,所以为了解决这个问题,我这里提供了一种解决办法。
但是我认为这并不是最好的方案,所以如果你有更好的方案,欢迎评论留言。
方案原理:定时检查右侧托盘区域图标的数量,每个托盘图标的尺寸是固定的,所以通过数量可算出偏移量,最后应用偏移量,实现移动。
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@ File : test_taskbar.py
@ Author : yqbao
@ Version : V1.0.0
@ Description : 任务栏编程
"""
class TaskbarWidget(QWidget):
def __init__(self):
super().__init__()
... # 忽略省略
self.init_set_taskbar() # 初始化设置
self.icon_count = 0 # 默认图标数量
self.icon_status = True # 获取初始数量时的状态
self.timer_set_taskbar() # 定时设置任务栏
def timer_set_taskbar(self, interval: int = 200): # 200ms 检查一次
"""定时设置任务栏"""
self.time_set_taskbar = QTimer(self)
self.time_set_taskbar.setInterval(interval)
self.time_set_taskbar.timeout.connect(self.get_tray_icon_count)
self.time_set_taskbar.start() # 启动
def get_tray_icon_count(self):
# 获取托盘区域的窗口句柄
tray_notify_handle = win32gui.FindWindowEx(self.m_h_taskbar, 0, "TrayNotifyWnd", None)
sys_pager_handle = win32gui.FindWindowEx(tray_notify_handle, 0, "SysPager", None)
notification_area_handle = win32gui.FindWindowEx(sys_pager_handle, 0, "ToolbarWindow32", None)
# 获取托盘图标的数量
count = win32gui.SendMessage(notification_area_handle, commctrl.TB_BUTTONCOUNT, 0, 0)
if self.icon_status:
self.icon_count = count # 初始化
self.icon_status = False
if self.icon_count != count:
self.dynamic_set_taskbar(icon_count=count)
def dynamic_set_taskbar(self, icon_count=0):
"""动态设置任务栏"""
if icon_count and self.icon_count != 0:
x = self.icon_count - icon_count
self.b = (self.b[0], self.b[1], self.b[2] + (x * 24), self.b[3],)
self.icon_count = icon_count
win32gui.MoveWindow(self.m_h_min, 0, 0, self.b[2] - self.b[0] - 75, self.b[3] - self.b[1], True)
self.setGeometry(self.b[2] - self.b[0] - 75, 5, 75, self.b[3] - self.b[1])
win32gui.SetParent(int(self.winId()), self.m_h_bar)
def closeEvent(self, event):
self.time_set_taskbar.stop()
super(TaskbarWidget, self).closeEvent(event)
运行效果如下:
四. 使用此方案的项目
基于上述方案,诞生了此项目,项目地址见这里
五. 完整代码
本文所展示的功能,并不完善,你其实还可以增加很多功能,比如鼠标右键呼出设置菜单
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@ File : test_taskbar.py
@ Author : yqbao
@ Version : V1.0.0
@ Description : 任务栏编程
"""
import commctrl
import win32gui
from PyQt5.QtCore import QTimer, Qt
from PyQt5.QtWidgets import QWidget, QLabel, QGridLayout
class TaskbarWidget(QWidget):
def __init__(self):
super().__init__()
self.setMaximumSize(100, 30) # 默认任务栏高是 40,于是我们设置窗口最大是 30
self.setWindowFlags(Qt.FramelessWindowHint) # 设置窗口为无边框
self.gridLayout = QGridLayout(self)
self.gridLayout.setContentsMargins(0, -1, 0, -1) # 内边距
self.label = QLabel('Hello!')
self.label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.init_set_taskbar() # 初始化设置
self.icon_count = 0 # 默认图标数量
self.icon_status = True # 获取初始数量时的状态
self.timer_set_taskbar() # 定时设置任务栏
def init_set_taskbar(self):
"""设置任务栏"""
self.m_h_taskbar = win32gui.FindWindow("Shell_TrayWnd", None) # 任务栏“Shell_TaryWnd”的窗口句柄
self.m_h_bar = m_h_bar = win32gui.FindWindowEx(self.m_h_taskbar, 0, "ReBarWindow32",
None) # 子窗口“ReBarWindow32”的窗口句柄
self.m_h_min = m_h_min = win32gui.FindWindowEx(m_h_bar, 0, "MSTaskSwWClass", None) # 子窗口“MSTaskSwWClass”的窗口句柄
self.b = b = win32gui.GetWindowRect(m_h_bar) # 获取m_hBar窗口尺寸b为[左,上,右,下]的数组
win32gui.MoveWindow(m_h_min, 0, 0, b[2] - b[0] - 75, b[3] - b[1], True) # 调整m_hMin的窗口大小,为我们的程序预留出位置
self.setGeometry(b[2] - b[0] - 75, 5, 75, b[3] - b[1]) # 调整我们自己的窗口到预留位置的大小
win32gui.SetParent(int(self.winId()), m_h_bar) # 将我们自己的窗口设置为m_hBar的子窗口
def timer_set_taskbar(self, interval: int = 200): # 500ms
"""定时设置任务栏"""
self.time_set_taskbar = QTimer(self)
self.time_set_taskbar.setInterval(interval)
self.time_set_taskbar.timeout.connect(self.get_tray_icon_count)
self.time_set_taskbar.start() # 启动
def get_tray_icon_count(self):
# 获取托盘区域的窗口句柄
tray_notify_handle = win32gui.FindWindowEx(self.m_h_taskbar, 0, "TrayNotifyWnd", None)
sys_pager_handle = win32gui.FindWindowEx(tray_notify_handle, 0, "SysPager", None)
notification_area_handle = win32gui.FindWindowEx(sys_pager_handle, 0, "ToolbarWindow32", None)
# 获取托盘图标的数量
count = win32gui.SendMessage(notification_area_handle, commctrl.TB_BUTTONCOUNT, 0, 0)
if self.icon_status:
self.icon_count = count # 初始化
self.icon_status = False
if self.icon_count != count:
self.dynamic_set_taskbar(icon_count=count)
def dynamic_set_taskbar(self, icon_count=0):
"""动态设置任务栏"""
if icon_count and self.icon_count != 0:
x = self.icon_count - icon_count
self.b = (self.b[0], self.b[1], self.b[2] + (x * 24), self.b[3],)
self.icon_count = icon_count
win32gui.MoveWindow(self.m_h_min, 0, 0, self.b[2] - self.b[0] - 75, self.b[3] - self.b[1], True)
self.setGeometry(self.b[2] - self.b[0] - 75, 5, 75, self.b[3] - self.b[1])
win32gui.SetParent(int(self.winId()), self.m_h_bar)
def closeEvent(self, event):
self.time_set_taskbar.stop()
super(TaskbarWidget, self).closeEvent(event)
if __name__ == '__main__':
import sys
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
rc = TaskbarWidget()
rc.show()
sys.exit(app.exec_())
使用此方案的项目,见这里
本文章的原文地址
GitHub主页