PyQt 是否有相当于 Toastr 的工具?

2023-12-11

我正在开发我的第一个 PyQt 项目,我想想出一种方法,在用户完成任务时向他们提供成功或错误消息。过去,我用过 JavascriptToastr我很好奇 Python 应用程序是否有类似的东西。我考虑过在 PyQt 中使用 QDialog 类,但如果可能的话,我宁愿不使用单独的窗口作为弹出窗口,因为即使是非模式对话框窗口也会分散用户的注意力。


UPDATE:我已经更新了代码,可以显示桌面通知(见下文)。

实现像小部件一样的桌面感知烤面包机并非不可能,但会带来一些与平台相关的问题。另一方面,客户端更容易。

我创建了一个小类,它能够根据当前小部件的顶级窗口显示通知,并且可以设置消息文本、图标以及通知是否可由用户关闭。我还添加了一个漂亮的不透明动画,这在此类系统中很常见。

它的主要用途是基于静态方法,类似于 QMessageBox 的用途,但也可以通过添加其他功能以类似的方式实现。

UPDATE

我意识到制作桌面通知并不是that很难(但是跨平台开发需要一些小心,我将把它留给程序员)。
以下是updated允许使用的代码None作为类的父级,使通知成为桌面小部件而不是现有 Qt 小部件的子小部件。如果您正在阅读本文并且对此类功能不感兴趣,只需检查原始(稍微简单的)代码的编辑历史记录即可。

from PyQt5 import QtCore, QtGui, QtWidgets
import sys

