如何在pyqt中實現帶動畫的動態QMenu


彈出菜單的視覺效果

QLineEdit 原生的菜單彈出效果十分生硬,而且樣式很丑。所以照着Groove中單行輸入框彈出菜單的樣式和動畫效果寫了一個可以實現動態變化Item的彈出菜單,根據剪貼板的內容是否為文本、編輯框是否有文本以及是否有選中文本分為6種情況,大體效果如下所示(ヾ(๑╹◡╹)ノ" 硝子依舊如此迷人:
在這里插入圖片描述

具體實現流程

Menu 繼承自 QMenu,在這個類中通過調用自定義類 WindowEffect 的方法來調用win10的api從而實現Aero效果和陰影效果,定義WindowEffect的代碼放在了文末的鏈接中,可以自取,而Aero效果的實現方法不妨先康康 《如何在pyqt中實現窗口磨砂效果》,需要指出的是要想實現這兩種效果需要事先安裝好 MSVC (不安裝 MSVC 的解決方案參見 (《如何在pyqt中通過調用SetWindowCompositionAttribute實現Win10亞克力效果》)。Menu 類的具體代碼如下:

import sys
from ctypes.wintypes import HWND

from PyQt5.QtCore import QAbstractAnimation, QEasingCurve, QEvent, Qt, QPropertyAnimation, QRect
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction, QApplication, QGraphicsDropShadowEffect, QMenu

from window_effect import WindowEffect


class Menu(QMenu):
    """ 自定義菜單 """
    windowEffect = WindowEffect()

    def __init__(self, string='', parent=None):
        super().__init__(string,parent)
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup | Qt.NoDropShadowWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground | Qt.WA_StyledBackground)
        self.setQss()

    def event(self, e: QEvent):
        if e.type() == QEvent.WinIdChange:
            self.hWnd = HWND(int(self.winId()))
            self.setMenuEffect()
        return QMenu.event(self, e)

    def setMenuEffect(self):
        """ 添加特效 """
        self.windowEffect.setAeroEffect(self.hWnd)
        self.windowEffect.addShadowEffect(True,self.hWnd)

    def setQss(self):
        """ 設置層疊樣式 """
        with open('menu.qss', encoding='utf-8') as f:
            self.setStyleSheet(f.read())

LineEditMenu 類

LineEditMenu 繼承自 Menu,需要注意的是這個類對 parent 的是有要求的,具體要求見代碼:

class LineEditMenu(Menu):
    """ 單行輸入框右擊菜單 """
    def __init__(self, parent):
        super().__init__('', parent)
        # 實例化動畫,注意不能直接改width這個只讀的屬性
        self.animation = QPropertyAnimation(self, b'geometry')
        self.initWidget()

    def initWidget(self):
        """ 初始化小部件 """
        self.setObjectName('lineEditMenu')
        # 設置動畫持續時間
        self.animation.setDuration(300)
        # 設置插值方式
        self.animation.setEasingCurve(QEasingCurve.OutQuad)

    def createActions(self):
        # 創建動作
        self.cutAct = QAction(
            QIcon('images\\黑色剪刀.png'), '剪切', self, shortcut='Ctrl+X', triggered=self.parent().cut)
        self.copyAct = QAction(
            QIcon('images\\黑色復制.png'), '復制', self, shortcut='Ctrl+C', triggered=self.parent().copy)
        self.pasteAct = QAction(
            QIcon('images\\黑色粘貼.png'), '粘貼', self, shortcut='Ctrl+V', triggered=self.parent().paste)
        self.cancelAct = QAction(
            QIcon('images\\黑色撤銷.png'), '取消操作', self, shortcut='Ctrl+Z', triggered=self.parent().undo)
        self.selectAllAct = QAction('全選', self, shortcut='Ctrl+A', triggered=self.parent().selectAll)
        # 創建動作列表
        self.action_list = [self.cutAct, self.copyAct, self.pasteAct, self.cancelAct, self.selectAllAct]

    def exec_(self, pos):
    	""" 重寫exec_() """
        # 刪除所有動作
        self.clear()
        # clear會直接delete之前的動作故需重新創建
        self.createActions()
        # 初始化屬性,本來是在沒有添加動畫時為qss設置的,設置動畫之后這個屬性沒什么用
        self.setProperty('hasCancelAct', 'false')
        width = 176
        # 本來是在后面調用columnCount()來計算item個數的,結果算出來是1,所以手動創建一個變量來記錄Item個數
        actionNum = len(self.action_list)
        # 訪問系統剪貼板
        self.clipboard = QApplication.clipboard()
        # 根據剪貼板內容是否為text分兩種情況討論
        if self.clipboard.mimeData().hasText():
            # 再根據3種情況分類討論
            if self.parent().text():
                self.setProperty('hasCancelAct', 'true')
                width = 213
                if self.parent().selectedText():
                    self.addActions(self.action_list)
                else:
                    self.addActions(self.action_list[2:])
                    actionNum -= 2
            else:
                self.addAction(self.pasteAct)
                actionNum = 1
        else:
            if self.parent().text():
                self.setProperty('hasCancelAct', 'true')
                width = 213
                if self.parent().selectedText():
                    self.addActions(self.action_list[:2] + self.action_list[3:])
                    actionNum -= 1
                else:
                    self.addActions(self.action_list[3:])
                    actionNum -= 3
            else:
                return
        # 每個item的高度為38px,10為上下的內邊距和
        height = actionNum * 38 + 10
        # 不能把初始的寬度設置為0px,不然會報警
        self.animation.setStartValue(
            QRect(pos.x(), pos.y(), 1, height))
        self.animation.setEndValue(
            QRect(pos.x(), pos.y(), width, height))
        self.setStyle(QApplication.style())
        # 開始動畫
        self.animation.start()
        super().exec_(pos)

源代碼和dll

動圖中用到的編輯框是可以實現對音頻文件標簽信息的寫入的,具體實現方法放在了我的github倉庫中,喜歡的話可以給我點個小星星,下面是這次用到的代碼、dll以及資源文件的網盤鏈接(提取碼:fl4m):鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM