环境#
Python3.5.2
PyQt5
陈述#
隐藏掉系统的控制栏,实现了自定义的标题控制栏,以及关闭/最大化/最小化的功能,自由调整窗体大小的功能(跟随一个大佬学的),代码内有详细注释
只要把MainWindow类自己实现就可以了,效果如下
标题栏的风格我和左侧栏的风格统一了,还是模仿网易云音乐的红色格调(我觉得网易云的红色很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_())
效果展示#