class QToaster(QtWidgets.QFrame):
    closed = QtCore.pyqtSignal()

    def __init__(self, *args, **kwargs):
        super(QToaster, self).__init__(*args, **kwargs)
        QtWidgets.QHBoxLayout(self)

        self.setSizePolicy(QtWidgets.QSizePolicy.Maximum, 
                           QtWidgets.QSizePolicy.Maximum)

        self.setStyleSheet('''
            QToaster {
                border: 1px solid black;
                border-radius: 4px; 
                background: palette(window);
            }
        ''')
        # alternatively:
        # self.setAutoFillBackground(True)
        # self.setFrameShape(self.Box)

        self.timer = QtCore.QTimer(singleShot=True, timeout=self.hide)

        if self.parent():
            self.opacityEffect = QtWidgets.QGraphicsOpacityEffect(opacity=0)
            self.setGraphicsEffect(self.opacityEffect)
            self.opacityAni = QtCore.QPropertyAnimation(self.opacityEffect, b'opacity')
            # we have a parent, install an eventFilter so that when it's resized
            # the notification will be correctly moved to the right corner
            self.parent().installEventFilter(self)
        else:
            # there's no parent, use the window opacity property, assuming that
            # the window manager supports it; if it doesn't, this won'd do
            # anything (besides making the hiding a bit longer by half a second)
            self.opacityAni = QtCore.QPropertyAnimation(self, b'windowOpacity')
        self.opacityAni.setStartValue(0.)
        self.opacityAni.setEndValue(1.)
        self.opacityAni.setDuration(100)
        self.opacityAni.finished.connect(self.checkClosed)

        self.corner = QtCore.Qt.TopLeftCorner
        self.margin = 10

    def checkClosed(self):
        # if we have been fading out, we're closing the notification
        if self.opacityAni.direction() == self.opacityAni.Backward:
            self.close()

    def restore(self):
        # this is a "helper function", that can be called from mouseEnterEvent
        # and when the parent widget is resized. We will not close the
        # notification if the mouse is in or the parent is resized
        self.timer.stop()
        # also, stop the animation if it's fading out...
        self.opacityAni.stop()
        # ...and restore the opacity
        if self.parent():
            self.opacityEffect.setOpacity(1)
        else:
            self.setWindowOpacity(1)

    def hide(self):
        # start hiding
        self.opacityAni.setDirection(self.opacityAni.Backward)
        self.opacityAni.setDuration(500)
        self.opacityAni.start()

    def eventFilter(self, source, event):
        if source == self.parent() and event.type() == QtCore.QEvent.Resize:
            self.opacityAni.stop()
            parentRect = self.parent().rect()
            geo = self.geometry()
            if self.corner == QtCore.Qt.TopLeftCorner:
                geo.moveTopLeft(
                    parentRect.topLeft() + QtCore.QPoint(self.margin, self.margin))
            elif self.corner == QtCore.Qt.TopRightCorner:
                geo.moveTopRight(
                    parentRect.topRight() + QtCore.QPoint(-self.margin, self.margin))
            elif self.corner == QtCore.Qt.BottomRightCorner:
                geo.moveBottomRight(
                    parentRect.bottomRight() + QtCore.QPoint(-self.margin, -self.margin))
            else:
                geo.moveBottomLeft(
                    parentRect.bottomLeft() + QtCore.QPoint(self.margin, -self.margin))
            self.setGeometry(geo)
            self.restore()
            self.timer.start()
        return super(QToaster, self).eventFilter(source, event)

    def enterEvent(self, event):
        self.restore()

    def leaveEvent(self, event):
        self.timer.start()

    def closeEvent(self, event):
        # we don't need the notification anymore, delete it!
        self.deleteLater()

    def resizeEvent(self, event):
        super(QToaster, self).resizeEvent(event)
        # if you don't set a stylesheet, you don't need any of the following!
        if not self.parent():
            # there's no parent, so we need to update the mask
            path = QtGui.QPainterPath()
            path.addRoundedRect(QtCore.QRectF(self.rect()).translated(-.5, -.5), 4, 4)
            self.setMask(QtGui.QRegion(path.toFillPolygon(QtGui.QTransform()).toPolygon()))
        else:
            self.clearMask()

    @staticmethod
    def showMessage(parent, message, 
                    icon=QtWidgets.QStyle.SP_MessageBoxInformation, 
                    corner=QtCore.Qt.TopLeftCorner, margin=10, closable=True, 
                    timeout=5000, desktop=False, parentWindow=True):

        if parent and parentWindow:
            parent = parent.window()

        if not parent or desktop:
            self = QToaster(None)
            self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint |
                QtCore.Qt.BypassWindowManagerHint)
            # This is a dirty hack!
            # parentless objects are garbage collected, so the widget will be
            # deleted as soon as the function that calls it returns, but if an
            # object is referenced to *any* other object it will not, at least
            # for PyQt (I didn't test it to a deeper level)
            self.__self = self

            currentScreen = QtWidgets.QApplication.primaryScreen()
            if parent and parent.window().geometry().size().isValid():
                # the notification is to be shown on the desktop, but there is a
                # parent that is (theoretically) visible and mapped, we'll try to
                # use its geometry as a reference to guess which desktop shows
                # most of its area; if the parent is not a top level window, use
                # that as a reference
                reference = parent.window().geometry()
            else:
                # the parent has not been mapped yet, let's use the cursor as a
                # reference for the screen
                reference = QtCore.QRect(
                    QtGui.QCursor.pos() - QtCore.QPoint(1, 1), 
                    QtCore.QSize(3, 3))
            maxArea = 0
            for screen in QtWidgets.QApplication.screens():
                intersected = screen.geometry().intersected(reference)
                area = intersected.width() * intersected.height()
                if area > maxArea:
                    maxArea = area
                    currentScreen = screen
            parentRect = currentScreen.availableGeometry()
        else:
            self = QToaster(parent)
            parentRect = parent.rect()

        self.timer.setInterval(timeout)

        # use Qt standard icon pixmaps; see:
        # https://doc.qt.io/qt-5/qstyle.html#StandardPixmap-enum
        if isinstance(icon, QtWidgets.QStyle.StandardPixmap):
            labelIcon = QtWidgets.QLabel()
            self.layout().addWidget(labelIcon)
            icon = self.style().standardIcon(icon)
            size = self.style().pixelMetric(QtWidgets.QStyle.PM_SmallIconSize)
            labelIcon.setPixmap(icon.pixmap(size))

        self.label = QtWidgets.QLabel(message)
        self.layout().addWidget(self.label)

        if closable:
            self.closeButton = QtWidgets.QToolButton()
            self.layout().addWidget(self.closeButton)
            closeIcon = self.style().standardIcon(
                QtWidgets.QStyle.SP_TitleBarCloseButton)
            self.closeButton.setIcon(closeIcon)
            self.closeButton.setAutoRaise(True)
            self.closeButton.clicked.connect(self.close)

        self.timer.start()

        # raise the widget and adjust its size to the minimum
        self.raise_()
        self.adjustSize()

        self.corner = corner
        self.margin = margin

        geo = self.geometry()
        # now the widget should have the correct size hints, let's move it to the
        # right place
        if corner == QtCore.Qt.TopLeftCorner:
            geo.moveTopLeft(
                parentRect.topLeft() + QtCore.QPoint(margin, margin))
        elif corner == QtCore.Qt.TopRightCorner:
            geo.moveTopRight(
                parentRect.topRight() + QtCore.QPoint(-margin, margin))
        elif corner == QtCore.Qt.BottomRightCorner:
            geo.moveBottomRight(
                parentRect.bottomRight() + QtCore.QPoint(-margin, -margin))
        else:
            geo.moveBottomLeft(
                parentRect.bottomLeft() + QtCore.QPoint(margin, -margin))

        self.setGeometry(geo)
        self.show()
        self.opacityAni.start()


