十三、QWidget的外观与样式设置
这里所说QWidget的外观设置,是指其背景颜色、边框、字体等设置。为便于后面控件的学习,本小节仅简要介绍API的基本使用。深入的设置将放在QSS和QPalette章节学习,字体将在QFont章节学习。
API函数 | 参数说明 | 返回值 | 功能作用 |
setStyle(self, arg__1) | arg_1:QStyle | None | 设置窗口风格 |
style(self) | None | QStyle | 获取窗口风格 |
setStyleSheet(self, styleSheet) | styleSheet: str | None | 设置窗口样式表 |
setFont(self, arg__1) | arg__1: QFont, str, Sequence[str] | QWidget | 设置字体 |
font(self) | None | QFont | 获取字体 |
setPalette(self, arg__1 | atg_1:QPalette Qt.GlobalColor QColor | None | 设置调色板 |
palette(self) | None | QPalette | 获取调色板 |
setAutoFillBackground(self, enabled) | enabled: bool | None | 设置是否自动填充背景 |
autoFillBackground(self) | None | bool | 获取是否自动填充背景 |
setAttribute(self, arg__1, on=True) | arg__1: Qt.WidgetAttribute on:bool 参数为Qt枚举类 | None | 设置控件或窗口属性,on为Ture时开启,on为Flase时关闭 |
testAttribute(self, arg__1) | arg__1: Qt.WidgetAttribute | bool | 如果控件设置了属性,开启时返回Ture,关闭时返回Flase |
setBackgroundRole(self, arg__1) | arg__1: QPalette.ColorRole 参数为QPalette的枚举类 | None | 设置控件的背景角色 |
backgroundRole(self) | None | QPalette.ColorRole | 获取控件的背景角色 |
fontInfo(self) | None | QFontInfo | 返回控件的字体信息。 |
fontMetrics(self) | None | QFontMetrics | 返回控件的字体度量。 |
1.样式表的设置
PySide6的样式表使用使用setStyleSheet()方法进行设置,其参数是一个QSS标注的字符串或是QSS文件的文件路径。QSS标准类似于CSS,熟悉CSS的基本可以正常使用,不熟悉的我们将在后面样式美化章节学习。
example:
创建一个名为btnStyleSheet普通文本文件,这里没有创建QSS文件,QSS文件的创建及添加到.qrc资源文件将在后面学。
QPushButton {
/* 设置前景色 */
color:blue;
/* 设置背景色 */
background-color:rgb(30,75,10);
/* 设置边框风格 */
border-style:outset;
/* 设置边框宽度 */
border-width:3px;
/* 设置边框颜色 */
border-color:rgb(10,45,110);
/* 设置边框倒角 */
border-radius:15px;
/* 设置字体 */
font:bold 24px;
/* 设置字体族 */
font-family:隶书;
}
将其添加到按钮的样式表里:
# -*- codeing:utf-8 -*-
from PySide6.QtWidgets import QWidget,QApplication,QPushButton
import sys
class testWindow(QWidget):
def __init__(self):
super(testWindow, self).__init__()
self.resize(500,500)
self.setWindowTitle("QSS测试")
self.setStyleSheet("background-color:green")
btn = QPushButton("测试按钮",self)
btn.move(200,200)
btn.resize(100,50)
with open("btnStyleSheet","r",encoding="utf-8") as f:
btn.setStyleSheet(f.read())
if __name__ == '__main__':
app = QApplication(sys.argv)
win = testWindow()
win.show()
sys.exit(app.exec())
运行结果:
2.QPalette和QFont类对控件外观与样式设置
Pyside6有两套系统对控件的外观进行颜色设置,一种是上面介绍的QSS,它的优点在于简单、通用,学过CSS的基本都不用再有学习成本,缺点在于针对具体控件,可以对颜色和外观进行部分设置,某些属性不支持。另一种是QPalette和QFont这些内置的外观设置,QPalette调色板类主要是对颜色类进行定义和设置,QFont主要对字体进行设置,配合QSize大小设置等,可以较好的完成控件的外观定义,不存在兼容性和不支持的情况,但是相对来说需要设置类型较多,需要多条语句才能实现,切QPalette主要是针对色彩类进行调整,控件的外型(如圆形按钮等)就需要配合QStyle类。
QPalette的设置较为复杂,将在高级专篇中介绍。QFont、QFontInfo、QFontDateBase以及QFontMetrics构成了Qt的字体类系统,我们将在后面的字体类章节专门介绍。但由于在接下来的学习中会经常用到,这里简要学习。
API函数 | 参数说明 | 返回值 | 功能作用 |
QFont(self,families,point,weight,italic) | families: Sequence[str], pointSize: int = -1, weight: int = -1, italic: bool = False | None | 创建一个字体 |
QFont(self, family, pointSize, weight, italic) | family: str, pointSize: int = -1, weight: int = -1, italic: bool = False | None | 创建一个字体 |
setBold(self, arg__1) | arg__1: bool | None | 设置是否开启字体为粗体。 |
bold(self) | None | bool | 获取是否开启字体为粗体。 |
setWeight(self, weight) | weight: PySide6.QtGui.QFont.Weight | None | 使用Weight枚举值设置字体粗细 |
weight(self) | None | QFont.Weight | 获取字体的粗细 |
setCapitalization(self, arg__1) | arg__1: PySide6.QtGui.QFont.Capitalization | None | 设置字体的大小写模式 |
capitalization(self) | None | QFont. Capitalization | 获取字体的大小写模式 |
setFamily(self, arg__1) | arg__1: str | None | 设置字体的族 |
famili(self) | None | str | 获取字体的族 |
setItalic(self, b) | b: bool | None | 设置是否开启斜体 |
italic(self) | None | bool | 获取是否开启斜体 |
setPointSize(self, arg__1) | int | None | 设置字体的点大小 |
pointSize(self) | None | int | 获取字体的点大小 |
famliy属性是我们常规所说的字体的类型,比如“宋体、隶书、微软雅黑”等。但是,所设置的famlily需要是系统字体库中存在的字体。
setWeight方法是设置字体的粗细程度,他的参数是一个枚举类:
枚举类 | 枚举常量 | 枚举值 | 功能描述 |
QFont.Weight | Thin | 100 | |
ExtraLight | 200 | ||
Light | 300 | ||
Normal | 400 | ||
Medium | 500 | ||
DemiBold | 600 | ||
Bold | 700 | ||
ExtraBold | 800 | ||
Black | 900 |
setBold()方法的参数设置为True时,与setWeight(QFont.Weight.Bold)等价。
对于字体的颜色需要使用QPalette的前景色进行设置。
example:
# -*- codeing:utf-8 -*-
from PySide6.QtWidgets import QWidget,QApplication,QPushButton
from PySide6.QtGui import QFont,QPalette,QColor
import sys
class testWindow(QWidget):
def __init__(self):
super(testWindow, self).__init__()
self.resize(500,500)
self.setWindowTitle("QSS测试")
btn = QPushButton("测试按钮",self)
btn.move(200,200)
btn.resize(100,50)
font = btn.font()
font.setFamily("宋体")
font.setWeight(QFont.Weight.Bold)
font.setItalic(True)
font.setPointSize(15)
btn.setFont(font)
palette = btn.palette()
palette.setColor(QPalette.ColorRole.ButtonText,QColor("red"))
btn.setPalette(palette)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = testWindow()
win.show()
sys.exit(app.exec())
3.QStyle设置外观
除了上述的两种方法设置外观,针对自定义控件外形以及主题样式,Pyside6提供QStyle类可以对单个控件进行调整和自定义,也可以对整个QApplication进行调整和自定义。
Pyside6的内置窗口组件使用QStyle来完成几乎所有的绘图工作,确保它们看起来与原生窗口组件完全相同。下图显示了九种不同样式的QComboBox。
样式的设置,我们可以通过setStyle()方法进行操作。Pyside6提供了三种可以方便替换的内置系统样式类型“windows11”,“windows”,“windowsvista”和“Fusion”。我们可以通过app.style(QStyleFactory.creat("style"))方式更换系统样式。
from PySide6.QtWidgets import QApplication,QWidget,QComboBox,QStyleFactory
import sys
class testWindow(QWidget):
def __init__(self):
super(testWindow, self).__init__()
self.setWindowTitle("style测试")
self.resize(500,500)
self.combobox = QComboBox(self)
self.combobox.resize(200, 50)
self.combobox.move((self.width()-self.combobox.width())/2,(self.height()-self.combobox.height())/2)
# QStyleFactory.keys()获取内置系统样式的字符串列表
styleList = QStyleFactory.keys()
self.combobox.addItems(styleList)
# 当combobox的当前条目文字改变时将系统风格样式改为条目内字体标识的样式
self.combobox.currentTextChanged.connect(lambda text:app.setStyle(QStyleFactory.create(text)))
if __name__ == '__main__':
app = QApplication(sys.argv)
win = testWindow()
win.show()
sys.exit(app.exec())
上面的代码只是简单的利用Pyside6提供的系统风格样式对控件外观进行改变。当然如果想自定义样式,搞得花里胡哨的,除了使用QSS和QPalette外,就需要自定义QStyle了。
QStyle相当复杂,对于使用Pyside6的我们比使用C++版本的Qt还难。区别在于,自定义QStyle经常需要查看控件源码,而源码只有C++版本的。首先,对于我这样对C++基本不太明白的,看起来非常吃力。同时,QStyle的自定义涉及大量的后续内容,包括基本控件的用法、QPalette的使用、字体系统的使用、绘制系统QPainter、QPen、QBrush的使用,QStyle的QProxyStyle、QCommonStyle的使用,QStyleOption的使用、控件绘制的层次等等。但是,这里面最难得还是看源码。。。。
所以,目前不对QStyle进行学习,只需要知道QWidget.setStyle()是干什么的就可以了。(就这个问题,自学的过程中花了两年多时间才知道他是干什么的)
下面的例子转载自:[Qt]自定义QStyle——实现QProgressBar自定义样式 - During丶 - 博客园 (cnblogs.com)
这个例子原是C++的Qt版本,我对其进行了Pyside6的改写,可以看看效果。
from PySide6.QtWidgets import QComboBox,QPushButton,QProxyStyle,QWidget,\
QStyle,QStyleOption,QProgressBar,QApplication,QVBoxLayout,QStyleOptionProgressBar
from PySide6.QtGui import QPainter,QPalette,QColor,QBrush,QPen,QFont,QLinearGradient,QFontMetricsF
from PySide6.QtCore import Qt,QPointF,QRect,QTimer
import sys
class customStyle(QProxyStyle):
ContentsRect = QRect(0,0,0,0)
LabelRect = QRect(0,0,0,0)
def drawControl(self, element, opt, p, w):
super(customStyle, self).drawControl(element, opt, p, w)
if element == QStyle.ControlElement.CE_ProgressBarContents:
if isinstance(opt,QStyleOptionProgressBar):
pb = opt
else:
pb = QStyleOptionProgressBar()
rect = self.subElementRect(QStyle.SubElement.SE_ProgressBarContents,pb,w)
customStyle.ContentsRect = rect
minimum = pb.minimum
maximum = pb.maximum
progress = pb.progress
pbBits = pb
pbBits.rect = QRect(rect.x(), rect.y(), int(rect.width() * (progress) / (maximum-minimum)), rect.height())
p.setBrush(QColor("#D3D3D3"))
p.drawRoundedRect(rect,8,8)
self.drawPrimitive(QStyle.PrimitiveElement.PE_IndicatorProgressChunk,pbBits,p,w)
if element == QStyle.ControlElement.CE_ProgressBarGroove:
p.setPen(Qt.GlobalColor.transparent)
p.setBrush(Qt.BrushStyle.NoBrush)
p.drawRect(opt.rect)
if element == QStyle.ControlElement.CE_ProgressBarLabel:
if isinstance(opt,QStyleOptionProgressBar):
pBarOpt = opt
else:
pBarOpt = QStyleOptionProgressBar()
pBarOpt.rect = self.subElementRect(QStyle.SubElement.SE_ProgressBarLabel,pBarOpt, w)
progressNumber = pBarOpt.progress /(pBarOpt.maximum - pBarOpt.minimum)
text = "已完成{}%".format(progressNumber * 100)
font = p.font()
font.setLetterSpacing(QFont.SpacingType.AbsoluteSpacing,2)
p.setFont(font)
if progressNumber > 0:
mid = progressNumber
else:
mid = 0.001
mid = 0.999 if mid >= 1 else mid
textGradient = QLinearGradient(QPointF(pBarOpt.rect.left(), pBarOpt.rect.height()),
QPointF(pBarOpt.rect.left(), pBarOpt.rect.top()))
textGradient.setColorAt(0, Qt.GlobalColor.white);
textGradient.setColorAt(mid, Qt.GlobalColor.white);
textGradient.setColorAt(mid + 0.001, Qt.GlobalColor.darkGray);
textGradient.setColorAt(1, Qt.GlobalColor.darkGray);
p.setPen(QPen(QBrush(textGradient), 1))
customStyle.LabelRect = QRect((customStyle.ContentsRect.width() - QFontMetricsF(p.font()).boundingRect("字").width() * len(text)) / 2, pBarOpt.rect.top(), QFontMetricsF(p.font()).boundingRect("字").width() * len(text), w.height())
p.drawText(customStyle.LabelRect,Qt.AlignmentFlag.AlignCenter | Qt.TextFlag.TextSingleLine,text)
def subElementRect(self, element, option, widget):
if element == QStyle.SubElement.SE_ProgressBarLabel:
option.rect = customStyle.LabelRect
return QProxyStyle().subElementRect(element, option, widget)
elif element == QStyle.SubElement.SE_ProgressBarGroove:
return widget.rect()
else:
return QProxyStyle().subElementRect(element, option, widget)
def drawPrimitive(self, element, option, painter, widget):
if element == QStyle.PrimitiveElement.PE_IndicatorProgressChunk:
linear = QLinearGradient()
linear.setStart(0, 0)
linear.setFinalStop(widget.width(), widget.height())
linear.setColorAt(0, QColor(255, 182, 193))
linear.setColorAt(0.5, QColor(100, 149, 237))
linear.setColorAt(1, QColor(255, 222, 173))
painter.setPen(Qt.PenStyle.NoPen);
painter.setBrush(linear);
painter.drawRoundedRect(option.rect, 8, 8);
class testWindow(QWidget):
def __init__(self):
super(testWindow, self).__init__()
self.resize(500,500)
self.number = 0
self.t = QProgressBar(self)
self.t.setMaximum(100)
self.t.setMinimum(0)
self.btn = QPushButton("开始/暂停")
self.btn.clicked.connect(self.control)
self.timer = QTimer(self)
self.timer.timeout.connect(self.progress)
layout = QVBoxLayout()
self.setLayout(layout)
layout.addWidget(self.t)
layout.addWidget(self.btn)
def progress(self):
self.number += 10
self.t.setValue(self.number)
def control(self):
if self.timer.isActive():
self.timer.stop()
else:
self.timer.start(1000)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle(customStyle())
win = testWindow()
win.show()
sys.exit(app.exec())
输出结果:
20240926-013941
十四、窗口及控件的显示与隐藏
API函数 | 参数说明 | 返回值 | 功能作用 |
setHidden(self, hidden) | hidden:bool | None | 设置控件是否隐藏 |
isHidden(self) | None | bool | 查看控件是否隐藏 |
hide() | None | None | 隐藏窗口或小部件 |
setVisible(self, visible) | bool | None | 设置控件是否可见 |
isVisible(self) | None | bool | 查看控件是否可见 |
isVisibleTo(w) | QWidget | bool | 如果小部件的直接或间接父对象可见,小部件可见返回Ture;否则返回Flase。(通常没用) |
show(self) | None | None | 显示窗口及其小部件 |
close(self) | None | bool | 关闭窗口或小部件,如果关闭成功则返回Ture,否则返回Flase |
上面的方法都很简单,功能说的很明白,不再解释说明。
十五、QWidget的右键菜单设置
所谓的右键菜单,就是当我们在当前系统页面或控件面板上点击右键时,会弹出一个命令菜单的面板。如在windows系统桌面右键的时候,会出现“复制”、“粘贴”等命令的一个面板。
API函数 | 参数说明 | 返回值 | 功能作用 |
setConetextMenuPolicy(self,policy) | policy :Qt.ContextMenuPolicy 枚举类型 | None | 设置控件的右键菜单策略 |
contextMenuPolicy(self) | None | None | 获取控件的右键菜单策略 |
QWidget默认是不显示右键菜单的,但是我们可以自己创建菜单再通过setConetextMenuPolicy()更改其菜单策略从而显示自定义的菜单。
policy参数是一个枚举类,如下:
枚举类 | 枚举常量 | 枚举值 | 功能描述 |
Qt.ContextMenuPolicy | NoContextMenu | 0 | 控件没有自己特有的快捷菜单,使用控件父窗口或父容器的快捷菜单 |
DefaultConetextMenu | 1 | 鼠标右键事件交给控件的conetextMenuEvent()函数处理 | |
ActionsContextMenu | 2 | 右键快捷菜单是控件或窗口的actions()方法获取的动作 | |
CustomConetxtMenu | 3 | 用户自定义的快捷菜单,右键鼠标时,发射cunstomContextMenuRequested(QPoint)信号,其中QPoint是鼠标右击是光标位置 | |
PreventContextMenu | 4 | 鼠标右键菜单交给控件的MousePressEvent()函数处理 |
上面所说的QWidget默认不显示菜单并非其菜单策略是NoContextMenu,而是由于其默认菜单策略为DefaultConetextMenu,但其内置的conetextMenuEvent()方法并没有实现菜单的内容。我们可以通过重写其conetextMenuEvent()方法进行实现。
当然,我们如果选择PreventContextMenu策略,就在MousePressEvent()中实现。而大部分情况下,我们会使用CustomConetxtMenu自己定义菜单,这是由于这个方法可以让我们调整菜单的弹出位置。
DefaultConetextMenu、CustomConetxtMenu、PreventContextMenu的实现都需要自定义菜单QMenu并设置子菜单或QAction,关于菜单会在后面学习。
这里举个例子,用最简单的方法,ActionsContextMenu来实现右键菜单。这其中最重要的是我们需要使用QWidget的addAction()或addActions()为其添加动作。
API函数 | 参数说明 | 返回值 | 功能作用 |
addAction(self, action) | action:QAction | None | 添加动作 |
addActions(self, actions) | actions: Sequence[QAction] | None | 添加动作列表 |
example:
from PySide6.QtWidgets import QApplication,QWidget
from PySide6.QtGui import QAction
from PySide6.QtCore import Qt
import sys
class testWindow(QWidget):
def __init__(self):
super(testWindow, self).__init__()
self.setWindowTitle("右键菜单测试")
self.resize(500,500)
self.addActions([QAction("复制",self),QAction("粘贴",self),QAction("剪切",self)])
self.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = testWindow()
win.show()
sys.exit(app.exec())
十六、QWidget的内容更新
API函数 | 参数说明 | 返回值 | 功能作用 |
update() | None 静态函数 | None | 刷新窗口 |
update(self,arg1) | arg1:union[QRegion,QBitmap,QRect] | None | 刷新窗口指定区域。 |
update(self,x,y,w,h) | x:QPoint,y:QPoint,w:width,h:heigth | None | 刷新窗口指定区域。 |
repaint() | None 静态函数 | None | 重绘窗口 |
repaint(self,arg1) | arg1:union[QRegion,QBitmap,QRect] | None | 重绘窗口指定区域。 |
repaint(self,x,y,w,h) | x:QPoint,y:QPoint,w:width,h:heigth | None | 重绘窗口指定区域。 |
Pyside6的应用程序在初始化或者改变内容(外观、样式、字体等)及绘图的时候,都会调用QWidget的paintEvent()事件函数。一般有两种方法来重绘widget。repaint()被调用之后,立即执行重绘,因此repaint是最快的,紧急情况下需要立刻重绘的可以使用repaint()。但是调用repaint的函数不能放到paintEvent中调用,会造成死循环。update()跟repaint()比较,update则更加有优越性。update()调用之后并不是立即重绘,而是将重绘事件放入主消息循环中,由main的event loop来统一调度的(其实也是比较快的)。update在调用paintEvent之前,还做了很多优化,如果update被调用了很多次,最后这些update会合并到一个大的重绘事件加入到消息队列,最后只有这个大的update被执行一次。同时也避免了repaint()中所提到的死循环。因此,一般情况下,我们调用update就够了,跟repaint()比起来,update是推荐使用的。
这部分内容不用太多理解,在后面事件的QPaintEvent中会中重点学习。
十七、QWidget父子控件及电脑屏幕坐标关系
在第4节中介绍了坐标点类并在鼠标的实例中进行了使用。而我们在实际开发过程中不管是创建控件还是绘图都会遇到父子控件的坐标转换及与屏幕之间的相对转换。比如播放器软件的音量调节键。当我们在主控件中点击声音按钮时会弹出音量调节框,那么音量调节框位置的确定过程就会涉及到子控件的位置相对于父控件的位置,以及弹出控件根据子控件位置定位到屏幕的位置。因为,我们的音量调节面板是通过其父控件进行定位的,而鼠标则是则是通过桌面进行定位的,那么我们就要统一坐标系(将二者统一到音量部件的父控件坐标系下或者屏幕坐标系下都可以),而同一坐标系就需要用到以下的方法。
API函数 | 参数说明 | 返回值 | 功能作用 |
mapFrom(self, arg__1, arg__2) | arg_1:QWidget arg_2:QPoint/QPointF | QPoint/QPointF | 将父容器的点映射成控件坐标系下的点 |
mapFromGlobal(self, arg__1) | arg_1:QPoint/QPointF | QPoint/QPointF | 将屏幕坐标系下的点映射成控件坐标系下的点 |
mapFrompParent(self, arg__1) | arg_1:QPoint/QPointF | QPoint/QPointF | 将父容器的点映射成控件坐标系下的点 |
mapTo( self, arg__1, arg__2) | arg_1:QWidget arg_2:QPoint/QPointF | QPoint/QPointF | 将控件坐标系下的点映射成父控件坐标系下的点 |
mapToGlobal(self, arg__1) | arg_1:QPoint/QPointF | QPoint/QPointF | 将控件坐标系下的点映射成p屏幕坐标系下的点 |
mapToParent(self, arg__1) | arg_1:QPoint/QPointF | QPoint/QPointF | 将控件坐标系下的点映射成父控件坐标系下的点 |
十八、拖拽与放置
实际开发的过程中经常跟会用鼠标拖放动作来完成一些操作,例如把一个docx文档拖到word中直接打开。拖放动作需要触发鼠标移动、鼠标释放及拖放的事件。这里只做简单叙述,具体将在事件章节的拖放事件小节进行学习。
十九、QWidget的属性设置
QWidget的属性主要通过setAttribute(arg__1[, on=true])进行设置,其参数为Qt.WidgetAttribute枚举类,这个没办法说。一些设置如:WA_AcceptDrops是接收鼠标拖放的数据,它与
setAcceptDrops(True)作用一样。其他很多方法,需要在具体学习或者工程中学习,例如WA_Hover就经常用于前面所述的QStyle自定义样式时使用。
二十、QWidget的信号
信号 | 参数说明 | 返回值 | 功能作用 |
objectNameChanged(str) | Str-传递新对象名给槽函数 | None | 窗口名称改变发射信号 |
destroyed(obj) | Obj-被销毁的对象传递给槽函数 | None | 窗口被销毁发射信号 |
windowIconChanged(QIcon) | QIcon—传递新图标给槽函数 | None | 窗口图标改变发射信号 |
windowTextChanged(str) | str—传递新图标文字给槽函数 | None | 窗口图标文字改变发射信号 |
windowTitleChanged(str) | str—传递新图标文字给槽函数 | None | 窗口标题改变发射信号 |
customContextMenuRequested(QPoint) | QPoint—右键菜单弹出点坐标 | None | 通过setContextMenuPolicy(Qt.CustomContextMenu)方法设置快捷菜单是自定义菜单,此时右击鼠标时发送信号。 |