- 本篇文章主要记录笔者在使用PyQt5的过程中,一些常用的代码技巧,以及好用可复用的代码
- 如何实现软件只能启动单应用
def is_running(start_name: str):
"""
判断程序是否运行
"""
out_socket = QtNetwork.QLocalSocket()
out_socket.connectToServer(start_name)
is_running = out_socket.waitForConnected()
print(is_running)
return is_running
if is_running(start_name="test"):
print("程序在运行")
else:
localServer = QtNetwork.QLocalServer()
localServer.listen("test")
- 软件打包的spec文件的编写
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['main.py'],
pathex=['xxx'],
binaries=[],
datas=[('resource','resource')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon= "music.ico")
- 自定义边框的程序
实现效果:
import enum
from PyQt5 import QtGui, QtCore
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit
from PyQt5.QtWidgets import QPushButton, QTextEdit, QGridLayout, QHBoxLayout, QFrame, QStyle, QStyleOption
from PyQt5.QtGui import QPainter, QPainterPath, QPen, QBrush, QColor, QPalette, QRegion, QMouseEvent, QHoverEvent
from PyQt5.QtCore import QRect, QRectF, QSize, QPoint, QEvent, QMargins
from widgets.components.custom_border_draggers import LeftDragger, RightDragger, TopDragger, BottomDragger
from widgets.components.custom_border_draggers import TopLeftDragger, TopRightDragger, BottomLeftDragger, BottomRightDragger
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class TitleToolButton(QPushButton):
def __init__(self):
super().__init__()
self.init_all()
def init_all(self):
self.setFixedSize(QSize(24,24))
pass
def set_icon(self, icon_path:str):
icon_ = QIcon()
icon_.addFile(icon_path)
self.setIcon(icon_)
class TitleBarWidget(QWidget):
resize_window_signal = pyqtSignal()
min_window_signal = pyqtSignal()
close_window_signal = pyqtSignal()
def __init__(self):
super().__init__()
self.init_all()
def init_all(self):
self.setAttribute(Qt.WA_TranslucentBackground) # 场景透明
self.setAttribute(Qt.WA_StyledBackground, True)
self.hbox_layout = QHBoxLayout()
self.setLayout(self.hbox_layout)
self.hbox_layout.setSpacing(0)
self.hbox_layout.setContentsMargins(0,0,0,0)
self.setFixedHeight(40)
self.setStyleSheet(self.styleSheet() + "QWidget {background-color: rgb(232, 222, 248);} QPushButton { background-color: rgb(232, 222, 248); border: 0px; }"
"QPushButton:hover{background-color: rgb(216, 207, 232);} QPushButton:pressed{background-color: rgb(207, 198, 223);}")
self.hbox_layout.addStretch()
# set min max close buttons
self.min_button = TitleToolButton()
self.min_button.set_icon("resource/image/window/min_btn.png")
self.min_button.clicked.connect(self.slots_min_button_clicked)
self.hbox_layout.addWidget(self.min_button)
self.max_button = TitleToolButton()
self.max_button.set_icon("resource/image/window/max_btn.png")
self.max_button.clicked.connect(self.slots_max_button_clicked)
self.hbox_layout.addWidget(self.max_button)
self.close_button = TitleToolButton()
self.close_button.set_icon("resource/image/window/close_btn.png")
self.close_button.clicked.connect(self.slots_close_button_clicked)
self.hbox_layout.addWidget(self.close_button)
self.hbox_layout.addItem(QSpacerItem(10, 10, QSizePolicy.Fixed, QSizePolicy.Fixed))
def slots_min_button_clicked(self):
"""
明明规则: 槽函数 + 对象 + 事件类型
:return:
"""
self.min_window_signal.emit()
def slots_max_button_clicked(self):
"""
明明规则: 槽函数 + 对象 + 事件类型
:return:
"""
self.resize_window_signal.emit()
def slots_close_button_clicked(self):
"""
明明规则: 槽函数 + 对象 + 事件类型
:return:
"""
self.close_window_signal.emit()
def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
brush = QBrush(Qt.SolidPattern)
color = QColor()
color.setRgb(232, 222, 248)
brush.setColor(color)
painter.setBrush(brush)
painter.setPen(Qt.transparent)
rect = self.rect()
rect.setWidth(rect.width() )
rect.setHeight(rect.height() )
painter.drawRoundedRect(rect, 10, 10)
def mouseDoubleClickEvent(self, a0: QtGui.QMouseEvent) -> None:
self.resize_window_signal.emit()
class BaseFramelessWidget(QWidget):
class State(enum.Enum):
Init = 0
Moving = 1
Resizing = 2
def __init__(self, target=None):
super().__init__()
# 最小700 * 700 最大和界面一样大
self.setMinimumSize(900, 700)
self.setMaximumSize(QDesktopWidget().availableGeometry().width(), QDesktopWidget().availableGeometry().height())
self.state = BaseFramelessWidget.State.Init
self.leftButtonPressed = False
self.borderMargin = 5
self.cornerMargin = 5
self.activeBorderDragger = None
self.borderDraggers = (LeftDragger(), RightDragger(), TopDragger(), BottomDragger())
self.cornerDraggers = (TopLeftDragger(), TopRightDragger(), BottomLeftDragger(), BottomRightDragger())
self.wholeWidgetDragPos = None
self.setAttribute(Qt.WA_TranslucentBackground) # 场景透明
self.target = target
if not target:
self.target = self
self.target.setMouseTracking(True)
self.target.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint)
self.target.setAttribute(QtCore.Qt.WidgetAttribute.WA_Hover)
self.target.installEventFilter(self)
self.vbox_layout = QVBoxLayout()
self.setLayout(self.vbox_layout)
self.title_bar = TitleBarWidget()
self.init_title_bar(self.title_bar)
self.title_bar.resize_window_signal.connect(self.slots_resize_window)
self.title_bar.close_window_signal.connect(self.slots_close_button_clicked)
self.title_bar.min_window_signal.connect(self.slots_min_button_clicked)
def init_title_bar(self, title_bar: QWidget):
"""
初始化标题栏
:param title_bar:
:return:
"""
self.vbox_layout.setSpacing(0)
self.vbox_layout.setContentsMargins(0,0,0,0)
self.vbox_layout.addWidget(title_bar, 0 , Qt.AlignTop)
pass
def setBorderMargin(self, margin):
self.borderMargin = margin
def setCornerMargin(self, margin):
self.cornerMargin = margin
def selectActiveBorderDragger(self, pos):
activeDragger = None
for dragger in self.borderDraggers:
if dragger.isActive(pos, self.target.frameGeometry(), self.borderMargin):
activeDragger = dragger
for dragger in self.cornerDraggers:
if dragger.isActive(pos, self.target.frameGeometry(), self.cornerMargin):
activeDragger = dragger
return activeDragger
def eventFilter(self, o, e):
if e.type() == QEvent.Type.MouseMove or \
e.type() == QEvent.Type.HoverMove or \
e.type() == QEvent.Type.Leave or \
e.type() == QEvent.Type.MouseButtonPress or \
e.type() == QEvent.Type.MouseButtonRelease:
switcher = {
QEvent.Type.MouseMove: self.mouseMove,
QEvent.Type.HoverMove: self.mouseHover,
QEvent.Type.Leave: self.mouseLeave,
QEvent.Type.MouseButtonPress: self.mousePress,
QEvent.Type.MouseButtonRelease: self.mouseRelease
}
switcher.get(e.type())(e)
return True
else:
return super().eventFilter(o, e)
def mousePress(self, event):
if event.buttons() == QtCore.Qt.MouseButton.LeftButton:
self.leftButtonPressed = True
self.activeBorderDragger = self.selectActiveBorderDragger(event.globalPos())
if self.activeBorderDragger is not None:
self.state = BaseFramelessWidget.State.Resizing
self.target.update()
elif self.target.rect().marginsRemoved(QMargins(self.borderMargin, \
self.borderMargin, \
self.borderMargin, \
self.borderMargin)) \
.contains(event.pos()):
self.state = BaseFramelessWidget.State.Moving
self.target.update()
self.wholeWidgetDragPos = event.pos()
def mouseMove(self, event):
if self.leftButtonPressed:
if self.state == BaseFramelessWidget.State.Moving:
self.target.move((event.globalPos() - self.wholeWidgetDragPos))
elif self.state == BaseFramelessWidget.State.Resizing:
rect = self.target.frameGeometry()
self.activeBorderDragger.updateGeometry(rect, event.globalPos())
if rect.width() <= self.target.minimumSize().width():
rect.setLeft(self.target.frameGeometry().x())
if rect.height() <= self.target.minimumSize().height():
rect.setTop(self.target.frameGeometry().y())
self.target.setGeometry(rect)
def mouseHover(self, event):
self.updateCursorShape(self.target.mapToGlobal(event.pos()))
def mouseLeave(self, event):
self.target.unsetCursor()
self.target.update()
def mouseRelease(self, event):
if self.leftButtonPressed:
self.leftButtonPressed = False
self.state = BaseFramelessWidget.State.Init
self.target.update()
self.activeBorderDragger = None
self.wholeWidgetDragPos = None
def updateCursorShape(self, pos):
if self.target.isFullScreen() or self.target.isMaximized():
if self.cursorchanged:
self.target.unsetCursor()
return
self.cursorchanged = True
borderDragger = self.selectActiveBorderDragger(pos)
if borderDragger is not None:
self.target.setCursor(borderDragger.getCursorShape())
else:
self.target.unsetCursor()
self.target.update()
def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
brush = QBrush(Qt.SolidPattern)
color = QColor()
color.setRgb(243, 237, 247)
brush.setColor(color)
painter.setBrush(brush)
painter.setPen(Qt.transparent)
rect = self.rect()
rect.setWidth(rect.width() )
rect.setHeight(rect.height() )
painter.drawRoundedRect(rect, 10, 10)
def slots_resize_window(self):
desktop = QDesktopWidget().availableGeometry()
if self.size() == self.maximumSize():
x = (desktop.width() - self.minimumSize().width()) /2
y = (desktop.height() - self.minimumSize().height()) /2
self.setGeometry(int(x),int(y),self.minimumSize().width(),self.minimumSize().height())
else:
x = (desktop.width() - self.maximumSize().width()) /2
y = (desktop.height() - self.maximumSize().height()) /2
self.setGeometry(x,y,self.maximumSize().width(),self.maximumSize().height())
def slots_min_button_clicked(self):
"""
明明规则: 槽函数 + 对象 + 事件类型
:return:
"""
self.showMinimized()
def slots_close_button_clicked(self):
"""
明明规则: 槽函数 + 对象 + 事件类型
:return:
"""
self.close()
import sys
if __name__ == '__main__':
# 测试功能
app = QApplication(sys.argv)
widget = BaseFramelessWidget()
widget.setMinimumSize(400, 400)
widget.show()
sys.exit(app.exec_())
依赖的文件:
from PyQt5 import QtGui, QtCore
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit
from PyQt5.QtWidgets import QPushButton, QTextEdit, QGridLayout, QHBoxLayout, QFrame, QStyle, QStyleOption
from PyQt5.QtGui import QPainter, QPainterPath, QPen, QBrush, QColor, QPalette, QRegion
from PyQt5.QtCore import QRect, QRectF, QSize, QPoint, QEvent
# Hope this will not affect performance much.
class BorderDragger:
def isActive(self, pos, framerect, borderWidth):
raise Exception("Not implemented")
def updateGeometry(self, framerect, pos):
raise Exception("Not implemented")
def getCursorShape(self):
raise Exception("Not implemented")
class CornerDragger(BorderDragger):
verDragger = BorderDragger()
horDragger = BorderDragger()
def isActive(self, pos, framerect, borderWidth):
return self.verDragger.isActive(pos, framerect, borderWidth) and \
self.horDragger.isActive(pos, framerect, borderWidth)
def updateGeometry(self, framerect, pos):
self.verDragger.updateGeometry(framerect, pos)
self.horDragger.updateGeometry(framerect, pos)
def getCursorShape(self):
raise Exception("Not implemented")
class LeftDragger(BorderDragger):
def isActive(self, pos, framerect, borderWidth):
return framerect.contains(pos) and \
pos.x() >= framerect.x() and \
pos.x() <= framerect.x() + borderWidth
def updateGeometry(self, framerect, pos):
framerect.setLeft(pos.x())
def getCursorShape(self):
return QtCore.Qt.CursorShape.SizeHorCursor
class RightDragger(BorderDragger):
def isActive(self, pos, framerect, borderWidth):
return framerect.contains(pos) and \
pos.x() >= framerect.x() + framerect.width() - borderWidth and \
pos.x() <= framerect.x() + framerect.width()
def updateGeometry(self, framerect, pos):
framerect.setRight(pos.x())
def getCursorShape(self):
return QtCore.Qt.CursorShape.SizeHorCursor
class TopDragger(BorderDragger):
def isActive(self, pos, framerect, borderWidth):
return framerect.contains(pos) and \
pos.y() >= framerect.y() and \
pos.y() <= framerect.y() + borderWidth
def updateGeometry(self, framerect, pos):
framerect.setTop(pos.y())
def getCursorShape(self):
return QtCore.Qt.CursorShape.SizeVerCursor
class BottomDragger(BorderDragger):
def isActive(self, pos, framerect, borderWidth):
return framerect.contains(pos) and \
pos.y() >= framerect.y() + framerect.height() - borderWidth and \
pos.y() <= framerect.y() + framerect.height()
def updateGeometry(self, framerect, pos):
framerect.setBottom(pos.y())
def getCursorShape(self):
return QtCore.Qt.CursorShape.SizeVerCursor
class TopLeftDragger(CornerDragger):
def __init__(self):
self.verDragger = TopDragger()
self.horDragger = LeftDragger()
def getCursorShape(self):
return QtCore.Qt.CursorShape.SizeFDiagCursor
class TopRightDragger(CornerDragger):
def __init__(self):
self.verDragger = TopDragger()
self.horDragger = RightDragger()
def getCursorShape(self):
return QtCore.Qt.CursorShape.SizeBDiagCursor
class BottomLeftDragger(CornerDragger):
def __init__(self):
self.verDragger = BottomDragger()
self.horDragger = LeftDragger()
def getCursorShape(self):
return QtCore.Qt.CursorShape.SizeBDiagCursor
class BottomRightDragger(CornerDragger):
def __init__(self):
self.verDragger = BottomDragger()
self.horDragger = RightDragger()
def getCursorShape(self):
return QtCore.Qt.CursorShape.SizeFDiagCursor
- Python打包后配置资源文件访问
# 资源文件目录访问
def source_path(relative_path):
# 是否Bundle Resource
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS
else:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
# 修改当前工作目录,使得资源文件可以被正确访问
cd = source_path('')
os.chdir(cd)
- PyQt5 创建程序基本流程
import sys
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from PyQt6.QtCore import *
app = QApplication(sys.argv) # 窗体
# 窗体实现部分
widget = QWidget()
widget.show()
# 等待程序退出【阻塞】
ret = app.exec()
sys.exit(ret)
未完。。。待续