class W(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        layout = QtWidgets.QVBoxLayout(self)

        toasterLayout = QtWidgets.QHBoxLayout()
        layout.addLayout(toasterLayout)

        self.textEdit = QtWidgets.QLineEdit('Ciao!')
        toasterLayout.addWidget(self.textEdit)

        self.cornerCombo = QtWidgets.QComboBox()
        toasterLayout.addWidget(self.cornerCombo)
        for pos in ('TopLeft', 'TopRight', 'BottomRight', 'BottomLeft'):
            corner = getattr(QtCore.Qt, '{}Corner'.format(pos))
            self.cornerCombo.addItem(pos, corner)

        self.windowBtn = QtWidgets.QPushButton('Show window toaster')
        toasterLayout.addWidget(self.windowBtn)
        self.windowBtn.clicked.connect(self.showToaster)

        self.screenBtn = QtWidgets.QPushButton('Show desktop toaster')
        toasterLayout.addWidget(self.screenBtn)
        self.screenBtn.clicked.connect(self.showToaster)

        # a random widget for the window
        layout.addWidget(QtWidgets.QTableView())

    def showToaster(self):
        if self.sender() == self.windowBtn:
            parent = self
            desktop = False
        else:
            parent = None
            desktop = True
        corner = QtCore.Qt.Corner(self.cornerCombo.currentData())
        QToaster.showMessage(
            parent, self.textEdit.text(), corner=corner, desktop=desktop)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = W()
    w.show()
    sys.exit(app.exec_())
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PyQt 是否有相当于 Toastr 的工具? 的相关文章

随机推荐

  • 使用 ShellContent xamarin 表单时添加导航后退箭头

    当我们使用ShellContent例如 导航到仪表板页面时 导航栏中没有后退箭头 知道如何导航到仪表板页面并可以返回上一页吗
  • 朱莉娅的执行速度

    我正在对 Julia 的执行速度进行基准测试 我执行了 time i 2 for i in 1 1000 在 Julia 提示下 这导致了大约 20 毫秒的时间 这看起来很奇怪 因为我的计算机是现代的 i7 处理器 我使用的是 Linux
  • 在tensorflow中使用tf.nn.conv2d_transpose获取反卷积层的输出形状

    根据这个paper 输出形状为N H 1 N是输入的高度或宽度 H是内核高度或宽度 这显然是卷积的逆过程 这tutorial给出计算卷积输出形状的公式 即 W F 2P S 1 W 输入大小 F 过滤器尺寸 P 填充尺寸 S 迈步 但在张量
  • 如果在范围内找到单个单元格值,则删除整行

    我正在处理每个多个站点记录 31 天数据的每日数据 我需要一个 VBA 代码来删除闰年日 我有一个记录数据日期的列表和一个我想要删除的非闰年的年份列表 为了删除额外的 30 和 31 天 我使用了以下基本代码 Dim lastrow i A
  • Excel 超链接 - 跳转到单元格并滚动窗口

    我正在使用 Excel 我想创建一个从页面顶部到页面上另一个位置的超链接 我在顶部的一个框中输入 然后右键链接并向下转到hyper link在下拉菜单中 我单击它并选择 在此工作簿中 的选项卡 并将其更改为我想要的位置 所以这一切都很好 但
  • 打字稿中的 useContext 和 useReducer

    我是新来的打字稿我正在实现react useContext 和useReducer 我正在遵循教程 但出现错误Parameter action implicitly has an any type 在我的减速器功能中 减速机功能 funct
  • 十进制数的正则表达式

    有人可以帮助我使用正则表达式吗 基本上 我想要一个与十进制数字匹配的正则表达式 允许的类型 12 1 3234 0 3423434 23423 12 不允许的类型 0012 12 324 12 01 2332 12 121212 提前感谢您
  • 如何使用 pip 安装 numpy

    我正在尝试安装numpy在我的 Mac 上 我目前使用 OS X 10 7 3 和 Python 版本 2 7 根据which python I tried pip install numpy在命令行中显示 找不到任何满足 numpy 要求
  • 在java中,JFrame是重量级组件还是轻量级组件?

    我知道 Swing 被认为是轻量级的 因为它完全用 Java 实现 无需调用本机操作系统来绘制图形用户界面组件 另一方面 AWT 抽象窗口工具包 是重量级工具包 因为它仅调用操作系统来生成其 GUI 组件 但听说Swing还是基于AWT的
  • 如何强制 .NET 3.5 应用程序在 .NET 4.5 运行时运行?

    我正在尝试实施此处描述的解决方案 我可以在面向 NET 3 5 SP1 时使用 NET 4 功能吗 当 NET 3 5 应用程序在 NET 4 0 上运行时 它可以正常工作 但是 在 NET 4 5 上 该应用程序在 NET 3 5 环境中
  • iOS:初始界面方向 [UIInterfaceOrientation] 在 plist 中被忽略

    我正在尝试将我的应用程序的初始方向设置为 UI界面方向横向左 我无法得到 Initial interface orientation UIInterfaceOrientation 覆盖数组中的第一项 Supported interface
  • 为什么我的 ViewScoped bean 无法在 h:commandButton 中生存?

    在 JBoss AS 7 1 0 Final 上部署 我有一个非常简单的测试应用程序 直到前几天它都按预期工作 著名的遗言 并且不再做最基本的事情 即设置输入组件的值并在操作组件中使用它 我已经将这件事剥离到基础知识 但无法弄清楚发生了什么
  • android 手机启动时启动 Activity

    我试图在手机启动时启动活动 但整个程序没有运行 程序中没有错误 请参阅下面的编码 或此处http pastebin com BKaE4AaU 自动启动 java import android content BroadcastReceive
  • 使用 LINQ 自定义排序

    看来我错过了一些微不足道的事情 无论如何 事情是这样的 var order new 1 3 2 var foos new new Foo Id 1 new Foo Id 2 new Foo Id 3 如何使用 Linq 按顺序数组对 foo
  • Angular 4.0 http put 请求

    我编写了一个函数来发送 http put 请求来更新一些数据 但它说它没有收到任何数据 updateHuman human Human const url this url human id const data JSON stringif
  • 如何使用访问权限和密钥来访问 Google Cloud Storage

    我拥有 Google Cloud Storage 的访问权限和密钥 并且我想使用这些凭据实例化客户端 我一直在查看教程并遇到了这个示例 public class QuickstartSample public static void mai
  • 如何在 C 预处理器中生成唯一值?

    我正在编写一堆相关的预处理器宏 其中一个生成标签 另一个宏则跳转到该标签 我以这种方式使用它们 MAKE FUNNY JUMPING LOOP MAKE LABEL MAKE LABEL 我需要某种方法来生成独特的标签 每个内部标签一个MA
  • 如何注册 OMX 核心以添加新解码器

    我指的是帖子 Android 如何将解码器集成到多媒体框架 接下来我注册了我的新解码器 Android 目前不支持 media codecs xml 上述帖子的步骤 2 需要我执行OMX核心注册 但是 由于我对这个主题非常陌生 所以我无法遵
  • 为什么我的 onclick 事件没有在 Firefox 中注册?

    我有一个列表项onclick事件 它可以在 Chrome 和 Internet Explorer 中运行 但不能在 Firefox 中运行 有什么建议么 li test test br li 这对我来说在 Firefox 中工作得很好 检查
  • PyQt 是否有相当于 Toastr 的工具?

    我正在开发我的第一个 PyQt 项目 我想想出一种方法 在用户完成任务时向他们提供成功或错误消息 过去 我用过 JavascriptToastr我很好奇 Python 应用程序是否有类似的东西 我考虑过在 PyQt 中使用 QDialog