PyQt5無邊框視窗的標題拖動和視窗縮放實現
阿新 • • 發佈:2019-01-06
網上找了半天都找不到好用的PyQt5無邊框視窗的實現
借鑑部分前輩的視窗拖放程式碼
自己搗鼓了一下,實現了一下無邊框視窗,問題可能還有一點,慢慢改吧
先做個筆記
py檔案
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QVBoxLayout
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QFont, QCursor
class QTitleLabel(QLabel) :
"""
新建標題欄標籤類
"""
def __init__(self, *args):
super(QTitleLabel, self).__init__(*args)
self.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
self.setFixedHeight(30)
class QTitleButton(QPushButton):
"""
新建標題欄按鈕類
"""
def __init__(self, *args):
super(QTitleButton, self).__init__(*args)
self.setFont(QFont("Webdings" )) # 特殊字型以不借助圖片實現最小化最大化和關閉按鈕
self.setFixedWidth(40)
class QUnFrameWindow(QWidget):
"""
無邊框視窗類
"""
def __init__(self):
super(QUnFrameWindow, self).__init__(None, Qt.FramelessWindowHint) # 設定為頂級視窗,無邊框
self._padding = 5 # 設定邊界寬度為5
self.initTitleLabel() # 安放標題欄標籤
self.setWindowTitle = self._setTitleText(self.setWindowTitle) # 用裝飾器將設定WindowTitle名字函式共享到標題欄標籤上
self.setWindowTitle("UnFrameWindow")
self.initLayout() # 設定框架佈局
self.setMinimumWidth(250)
self.setMouseTracking(True) # 設定widget滑鼠跟蹤
self.initDrag() # 設定滑鼠跟蹤判斷預設值
def initDrag(self):
# 設定滑鼠跟蹤判斷扳機預設值
self._move_drag = False
self._corner_drag = False
self._bottom_drag = False
self._right_drag = False
def initTitleLabel(self):
# 安放標題欄標籤
self._TitleLabel = QTitleLabel(self)
self._TitleLabel.setMouseTracking(True) # 設定標題欄標籤滑鼠跟蹤(如不設,則標題欄內在widget上層,無法實現跟蹤)
self._TitleLabel.setIndent(10) # 設定標題欄文字縮排
self._TitleLabel.move(0, 0) # 標題欄安放到左上角
def initLayout(self):
# 設定框架佈局
self._MainLayout = QVBoxLayout()
self._MainLayout.setSpacing(0)
self._MainLayout.addWidget(QLabel(), Qt.AlignLeft) # 頂一個QLabel在豎放框架第一行,以免正常內容擠佔到標題範圍裡
self._MainLayout.addStretch()
self.setLayout(self._MainLayout)
def addLayout(self, QLayout):
# 給widget定義一個addLayout函式,以實現往豎放框架的正確內容區內巢狀Layout框架
self._MainLayout.addLayout(QLayout)
def _setTitleText(self, func):
# 設定標題欄標籤的裝飾器函式
def wrapper(*args):
self._TitleLabel.setText(*args)
return func(*args)
return wrapper
def setTitleAlignment(self, alignment):
# 給widget定義一個setTitleAlignment函式,以實現標題欄標籤的對齊方式設定
self._TitleLabel.setAlignment(alignment | Qt.AlignVCenter)
def setCloseButton(self, bool):
# 給widget定義一個setCloseButton函式,為True時設定一個關閉按鈕
if bool == True:
self._CloseButton = QTitleButton(b'\xef\x81\xb2'.decode("utf-8"), self)
self._CloseButton.setObjectName("CloseButton") # 設定按鈕的ObjectName以在qss樣式表內定義不同的按鈕樣式
self._CloseButton.setToolTip("關閉視窗")
self._CloseButton.setMouseTracking(True) # 設定按鈕滑鼠跟蹤(如不設,則按鈕在widget上層,無法實現跟蹤)
self._CloseButton.setFixedHeight(self._TitleLabel.height()) # 設定按鈕高度為標題欄高度
self._CloseButton.clicked.connect(self.close) # 按鈕訊號連線到關閉視窗的槽函式
def setMinMaxButtons(self, bool):
# 給widget定義一個setMinMaxButtons函式,為True時設定一組最小化最大化按鈕
if bool == True:
self._MinimumButton = QTitleButton(b'\xef\x80\xb0'.decode("utf-8"), self)
self._MinimumButton.setObjectName("MinMaxButton") # 設定按鈕的ObjectName以在qss樣式表內定義不同的按鈕樣式
self._MinimumButton.setToolTip("最小化")
self._MinimumButton.setMouseTracking(True) # 設定按鈕滑鼠跟蹤(如不設,則按鈕在widget上層,無法實現跟蹤)
self._MinimumButton.setFixedHeight(self._TitleLabel.height()) # 設定按鈕高度為標題欄高度
self._MinimumButton.clicked.connect(self.showMinimized) # 按鈕訊號連線到最小化視窗的槽函式
self._MaximumButton = QTitleButton(b'\xef\x80\xb1'.decode("utf-8"), self)
self._MaximumButton.setObjectName("MinMaxButton") # 設定按鈕的ObjectName以在qss樣式表內定義不同的按鈕樣式
self._MaximumButton.setToolTip("最大化")
self._MaximumButton.setMouseTracking(True) # 設定按鈕滑鼠跟蹤(如不設,則按鈕在widget上層,無法實現跟蹤)
self._MaximumButton.setFixedHeight(self._TitleLabel.height()) # 設定按鈕高度為標題欄高度
self._MaximumButton.clicked.connect(self._changeNormalButton) # 按鈕訊號連線切換到恢復視窗大小按鈕函式
def _changeNormalButton(self):
# 切換到恢復視窗大小按鈕
try:
self.showMaximized() # 先實現視窗最大化
self._MaximumButton.setText(b'\xef\x80\xb2'.decode("utf-8")) # 更改按鈕文字
self._MaximumButton.setToolTip("恢復") # 更改按鈕提示
self._MaximumButton.disconnect() # 斷開原本的訊號槽連線
self._MaximumButton.clicked.connect(self._changeMaxButton) # 重新連線訊號和槽
except:
pass
def _changeMaxButton(self):
# 切換到最大化按鈕
try:
self.showNormal()
self._MaximumButton.setText(b'\xef\x80\xb1'.decode("utf-8"))
self._MaximumButton.setToolTip("最大化")
self._MaximumButton.disconnect()
self._MaximumButton.clicked.connect(self._changeNormalButton)
except:
pass
def resizeEvent(self, QResizeEvent):
# 自定義視窗調整大小事件
self._TitleLabel.setFixedWidth(self.width()) # 將標題標籤始終設為視窗寬度
# 分別移動三個按鈕到正確的位置
try:
self._CloseButton.move(self.width() - self._CloseButton.width(), 0)
except:
pass
try:
self._MinimumButton.move(self.width() - (self._CloseButton.width() + 1) * 3 + 1, 0)
except:
pass
try:
self._MaximumButton.move(self.width() - (self._CloseButton.width() + 1) * 2 + 1, 0)
except:
pass
# 重新調整邊界範圍以備實現滑鼠拖放縮放視窗大小,採用三個列表生成式生成三個列表
self._right_rect = [QPoint(x, y) for x in range(self.width() - self._padding, self.width() + 1)
for y in range(1, self.height() - self._padding)]
self._bottom_rect = [QPoint(x, y) for x in range(1, self.width() - self._padding)
for y in range(self.height() - self._padding, self.height() + 1)]
self._corner_rect = [QPoint(x, y) for x in range(self.width() - self._padding, self.width() + 1)
for y in range(self.height() - self._padding, self.height() + 1)]
def mousePressEvent(self, event):
# 重寫滑鼠點選的事件
if (event.button() == Qt.LeftButton) and (event.pos() in self._corner_rect):
# 滑鼠左鍵點選右下角邊界區域
self._corner_drag = True
event.accept()
elif (event.button() == Qt.LeftButton) and (event.pos() in self._right_rect):
# 滑鼠左鍵點選右側邊界區域
self._right_drag = True
event.accept()
elif (event.button() == Qt.LeftButton) and (event.pos() in self._bottom_rect):
# 滑鼠左鍵點選下側邊界區域
self._bottom_drag = True
event.accept()
elif (event.button() == Qt.LeftButton) and (event.y() < self._TitleLabel.height()):
# 滑鼠左鍵點選標題欄區域
self._move_drag = True
self.move_DragPosition = event.globalPos() - self.pos()
event.accept()
def mouseMoveEvent(self, QMouseEvent):
# 判斷滑鼠位置切換滑鼠手勢
if QMouseEvent.pos() in self._corner_rect:
self.setCursor(Qt.SizeFDiagCursor)
elif QMouseEvent.pos() in self._bottom_rect:
self.setCursor(Qt.SizeVerCursor)
elif QMouseEvent.pos() in self._right_rect:
self.setCursor(Qt.SizeHorCursor)
else:
self.setCursor(Qt.ArrowCursor)
# 當滑鼠左鍵點選不放及滿足點選區域的要求後,分別實現不同的視窗調整
# 沒有定義左方和上方相關的5個方向,主要是因為實現起來不難,但是效果很差,拖放的時候視窗閃爍,再研究研究是否有更好的實現
if Qt.LeftButton and self._right_drag:
# 右側調整視窗寬度
self.resize(QMouseEvent.pos().x(), self.height())
QMouseEvent.accept()
elif Qt.LeftButton and self._bottom_drag:
# 下側調整視窗高度
self.resize(self.width(), QMouseEvent.pos().y())
QMouseEvent.accept()
elif Qt.LeftButton and self._corner_drag:
# 右下角同時調整高度和寬度
self.resize(QMouseEvent.pos().x(), QMouseEvent.pos().y())
QMouseEvent.accept()
elif Qt.LeftButton and self._move_drag:
# 標題欄拖放視窗位置
self.move(QMouseEvent.globalPos() - self.move_DragPosition)
QMouseEvent.accept()
def mouseReleaseEvent(self, QMouseEvent):
# 滑鼠釋放後,各扳機復位
self._move_drag = False
self._corner_drag = False
self._bottom_drag = False
self._right_drag = False
if __name__ == "__main__":
from PyQt5.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
app.setStyleSheet(open("./UnFrameStyle.qss").read())
window = QUnFrameWindow()
window.setCloseButton(True)
window.setMinMaxButtons(True)
window.show()
sys.exit(app.exec_())
qss檔案
/**********Title**********/
QTitleLabel{
background-color: Gainsboro;
font: 100 10pt;
}
/**********Button**********/
QTitleButton{
background-color: rgba(255, 255, 255, 0);
color: black;
border: 0px;
font: 100 10pt;
}
QTitleButton#MinMaxButton:hover{
background-color: #D0D0D1;
border: 0px;
font: 100 10pt;
}
QTitleButton#CloseButton:hover{
background-color: #D32424;
color: white;
border: 0px;
font: 100 10pt;
}