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的设备。在实际用的时候尽量加上判断,来适应不同的设备。

 

  

posted @ 2018-08-11 22:11  JYRoy  阅读(18357)  评论(9编辑  收藏  举报