彈出菜單的視覺效果
QLineEdit
原生的菜單彈出效果十分生硬,而且樣式很丑。所以照着Groove中單行輸入框彈出菜單的樣式和動畫效果寫了一個可以實現動態變化Item的彈出菜單,根據剪貼板的內容是否為文本、編輯框是否有文本以及是否有選中文本分為6種情況,大體效果如下所示(ヾ(๑╹◡╹)ノ" 硝子依舊如此迷人:
具體實現流程
Menu 類
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):鏈接