PyThon运用PySide6/PyQt居然可以制作如此好看的界面——““创意解析””
导语:
你将获取以下知识:
相关控件:
QWidget QLineEidt QTableWidget QLabel QPushButton
Qss美化
多线程与信号
界面风格:
圆角,简约,暗夜模式
主界面分析:
- 窗口标题栏被替换
- 内容区由搜索框和快捷栏组成
窗口界面详解:
1.首先 (标题栏):
先将默认的标题栏去掉
self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.FramelessWindowHint) # 设置无边框 self.setAttribute(Qt.WA_TranslucentBackground) # 设置窗口背景透明
其次通过Qss实现圆角需要paintEvent的支持
def paintEvent(self, event): opt = QStyleOption() opt.initFrom(self) p = QPainter(self) self.style().drawPrimitive(QStyle.PrimitiveElement.PE_Widget,opt,p,self) super().paintEvent(event)
接着我们开始重实现标题栏
右边由:QLabel构成 左边由:QPushButton构成值得留意的是Qss背景不支持?
self.setAttribute(Qt.WA_StyledBackground, True) # 支持qss设置背景
重实现 标题栏拖拽窗口移动
def mousePressEvent(self, event): super().mousePressEvent(event) if event.button() == Qt.MouseButton.LeftButton: self._press = True self.mouseStartPos = event.globalPos() # 获取点击屏幕坐标 self.windowTopLeftPos = self.mapToGlobal(self.frameGeometry().topLeft()) # 获取窗口左上角屏幕坐标 def mouseMoveEvent(self, event): super().mouseMoveEvent(event) if self._press: distance = event.globalPos() - self.mouseStartPos # 计算移动距离 self.window().move(distance + self.windowTopLeftPos) # 移动窗口 def mouseReleaseEvent(self, event): super().mouseReleaseEvent(event) if self._press: self._press = False # 注意坐标系应相当于屏幕坐标系
2.其次 (搜索框):
主要是对QLineEidt的样式设置和PaintEvent的重写
def paintEvent(self, event): super().paintEvent(event) painter = QPainter(self) img = QPixmap('./img/find.png').scaled(34,34,Qt.AspectRatioMode.IgnoreAspectRatio, Qt.TransformationMode.SmoothTransformation) painter.drawPixmap( 600-25-18,2, img) # Qss QLineEdit{ color: white; padding-left: 12px; padding-right: 40px; border: 2px solid gray; border-radius: 20px; background-color: #282c35; }
至于QLineEdit的搜索按钮点击事件,通过重写mousePressEvent实现,详解源代码
3.最后 (快捷栏):
它由QTableWidet构成,单元格由两个QLabel构成
值得注意点是:QTableWidget设置样式(stylesheet)后,单元格行高列宽会失效,解决方法如下:
self.horizontalHeader().setDefaultSectionSize(int) self.verticalHeader().setDefaultSectionSize(int)
标题和图标,通过爬虫获取
from PIL import Image from urllib.parse import urlsplit import requests as rq def getIcon(url:str): # 知识点:网址通常开放 主机名 + “/favicon.ico” 为图标api parser_url = urlsplit(url) netloc = parser_url.netloc # 获取主机名 icon_url = parser_url.scheme + '://' + netloc + "/favicon.ico" savePath = f'./img/{netloc.replace(".","_")}.ico' if not os.path.exists(savePath): img = Image.open(BytesIO(rq.get(icon_url,headers=HEADERS,verify=False).content)) if img.width < 64 or img.height < 64: img = img.resize((64,64)) img.save(savePath,sizes=[(64,64)]) return netloc.replace(".","_") # 获取标题部分详见源代码
由于爬虫会阻塞主线程,所以使用多线程加载是个好方法
from threading import Thread currentThreading = Thread(target=self.cellInit, args=(row,index,DEFAULTURLS[key],key,)) currentThreading.start() class shortcutsBar(QTableWidget): addCell = Signal(int,int,str,str) def __init__(self, parent=None): super().__init__(parent) self.bind() self.initSetup() def bind(self): self.addCell.connect(self.setCellEvent) def initSetup(self): # 详解源代码 ....... DEFAULTURLS ={'SteamWorkShop': 'https://steamcommunity.com/', 'Watt Toolkit': 'https://steampp.net/'} for index,key in enumerate(DEFAULTURLS.keys()): row = 0 if index > 5: index -= 5 row = 1 currentThreading = Thread(target=self.cellInit, args=(row,index,DEFAULTURLS[key],key,)) currentThreading.start() def cellInit(self,row:int,col:int,url:str,text:str): img = getIcon(url) self.addCell.emit(row,col,img,text) @Slot(int,int,str,str) def setCellEvent(self,row:int,col:int,img:str,text:str): cell = QWidget() cellLayout = QVBoxLayout(cell) cellText = QLabel(text) cellText.setFont(QFont('微软雅黑',12)) cellText.setStyleSheet('color: #ffffff') cellIcon = iconLabel(f'./img/{img}.ico',text) cellLayout.addWidget(cellIcon,alignment=Qt.AlignmentFlag.AlignCenter) cellLayout.addWidget(cellText,alignment=Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignBottom) self.setCellWidget(row,col,cell)
这里采用原生多线程 + Signal 的原因是:在多线程中操作任何UI容易造成软件崩溃
如果是连续往UI添加东西要给界面留绘制时间 (sleep)