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