PyQt:無邊框自定義標題欄及最大化最小化窗體大小調整


環境

  Python3.5.2

  PyQt5

陳述

  隱藏掉系統的控制欄,實現了自定義的標題控制欄,以及關閉/最大化/最小化的功能,自由調整窗體大小的功能(跟隨一個大佬學的),代碼內有詳細注釋

  只要把MainWindow類自己實現就可以了,我把左側欄的demo(可以看我這篇https://www.cnblogs.com/jyroy/p/9457882.html)搭載上了,效果如下

  標題欄的風格我和左側欄的風格統一了,還是模仿網易雲音樂的紅色格調(我覺得網易雲的紅色很ok)

 

代碼

  1 #!/usr/bin/env python
  2 # -*- coding:utf-8 -*-
  3 # Author: jyroy
  4 import sys
  5 
  6 from PyQt5.QtCore import QSize
  7 from PyQt5.QtWidgets import QApplication
  8 from PyQt5.QtCore import Qt, pyqtSignal, QPoint
  9 from PyQt5.QtGui import QFont, QEnterEvent, QPainter, QColor, QPen
 10 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel,QSpacerItem, QSizePolicy, QPushButton
 11 from PyQt5.QtGui import QIcon
 12 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextEdit
 13 from LeftTabWidget import LeftTabWidget
 14 # 樣式
 15 StyleSheet = """
 16 /*標題欄*/
 17 TitleBar {
 18     background-color: red;
 19 }
 20 /*最小化最大化關閉按鈕通用默認背景*/
 21 #buttonMinimum,#buttonMaximum,#buttonClose {
 22     border: none;
 23     background-color: red;
 24 }
 25 /*懸停*/
 26 #buttonMinimum:hover,#buttonMaximum:hover {
 27     background-color: red;
 28     color: white;
 29 }
 30 #buttonClose:hover {
 31     color: white;
 32 }
 33 /*鼠標按下不放*/
 34 #buttonMinimum:pressed,#buttonMaximum:pressed {
 35     background-color: Firebrick;
 36 }
 37 #buttonClose:pressed {
 38     color: white;
 39     background-color: Firebrick;
 40 }
 41 """
 42 
 43 class TitleBar(QWidget):
 44 
 45     # 窗口最小化信號
 46     windowMinimumed = pyqtSignal()
 47     # 窗口最大化信號
 48     windowMaximumed = pyqtSignal()
 49     # 窗口還原信號
 50     windowNormaled = pyqtSignal()
 51     # 窗口關閉信號
 52     windowClosed = pyqtSignal()
 53     # 窗口移動
 54     windowMoved = pyqtSignal(QPoint)
 55 
 56     def __init__(self, *args, **kwargs):
 57         super(TitleBar, self).__init__(*args, **kwargs)
 58         # 支持qss設置背景
 59         self.setAttribute(Qt.WA_StyledBackground, True)
 60         self.mPos = None
 61         self.iconSize = 20  # 圖標的默認大小
 62         # 設置默認背景顏色,否則由於受到父窗口的影響導致透明
 63         self.setAutoFillBackground(True)
 64         palette = self.palette()
 65         palette.setColor(palette.Window, QColor(240, 240, 240))
 66         self.setPalette(palette)
 67         # 布局
 68         layout = QHBoxLayout(self, spacing=0)
 69         layout.setContentsMargins(0, 0, 0, 0)
 70         # 窗口圖標
 71         self.iconLabel = QLabel(self)
 72 #         self.iconLabel.setScaledContents(True)
 73         layout.addWidget(self.iconLabel)
 74         # 窗口標題
 75         self.titleLabel = QLabel(self)
 76         self.titleLabel.setMargin(2)
 77         layout.addWidget(self.titleLabel)
 78         # 中間伸縮條
 79         layout.addSpacerItem(QSpacerItem(
 80             40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
 81         # 利用Webdings字體來顯示圖標
 82         font = self.font() or QFont()
 83         font.setFamily('Webdings')
 84         # 最小化按鈕
 85         self.buttonMinimum = QPushButton(
 86             '0', self, clicked=self.windowMinimumed.emit, font=font, objectName='buttonMinimum')
 87         layout.addWidget(self.buttonMinimum)
 88         # 最大化/還原按鈕
 89         self.buttonMaximum = QPushButton(
 90             '1', self, clicked=self.showMaximized, font=font, objectName='buttonMaximum')
 91         layout.addWidget(self.buttonMaximum)
 92         # 關閉按鈕
 93         self.buttonClose = QPushButton(
 94             'r', self, clicked=self.windowClosed.emit, font=font, objectName='buttonClose')
 95         layout.addWidget(self.buttonClose)
 96         # 初始高度
 97         self.setHeight()
 98 
 99     def showMaximized(self):
100         if self.buttonMaximum.text() == '1':
101             # 最大化
102             self.buttonMaximum.setText('2')
103             self.windowMaximumed.emit()
104         else:  # 還原
105             self.buttonMaximum.setText('1')
106             self.windowNormaled.emit()
107 
108     def setHeight(self, height=38):
109         """設置標題欄高度"""
110         self.setMinimumHeight(height)
111         self.setMaximumHeight(height)
112         # 設置右邊按鈕的大小
113         self.buttonMinimum.setMinimumSize(height, height)
114         self.buttonMinimum.setMaximumSize(height, height)
115         self.buttonMaximum.setMinimumSize(height, height)
116         self.buttonMaximum.setMaximumSize(height, height)
117         self.buttonClose.setMinimumSize(height, height)
118         self.buttonClose.setMaximumSize(height, height)
119 
120     def setTitle(self, title):
121         """設置標題"""
122         self.titleLabel.setText(title)
123 
124     def setIcon(self, icon):
125         """設置圖標"""
126         self.iconLabel.setPixmap(icon.pixmap(self.iconSize, self.iconSize))
127 
128     def setIconSize(self, size):
129         """設置圖標大小"""
130         self.iconSize = size
131 
132     def enterEvent(self, event):
133         self.setCursor(Qt.ArrowCursor)
134         super(TitleBar, self).enterEvent(event)
135 
136     def mouseDoubleClickEvent(self, event):
137         super(TitleBar, self).mouseDoubleClickEvent(event)
138         self.showMaximized()
139 
140     def mousePressEvent(self, event):
141         """鼠標點擊事件"""
142         if event.button() == Qt.LeftButton:
143             self.mPos = event.pos()
144         event.accept()
145 
146     def mouseReleaseEvent(self, event):
147         '''鼠標彈起事件'''
148         self.mPos = None
149         event.accept()
150 
151     def mouseMoveEvent(self, event):
152         if event.buttons() == Qt.LeftButton and self.mPos:
153             self.windowMoved.emit(self.mapToGlobal(event.pos() - self.mPos))
154         event.accept()
155 
156 # 枚舉左上右下以及四個定點
157 Left, Top, Right, Bottom, LeftTop, RightTop, LeftBottom, RightBottom = range(8)
158 
159 class FramelessWindow(QWidget):
160 
161     # 四周邊距
162     Margins = 5
163 
164     def __init__(self, *args, **kwargs):
165         super(FramelessWindow, self).__init__(*args, **kwargs)
166 
167         self._pressed = False
168         self.Direction = None
169         # 背景透明
170         self.setAttribute(Qt.WA_TranslucentBackground, True)
171         # 無邊框
172         self.setWindowFlags(Qt.FramelessWindowHint)  # 隱藏邊框
173         # 鼠標跟蹤
174         self.setMouseTracking(True)
175         # 布局
176         layout = QVBoxLayout(self, spacing=0)
177         # 預留邊界用於實現無邊框窗口調整大小
178         layout.setContentsMargins(
179             self.Margins, self.Margins, self.Margins, self.Margins)
180         # 標題欄
181         self.titleBar = TitleBar(self)
182         layout.addWidget(self.titleBar)
183         # 信號槽
184         self.titleBar.windowMinimumed.connect(self.showMinimized)
185         self.titleBar.windowMaximumed.connect(self.showMaximized)
186         self.titleBar.windowNormaled.connect(self.showNormal)
187         self.titleBar.windowClosed.connect(self.close)
188         self.titleBar.windowMoved.connect(self.move)
189         self.windowTitleChanged.connect(self.titleBar.setTitle)
190         self.windowIconChanged.connect(self.titleBar.setIcon)
191 
192     def setTitleBarHeight(self, height=38):
193         """設置標題欄高度"""
194         self.titleBar.setHeight(height)
195 
196     def setIconSize(self, size):
197         """設置圖標的大小"""
198         self.titleBar.setIconSize(size)
199 
200     def setWidget(self, widget):
201         """設置自己的控件"""
202         if hasattr(self, '_widget'):
203             return
204         self._widget = widget
205         # 設置默認背景顏色,否則由於受到父窗口的影響導致透明
206         self._widget.setAutoFillBackground(True)
207         palette = self._widget.palette()
208         palette.setColor(palette.Window, QColor(240, 240, 240))
209         self._widget.setPalette(palette)
210         self._widget.installEventFilter(self)
211         self.layout().addWidget(self._widget)
212 
213     def move(self, pos):
214         if self.windowState() == Qt.WindowMaximized or self.windowState() == Qt.WindowFullScreen:
215             # 最大化或者全屏則不允許移動
216             return
217         super(FramelessWindow, self).move(pos)
218 
219     def showMaximized(self):
220         """最大化,要去除上下左右邊界,如果不去除則邊框地方會有空隙"""
221         super(FramelessWindow, self).showMaximized()
222         self.layout().setContentsMargins(0, 0, 0, 0)
223 
224     def showNormal(self):
225         """還原,要保留上下左右邊界,否則沒有邊框無法調整"""
226         super(FramelessWindow, self).showNormal()
227         self.layout().setContentsMargins(
228             self.Margins, self.Margins, self.Margins, self.Margins)
229 
230     def eventFilter(self, obj, event):
231         """事件過濾器,用於解決鼠標進入其它控件后還原為標准鼠標樣式"""
232         if isinstance(event, QEnterEvent):
233             self.setCursor(Qt.ArrowCursor)
234         return super(FramelessWindow, self).eventFilter(obj, event)
235 
236     def paintEvent(self, event):
237         """由於是全透明背景窗口,重繪事件中繪制透明度為1的難以發現的邊框,用於調整窗口大小"""
238         super(FramelessWindow, self).paintEvent(event)
239         painter = QPainter(self)
240         painter.setPen(QPen(QColor(255, 255, 255, 1), 2 * self.Margins))
241         painter.drawRect(self.rect())
242 
243     def mousePressEvent(self, event):
244         """鼠標點擊事件"""
245         super(FramelessWindow, self).mousePressEvent(event)
246         if event.button() == Qt.LeftButton:
247             self._mpos = event.pos()
248             self._pressed = True
249 
250     def mouseReleaseEvent(self, event):
251         '''鼠標彈起事件'''
252         super(FramelessWindow, self).mouseReleaseEvent(event)
253         self._pressed = False
254         self.Direction = None
255 
256     def mouseMoveEvent(self, event):
257         """鼠標移動事件"""
258         super(FramelessWindow, self).mouseMoveEvent(event)
259         pos = event.pos()
260         xPos, yPos = pos.x(), pos.y()
261         wm, hm = self.width() - self.Margins, self.height() - self.Margins
262         if self.isMaximized() or self.isFullScreen():
263             self.Direction = None
264             self.setCursor(Qt.ArrowCursor)
265             return
266         if event.buttons() == Qt.LeftButton and self._pressed:
267             self._resizeWidget(pos)
268             return
269         if xPos <= self.Margins and yPos <= self.Margins:
270             # 左上角
271             self.Direction = LeftTop
272             self.setCursor(Qt.SizeFDiagCursor)
273         elif wm <= xPos <= self.width() and hm <= yPos <= self.height():
274             # 右下角
275             self.Direction = RightBottom
276             self.setCursor(Qt.SizeFDiagCursor)
277         elif wm <= xPos and yPos <= self.Margins:
278             # 右上角
279             self.Direction = RightTop
280             self.setCursor(Qt.SizeBDiagCursor)
281         elif xPos <= self.Margins and hm <= yPos:
282             # 左下角
283             self.Direction = LeftBottom
284             self.setCursor(Qt.SizeBDiagCursor)
285         elif 0 <= xPos <= self.Margins and self.Margins <= yPos <= hm:
286             # 左邊
287             self.Direction = Left
288             self.setCursor(Qt.SizeHorCursor)
289         elif wm <= xPos <= self.width() and self.Margins <= yPos <= hm:
290             # 右邊
291             self.Direction = Right
292             self.setCursor(Qt.SizeHorCursor)
293         elif self.Margins <= xPos <= wm and 0 <= yPos <= self.Margins:
294             # 上面
295             self.Direction = Top
296             self.setCursor(Qt.SizeVerCursor)
297         elif self.Margins <= xPos <= wm and hm <= yPos <= self.height():
298             # 下面
299             self.Direction = Bottom
300             self.setCursor(Qt.SizeVerCursor)
301 
302     def _resizeWidget(self, pos):
303         """調整窗口大小"""
304         if self.Direction == None:
305             return
306         mpos = pos - self._mpos
307         xPos, yPos = mpos.x(), mpos.y()
308         geometry = self.geometry()
309         x, y, w, h = geometry.x(), geometry.y(), geometry.width(), geometry.height()
310         if self.Direction == LeftTop:  # 左上角
311             if w - xPos > self.minimumWidth():
312                 x += xPos
313                 w -= xPos
314             if h - yPos > self.minimumHeight():
315                 y += yPos
316                 h -= yPos
317         elif self.Direction == RightBottom:  # 右下角
318             if w + xPos > self.minimumWidth():
319                 w += xPos
320                 self._mpos = pos
321             if h + yPos > self.minimumHeight():
322                 h += yPos
323                 self._mpos = pos
324         elif self.Direction == RightTop:  # 右上角
325             if h - yPos > self.minimumHeight():
326                 y += yPos
327                 h -= yPos
328             if w + xPos > self.minimumWidth():
329                 w += xPos
330                 self._mpos.setX(pos.x())
331         elif self.Direction == LeftBottom:  # 左下角
332             if w - xPos > self.minimumWidth():
333                 x += xPos
334                 w -= xPos
335             if h + yPos > self.minimumHeight():
336                 h += yPos
337                 self._mpos.setY(pos.y())
338         elif self.Direction == Left:  # 左邊
339             if w - xPos > self.minimumWidth():
340                 x += xPos
341                 w -= xPos
342             else:
343                 return
344         elif self.Direction == Right:  # 右邊
345             if w + xPos > self.minimumWidth():
346                 w += xPos
347                 self._mpos = pos
348             else:
349                 return
350         elif self.Direction == Top:  # 上面
351             if h - yPos > self.minimumHeight():
352                 y += yPos
353                 h -= yPos
354             else:
355                 return
356         elif self.Direction == Bottom:  # 下面
357             if h + yPos > self.minimumHeight():
358                 h += yPos
359                 self._mpos = pos
360             else:
361                 return
362         self.setGeometry(x, y, w, h)
363 
364 class MainWindow(QWidget):
365 
366     def __init__(self, *args, **kwargs):
367         super(MainWindow, self).__init__(*args, **kwargs)
368         layout = QVBoxLayout(self, spacing=0)
369         layout.setContentsMargins(0, 0, 0, 0)
370         
371         self.left_tag = LeftTabWidget()
372         layout.addWidget(self.left_tag)
373 
374 
375 if __name__ == '__main__':
376 
377     app = QApplication(sys.argv)
378     app.setStyleSheet(StyleSheet)
379     mainWnd = FramelessWindow()
380     mainWnd.setWindowTitle('測試標題欄')
381     mainWnd.setWindowIcon(QIcon('Qt.ico'))
382     mainWnd.resize(QSize(1250,780))
383     mainWnd.setWidget(MainWindow(mainWnd))  # 把自己的窗口添加進來
384     mainWnd.show()
385     sys.exit(app.exec_())

 

 

效果展示

 

拓展知識

設置窗口尺寸的方法:
1.設置寬度和高度。
  resize(int w,int h)
  resize(QSize s)
2.設置窗口的位置、寬度和高度。
  setGeometry(int X,int Y,int W,int H)
  setGeometry(QRect r)
3.設置窗口為固定值。
  setFixedSize(int w,int h)
  setFixedSize(QSize s)
  注意:窗口標題欄上的最大化按鈕無效;用鼠標無法調整窗口尺寸。
4.設置窗口為固定值。
  setFixedWidth(int w)
  窗口標題欄上的最大化按鈕無效;用鼠標無法調整窗口的寬度。
5.設置窗口為固定值。
  setFixedHeight(int h)
  窗口標題欄上的最大化按鈕無效;用鼠標無法調整窗口的高度。
5.設置窗口的最小尺寸。
  setMinimumSize(int w,int h)
  setMinimumSize(QSize s)
  用鼠標可以讓窗口變寬、變高。
  設置窗口的最小寬度:
    setMinimumWidth(int w)
  設置窗口的最小高度:
    setMinimumHeight(int h)
6.設置窗口的最大尺寸。
  setMaximumSize(int w,int h)
  setMaximumSize(QSize s)
  用鼠標可以讓窗口變寬、變高。
  設置窗口的最小寬度:
    setMaximumWidth(int w)
  設置窗口的最小高度:
    setMaximumHeight(int h)
 

說明

  因為只是自己寫的簡單的例子,在窗口方面都是利用的寫死的大小。不同的電腦像素會有差別。我的是1920*1080的設備。在實際用的時候盡量加上判斷,來適應不同的設備。

 

  


免責聲明!

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



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