PyQt5/6 PySide2/6 在系统底部任务栏编程,用于显示文字(图片)信息

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_())

运行后,可以得到下面这样的窗口,窗口大小可自由改变
image

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(80, 30)  # 默认任务栏高是 40,于是我们设置窗口最大是 30
        self.setWindowFlags(Qt.FramelessWindowHint)  # 设置窗口为无边框

        ... # 忽略省略

此时再次运行,发现窗口变的很小,且没法移动,没法改变大小。但其实我们本身就不需要用鼠标来拖动
image

二. 将窗口设置到任务托盘区域中

这一步,就需要用到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”的窗口句柄
        # 子窗口“ReBarWindow32”的窗口句柄
        self.m_h_bar = win32gui.FindWindowEx(self.m_h_taskbar, 0, "ReBarWindow32", None)
        self.m_h_min = win32gui.FindWindowEx(self.m_h_bar, 0, "MSTaskSwWClass", None)  # 子窗口“MSTaskSwWClass”的窗口句柄
        self.b = win32gui.GetWindowRect(self.m_h_bar)  # 获取m_hBar窗口尺寸b为[左,上,右,下]的数组

        self.move_window()

    def move_window(self):
        # 调整m_hMin的窗口大小,为我们的程序预留出位置
        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)  # 将我们自己的窗口设置为m_hBar的子窗口

运行后,如下,我们的窗口已经被设置到了 任务栏中,并使其固定显示在最右侧,靠近托盘区域
image

三. 解决移动的问题

问题:一旦我们启动程序,位置就是被固定的,没法手动拖动,当我们右侧托盘区域图标增加或减少时,我们的窗口要么会被遮挡,要么会在右侧留个巨大的空隙,所以为了解决这个问题,我这里提供了两种解决办法。
但是我认为这并不是最好的方案,所以如果你有更好的方案,欢迎评论留言。

方案1:定时检查任务栏的尺寸(推荐)

方案原理:定时检查任务栏尺寸变化,此尺寸不包含托盘区域,故可以根据这个尺寸,移动我们的窗口到相应位置

#!/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.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_taskbar_size)
        self.time_set_taskbar.start()  # 启动

    def get_taskbar_size(self):
        """获取任务栏尺寸"""
        self.b_new = win32gui.GetWindowRect(self.m_h_bar)
        if self.b_new == self.b:  # 尺寸没变化,则直接返回
            return
        self.b = self.b_new
        self.move_window()

    def closeEvent(self, event):
        self.time_set_taskbar.stop()
        super(TaskbarWidget, self).closeEvent(event)

运行效果如下:
image

方案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.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_taskbar_size)  # 方案1
        # self.time_set_taskbar.timeout.connect(self.get_tray_icon_count) # 方案2
        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)

运行效果如下:
image

四. 相关项目

基于本片提到的内容,开发一个项目,GitHub 地址见这里Gitee 地址见这里

五. 完整代码

本文所展示的功能,并不完善,你其实还可以增加很多功能,比如鼠标右键呼出设置菜单

#!/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(80, 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”的窗口句柄
        # 子窗口“ReBarWindow32”的窗口句柄
        self.m_h_bar = win32gui.FindWindowEx(self.m_h_taskbar, 0, "ReBarWindow32", None)
        self.m_h_min = win32gui.FindWindowEx(self.m_h_bar, 0, "MSTaskSwWClass", None)  # 子窗口“MSTaskSwWClass”的窗口句柄
        self.b = win32gui.GetWindowRect(self.m_h_bar)  # 获取m_hBar窗口尺寸b为[左,上,右,下]的数组

        self.move_window()

    def move_window(self):
        # 调整m_hMin的窗口大小,为我们的程序预留出位置
        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)  # 将我们自己的窗口设置为m_hBar的子窗口

    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_taskbar_size)  # 方案1
        self.time_set_taskbar.timeout.connect(self.get_tray_icon_count)  # 方案2
        self.time_set_taskbar.start()  # 启动

    def get_taskbar_size(self):
        """获取任务栏尺寸"""
        self.b_new = win32gui.GetWindowRect(self.m_h_bar)
        if self.b_new == self.b:  # 尺寸没变化,则直接返回
            return
        self.b = self.b_new
        self.move_window()

    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
        self.move_window()

    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主页

posted @ 2024-10-12 11:15  星尘的博客  阅读(4)  评论(0编辑  收藏  举报