QObject不是可视化控件,是所有QT对象的基类。
Qwidget则是所有可视化控件的基类。
所有的可视控件的基类(仅有全部可视控件的共性,比如是个方块,可调整大小,移动位置等等);
是一个最简单的空白控件;
控件是用户界面的最小元素;
功能:接收各种事件(鼠标、键盘…),绘制在桌面上,展示给用户看。
每个控件都是矩形的,它们按Z轴顺序排序(Z轴面向人,后面的会被前面的遮挡,后面的是父控件);
控件由其父控件和前面的控件剪切;
没有父控件的控件,称之为窗口;
一般会被包装一个框架(标题栏、图标…),可以通过某些设置更改,窗口不能自动显示,必须调用show()方法。
例1:看一下QWidegt的空白样子。
# 0.导入包和模块
from PyQt5.Qt import *
import sys
# 1.创建应用程序对象
app = QApplication(sys.argv)
window = QWidget()
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
例2:Qwidget的嵌套
# 0.导入包和模块
from PyQt5.Qt import *
import sys
app = QApplication(sys.argv)
window = QWidget()
window.resize(500, 500)
red = QWidget(window)
red.resize(100, 100)
red.setStyleSheet("background-color:red;")
window.show()
sys.exit(app.exec_())
例3:显示时,子控件不会超出父控件的范围。
red.move(450, 0)
例3:两个子控件沿Z轴分布,后面的会受到前面的裁剪。
green = QWidget(window)
green.resize(100, 100)
green.move(450, 50)
green.setStyleSheet("background-color:green;")
class QWidget(_ _PyQt5_QtCore.QObject, _ _PyQt5_QtGui.QPaintDevice):
继承自QObject(对象)、QPaintDevice(绘制类)。
方法二:属性__bases__
print(QWidget.__bases__)
运行结果:(
PS:小括号代表元组,bases只是直接父类。
print(QWidget.mro())
运行结果:[
PS:mro代表检索整个链条,不仅包括直接父类,还包括父类的父类。
QWidget->QObject->wrapper->QPaintDevice->simplewrapper->object(python的对象)
_init_(self, parent=None, flags):parent,父控件;flags,标志位。
比如:red = QWidget(window)
创建控件的时候,设置父控件以及标志位(顶层窗口相关细讲)。
左上角为坐标原点,向右为x轴正方向,向下为y轴正方向。
控件位置参照:子控件参照父控件,顶层控件则参照桌面。
浅灰色:用户区(用户可以在这儿操作,比如加一个标签栏)。
蓝色:标题栏。
深灰色:外部框架。
注意: 控件显示完毕之后,具体的位置或者尺寸数据才会正确。
# 0.导入包和模块
from PyQt5.Qt import *
import sys
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = QWidget()
# 2.2 设置控件
window.setWindowTitle("")
window.resize(500, 500)
window.move(100, 100)
print("显示之前获取:")
print(window.pos())
print(window.geometry())
# 2.3 展示控件
window.show()
print("-" * 30)
print("显示之后获取:")
print(window.pos())
print(window.geometry())
# 3.进入消息循环
sys.exit(app.exec_())
注意:resize有最小值限制,因为要放的下标题栏的图标。
window.resize(500, 500)
window.move(100, 100)
window.setGeometry(100, 100, 500, 500)
window.show()
window.setGeometry(0, 0, 500, 500)
注意:setGeometry必须在显示之后设置,显示之前不确定要不要包装窗口框架,可能会出错。
自适应大小:
label = QLabel(window)
label.setText("标签啊")
label.move(100, 100)
label.setStyleSheet("background-color:yellow;")
def cao():
new_content = label.text() + "标签啊"
label.setText(new_content)
label.adjustSize()
btn = QPushButton(window)
btn.setText("按钮啊")
btn.move(200, 200)
btn.clicked.connect(cao)
固定尺寸:
window.setWindowTitle("")
# window.resize(500, 500)
window.setFixedSize(500, 500)
window.move(100, 100)
最大化按钮失效了!
布局控件位置
计算尺寸
创建一个窗口, 设置尺寸为500 x 500, 位置为 300, 300。
window.resize(500, 500)
window.move(300, 300)
通过给定的的个数, 在一个窗口内创建相应个数的子控件。要求:按照九宫格的布局进行摆放,一行3列。
# 0.导入包和模块
from PyQt5.Qt import *
import sys
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = QWidget()
# 2.2 设置控件
window.setWindowTitle("")
window.resize(500, 500)
window.move(300, 300)
# 总的控件个数
widget_count = 20
column_count = 3
row_count = (widget_count-1)//column_count + 1 # 整除
# 计算一个控件的宽和高
widget_width = window.width()/column_count
widget_height = window.height()/row_count
for i in range(0, widget_count):
w = QWidget(window)
x = i % column_count
y = i // column_count
w.resize(widget_width, widget_height)
w.move(x * widget_width, y * widget_height)
w.setStyleSheet("background-color: yellow;border: 1px solid purple")
# 2.3 展示控件
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
注意:行号是除数,列号是余数。
注意:控件完全展示前后会有所差异。
限定控件大小
创建一个窗口, 设置最小尺寸和最大尺寸。要求:最小为200, 200;最大为400, 400。测试通过resize是否可以改变。(不能)
# 0.导入包和模块
from PyQt5.Qt import *
import sys
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = QWidget()
# 2.2 设置控件
window.setWindowTitle("最小尺寸最大尺寸")
# window.resize(500, 500)
window.setMinimumSize(200, 200)
window.setMaximumSize(400, 400)
# 2.3 展示控件
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
注意:必须是控件本身留够对应的大小。
# 0.导入包和模块
from PyQt5.Qt import *
import sys
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = QWidget()
# 2.2 设置控件
window.setWindowTitle("内容边距的设定")
window.resize(500, 500)
label = QLabel(window)
label.setText("标签在此")
label.resize(300, 300)
label.setStyleSheet("background-color: cyan;")
print(label.contentsRect())
print(label.getContentsMargins())
# 2.3 展示控件
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
结果:什么都没设置的时候,内容区域是整个控件的范围,内容文本展示在内容区域水平靠左、垂直居中的位置。
修改内容区域后:
label.setContentsMargins(100, 200, 0, 0)
print(label.contentsRect())
print(label.getContentsMargins())
调整控件内容边距, 使得显示更好看。
创建一个窗口, 包含一个标签。要求:标签内容为"Hello Sz";内容区域大小为100, 60;将内容放在标签的右下角。
label.setText("Hello Sz")
label.resize(300, 300)
label.setStyleSheet("background-color: cyan;")
label.setContentsMargins(200, 240, 0, 0)
Qt.ArrowCursor(arrow箭头)
Qt.UpArrowCursor
Qt.CrossCursor
Qt.IBeamCursor(工型光标,beam光)
Qt.WaitCursor
Qt.BusyCursor
Qt.ForbiddenCursor
Qt.PointingHandCursor
Qt.WhatsThisCursor
Qt.SizeVerCursor
Qt.SizeHorCursor
Qt.SizeBDiagCursor
Qt.SizeAllCursor
Qt.SplitVCursor(Split分裂)
Qt.SplitHCursor
Qt.OpenHandCursor
Qt.ClosedHandCursor
Qt.BlankCursor(为了看出来加了个框,其实是一片空白)
自定义图标:需要QCursor对象
例1:
label = QLabel(window)
label.setText("标签在此")
label.resize(100, 100)
label.setStyleSheet("border:1px solid purple")
label.setCursor(Qt.ForbiddenCursor)
例2:
查看setCursor定义,发现可以传入独一无二的枚举类型参数,也可以传入自定义的QCursor对象。
def setCursor(self, Union, QCursor=None, Qt_CursorShape=None):
""" setCursor(self, Union[QCursor, Qt.CursorShape]) """
pass
查看QCursor对象的定义,其中一种构造方法是传入QPixmap图片对象。
class QCursor(__sip.simplewrapper):
"""
QCursor()
QCursor(QBitmap, QBitmap, hotX: int = -1, hotY: int = -1)
QCursor(QPixmap, hotX: int = -1, hotY: int = -1)
QCursor(Union[QCursor, Qt.CursorShape])
QCursor(Any)
"""
再去查看QPixmap对象的定义,str即图片的路径,如果在同一目录下直接写就行。
class QPixmap(QPaintDevice):
"""
QPixmap()
QPixmap(int, int)
QPixmap(QSize)
QPixmap(str, format: str = None, flags: Union[Qt.ImageConversionFlags, Qt.ImageConversionFlag] = Qt.ImageConversionFlag.AutoColor)
QPixmap(List[str])
QPixmap(QPixmap)
QPixmap(Any)
"""
设置自定义鼠标
pixmap = QPixmap("star.png")
# 缩放尺寸,返回值即为修改调整后的图片
newPixmap = pixmap.scaled(100, 100)
# 修改热点为右下角(默认-1,-1)
cursor = QCursor(newPixmap, 100, 100)
window.setCursor(cursor)
unsetCursor()
window.unsetCursor()
cursor() -> QCursor
print(window.cursor())
结果:
hasMouseTracking():判定是否设置了鼠标跟踪;
setMouseTracking(bool):设置鼠标是否跟踪;
所谓的鼠标跟踪,其实就是设置检测鼠标移动事件的条件。不跟踪:鼠标移动时,必须处于按下状态,才会触发mouseMoveEvent事件;跟踪:鼠标移动时,不处于按下状态,也会触发mouseMoveEvent事件。
# 0.导入包和模块
from PyQt5.Qt import *
import sys
class MyWindow(QWidget):
def mouseMoveEvent(self, evt):
print("鼠标移动了")
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = MyWindow()
# 2.2 设置控件
window.setWindowTitle("鼠标操作")
window.resize(500, 500)
window.setMouseTracking(True)
# 2.3 展示控件
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
结果:不按下鼠标也会跟踪。
事件对象QMouseEvent里面有全局(针对屏幕左上角)位置、局部(针对控件左上角)位置等方法。
全局:
局部:
pixmap();pos();setPos(x, y)…
根据特定场景,设置鼠标样式;使得用户交互时更加明确。
创建一个窗口,内部有一个label控件。要求:鼠标移入窗口时,让label位置跟随鼠标位置;让鼠标设置为指定图标,并封装。
import sys
from PyQt5.Qt import *
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.setWindowTitle("鼠标相关案例")
self.resize(500, 500)
self.move(200, 200)
self.setMouseTracking(True)
pixmap = QPixmap("star.png").scaled(60, 60)
cursor = QCursor(pixmap)
self.setCursor(cursor)
label = QLabel(self)
self.label = label
label.setText("标签在此")
label.move(100, 100)
label.setStyleSheet("background-color:cyan;")
def mouseMoveEvent(self, evt):
print("鼠标移动到:", evt.localPos())
# label2 = self.findChild(QLabel) 封装了就不用了,label已经变成类里的一个属性
self.label.move(evt.localPos().x(), evt.localPos().y())
# label2.move(evt.localPos()) arguments did not match any overloaded call
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
显示和关闭事件:
showEvent(QShowEvent) :控件显示时调用;
closeEvent(QCloseEvent) :控件关闭时调用。
移动事件:
moveEvent(QMoveEvent) :控件移动时调用,窗口显示时已经移动了两次,移动的判定是x、y是否变化。
调整大小:
resizeEvent(QResizeEvent) :控件调整大小时调用,窗口显示时已经改变了一次;
鼠标事件:
(1)进入和离开事件:enterEvent(QEvent) 鼠标进入时触发;leaveEvent(QEvent) 鼠标离开时触发,可通过这个显示被选中的阴影效果。
(2)鼠标按下时触发:mousePressEvent(QMouseEvent);
(3)鼠标释放时触发:mouseReleaseEvent(QMouseEvent),一次按下加一次释放(控件的空间范围内)就是一次单击。
(4)鼠标双击时触发:mouseDoubleClickEvent(QMouseEvent);
(5)鼠标按下后移动时触发:mouseMoveEvent(QMouseEvent),用来跟踪鼠标。
setMouseTracking(True):追踪设置后,没有按下的移动也能触发。
键盘事件:
keyPressEvent(QKeyEvent):键盘按下时调用;
keyReleaseEvent(QKeyEvent):键盘释放时调用。
如果想监测是哪个按键被按下:查看QKeyEvent事件对象。
焦点事件:
focusInEvent(QFocusEvent):获取焦点时调用;
focusOutEvent(QFocusEvent):失去焦点时调用。
拖拽事件:(可上传文件)
dragEnterEvent(QDragEnterEvent):拖拽进入控件时调用;
dragLeaveEvent(QDragLeaveEvent):拖拽离开控件时调用;
dragMoveEvent(QDragMoveEvent):拖拽在控件内移动时调用;
dropEvent(QDropEvent):拖拽放下时调用。
绘制事件:(美化控件)
paintEvent(QPaintEvent):显示控件, 更新控件时调用;
改变事件:(中英文切换)
changeEvent(QEvent):窗体改变, 字体改变时调用。
右键菜单:
contextMenuEvent(QContextMenuEvent):访问右键菜单时调用。
输入法:
inputMethodEvent(QInputMethodEvent):输入法调用。
例1:因为是继承特定对象的特定方法,所以采用面向对象的写法。
# 0.导入包和模块
from PyQt5.Qt import *
# 继承类、定义自己的方法
class Window(QWidget):
# 初始化
def __init__(self):
super(Window, self).__init__()
self.setWindowTitle("事件消息的学习")
self.resize(500, 500)
self.setup_ui()
# 存放所有子控件以及子控件的配置操作
def setup_ui(self):
label = QLabel(self)
label.setText("标签签")
def showEvent(self, a0: QShowEvent) -> None:
print("窗口被展示")
def closeEvent(self, a0: QCloseEvent) -> None:
print("窗口被关闭")
def moveEvent(self, a0: QMoveEvent) -> None:
print("窗口被移动")
def resizeEvent(self, a0: QResizeEvent) -> None:
print("窗口改变了尺寸大小")
def enterEvent(self, a0: QEvent) -> None:
print("鼠标进来了")
self.setStyleSheet("background-color: yellow;")
def leaveEvent(self, a0: QEvent) -> None:
print("鼠标离开了")
self.setStyleSheet("background-color: green;")
def mousePressEvent(self, a0: QMouseEvent) -> None:
print("鼠标被按下")
def mouseReleaseEvent(self, a0: QMouseEvent) -> None:
print("鼠标被释放")
def mouseDoubleClickEvent(self, a0: QMouseEvent) -> None:
print("鼠标双击")
def mouseMoveEvent(self, a0: QMouseEvent) -> None:
print("鼠标移动")
def keyPressEvent(self, a0: QKeyEvent) -> None:
print("键盘某一按键被按下")
def keyReleaseEvent(self, a0: QKeyEvent) -> None:
print("键盘某一按键被释放")
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
当一个控件被触发了一个特定的行为时, 就会调用特定的方法, 来将事件传递给开发人员, 方便处理。重写这些事件方法, 就可以监听相关的信息。
例2:事件机制之事件的传递
# 0.导入包和模块
from PyQt5.Qt import *
import sys
class window(QWidget):
def mousePressEvent(self, a0: QMouseEvent) -> None:
print("顶层窗口被按下")
class Midwindow(QWidget):
def mousePressEvent(self, a0: QMouseEvent) -> None:
print("中间控件被按下")
pass
class Label(QLabel):
def mousePressEvent(self, evt) -> None:
print("标签控件被按下")
# evt.accept()
# 接受事件,不会继续上传给父控件
# print(evt.isAccepted())
# 输出True证明本来就会自动被接受
# 忽略事件,继续上传给父控件
evt.ignore()
pass
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = window()
# 2.2 设置控件
window.setWindowTitle("事件转发")
window.resize(500, 500)
mid_window = Midwindow(window)
mid_window.resize(300, 300)
mid_window.setAttribute(Qt.WA_StyledBackground, True)
mid_window.setStyleSheet("background-color:yellow;")
label = Label(mid_window)
label.setText("标签在此")
label.move(100, 100)
label.setStyleSheet("background-color:cyan;")
btn = QPushButton(mid_window)
btn.setText("按钮在此")
btn.move(50, 50)
# 2.3 展示控件
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
PS:
1、创建一个窗口包含一个标签,要求:鼠标进入标签时, 展示"欢迎光临";鼠标离开标签时, 展示"谢谢惠顾"。
# 0.导入包和模块
from PyQt5.Qt import *
import sys
class newLabel(QLabel):
def enterEvent(self, a0: QEvent) -> None:
self.setText("欢迎光临")
def leaveEvent(self, a0: QEvent) -> None:
self.setText("谢谢惠顾")
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = QWidget()
# 2.2 设置控件
window.setWindowTitle("鼠标操作案例1")
window.resize(500, 500)
label = newLabel(window)
label.resize(200, 200)
label.move(100, 100)
label.setStyleSheet("background-color:cyan;")
# 2.3 展示控件
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
2、创建一个窗口, 监听用户按键。要求:监听用户输入Tab键;监听用户输入Ctrl+S组合键;监听用户输入Ctrl+Shift+A。快捷键的实现
(按下键盘的事件是def keyPressEvent(self, QKeyEvent),其中按下哪个键的信息存放在QKeyEvent里)
# 普通键
def key(self): # real signature unknown; restored from __doc__
""" key(self) -> int """
return 0
# 修饰键
def modifiers(self): # real signature unknown; restored from __doc__
""" modifiers(self) -> Qt.KeyboardModifiers """
pass
# 0.导入包和模块
from PyQt5.Qt import *
import sys
class newLabel(QLabel):
def keyPressEvent(self, evt) -> None:
# print("xxx")
if evt.key() == Qt.Key_Tab:
print("用户点击了Tab键")
if evt.modifiers() == Qt.ControlModifier and evt.key() == Qt.Key_S:
print("用户点击了CTRL+S键")
if evt.modifiers() == Qt.ControlModifier | Qt.ShiftModifier and evt.key() == Qt.Key_A:
print("用户点击了CTRL+Shift+A键")
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = QWidget()
# 2.2 设置控件
window.setWindowTitle("鼠标操作案例1")
window.resize(500, 500)
label = newLabel(window)
label.resize(200, 200)
label.move(100, 100)
label.setStyleSheet("background-color:cyan;")
# 标签捕获键盘操作
label.grabKeyboard()
# 2.3 展示控件
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
注意:
label.grabKeyboard()可以捕捉键盘行为到自己身上。
修饰键:比如Tab就不是修饰键,因为点击后会输入制表符。
Qt.NoModifier:没有修饰键;
Qt.ShiftModifier:Shift键被按下;
Qt.ControlModifier:Ctrl键被按下;
Qt.AltModifier:Alt键被按下
…
多个修饰键的组合之间使用按位或运算(原理:任意两个修饰键的或运算结果是唯一的)
普通键:Qt.Key_xxx
3、完成窗口, 用户区支持拖拽。要求:鼠标点击了用户区拖拽也可以移动窗口。(以前只有点击标题栏可以移动)
如果嫌弃系统标题栏,想自己画一个标题栏就会用到这个技能。
# 0.导入包和模块
from PyQt5.Qt import *
# 继承类、定义自己的方法
class Window(QWidget):
# 初始化
def __init__(self):
super(Window, self).__init__()
self.setWindowTitle("窗口移动")
self.resize(500, 500)
self.setup_ui()
# 存放所有子控件以及子控件的配置操作
def setup_ui(self):
label = QLabel(self)
label.setText("标签签")
def mousePressEvent(self, evt):
# print("鼠标按下")
# 确定两个点
self.mouse_x = evt.globalX()
self.mouse_y = evt.globalY()
# print(self.mouse_x, self.mouse_y)
self.window_x = self.x()
self.window_y = self.y()
def mouseMoveEvent(self, evt):
# print("鼠标移动")
# 移动向量
move_x = evt.globalX() - self.mouse_x
move_y = evt.globalY() - self.mouse_y
# print(move_x, move_y)
# 目标位置
dest_x = self.window_x + move_x
dest_y = self.window_y + move_y
self.move(dest_x, dest_y)
def mouseReleaseEvent(self, evt):
print("鼠标释放")
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
首先思考:需要监听三个事件"鼠标按下",“鼠标弹起”,“鼠标移动”。
(1) 确定两个点:鼠标左键的点(确定鼠标的移动向量(x, y)),窗口坐标点(定位窗口移动move)
(2) 让窗口坐标点走一个移动向量。
(3) 参考对象有两个选择——桌面和窗口控件,选择不动的作为参考对象,也就是桌面。
到这看似已经完成了,但如果设置了鼠标跟踪(window.setMouseTracking(True)也就是不用单击鼠标左键,就会跟踪鼠标)的话,程序会崩掉。报错如下:
Traceback (most recent call last):
File "C:/Users/16041/PycharmProjects/useQt/7-QWidget-案例3.py", line 31, in mouseMoveEvent
move_x = evt.globalX() - self.mouse_x
AttributeError: 'Window' object has no attribute 'mouse_x'
就是说此时没有self.mouse_x这一变量,这是因为还没有执行过鼠标单击操作就开始了移动。
此时只需要一个标记就可解决此安全隐患,标记贯穿于初始化、按下、释放、移动整个过程。
# 0.导入包和模块
from PyQt5.Qt import *
# 继承类、定义自己的方法
class Window(QWidget):
# 初始化
def __init__(self):
super(Window, self).__init__()
self.setWindowTitle("窗口移动")
self.resize(500, 500)
self.setup_ui()
self.moveflag = False
# 存放所有子控件以及子控件的配置操作
def setup_ui(self):
label = QLabel(self)
label.setText("标签签")
def mousePressEvent(self, evt):
self.moveflag = True
# print("鼠标按下")
# 确定两个点
self.mouse_x = evt.globalX()
self.mouse_y = evt.globalY()
# print(self.mouse_x, self.mouse_y)
self.window_x = self.x()
self.window_y = self.y()
def mouseMoveEvent(self, evt):
if self.moveflag:
# print("鼠标移动")
# 移动向量
move_x = evt.globalX() - self.mouse_x
move_y = evt.globalY() - self.mouse_y
# print(move_x, move_y)
# 目标位置
dest_x = self.window_x + move_x
dest_y = self.window_y + move_y
self.move(dest_x, dest_y)
def mouseReleaseEvent(self, evt):
self.moveflag = False
# print("鼠标释放")
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
window.setMouseTracking(True)
sys.exit(app.exec_())
现在还有个问题是按下鼠标上任意键都会移动,而不是仅仅左键。
def mousePressEvent(self, evt):
if evt.button() == Qt.LeftButton:
self.moveflag = True
# print("鼠标按下")
# 确定两个点
self.mouse_x = evt.globalX()
self.mouse_y = evt.globalY()
# print(self.mouse_x, self.mouse_y)
self.window_x = self.x()
self.window_y = self.y()
# 0.导入包和模块
from PyQt5.Qt import *
import sys
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = QWidget()
# 2.2 设置控件
window.setWindowTitle("父子关系学习")
window.resize(500, 500)
label1 = QLabel(window)
# label1.setParent()
label1.setText("标签1")
label1.move(200, 200)
label1.setStyleSheet("background-color: cyan;")
label2 = QLabel(window)
# label1.setParent()
label2.setText("标签2")
label2.move(50, 50)
label2.setStyleSheet("background-color: cyan;")
label3 = QLabel(window)
# label1.setParent()
label3.setText("标签3")
label3.move(100, 100)
label3.setStyleSheet("background-color: cyan;")
print("window:", window)
print("label1:", label1)
print("label2:", label2)
print("label3:", label3)
print(window.childAt(210, 210))
print(label2.parentWidget())
print(window.childrenRect())
# 2.3 展示控件
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
创建窗口, 包含若干Label控件。要求:点击哪个标签, 就让哪个标签背景变红。
方法一:使用父控件处理
# 0.导入包和模块
from PyQt5.Qt import *
import sys
class newWidget(QWidget):
def mousePressEvent(self, evt):
if evt.button() == Qt.LeftButton:
local_x = evt.x()
local_y = evt.y()
press_widget = self.childAt(local_x, local_y)
if press_widget:
press_widget.setStyleSheet("background-color:cyan;")
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = newWidget()
# 2.2 设置控件
window.setWindowTitle("自学父子关系")
window.resize(500, 500)
for i in range(1, 11):
label = QLabel(window)
label.setText("label" + str(i))
label.move(40 * i, 40 * i)
label.setStyleSheet("border:1px solid black;")
# 2.3 展示控件
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
还需要注意通过循环创建标签的方法,label的变量名可以是一样的。
方法二:自定义QLabel子类
class newLabel(QLabel):
def mousePressEvent(self, evt):
if evt.button() == Qt.LeftButton:
self.setStyleSheet("background-color:red;")
注意:以上操作专指同级控件,默认后添加的在上面。
# 0.导入包和模块
from PyQt5.Qt import *
import sys
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = QWidget()
# 2.2 设置控件
window.setWindowTitle("层级关系调整")
window.resize(500, 500)
label = QLabel(window)
label.setText("标签1")
label.resize(200, 200)
label.setStyleSheet("background-color:green;")
label2 = QLabel(window)
label2.setText("标签2")
label2.resize(200, 200)
label2.move(50, 50)
label2.setStyleSheet("background-color:cyan;")
# 2.3 展示控件
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
label2.lower()
或 label.raise_()
或 label2.stackUnder(label)
效果均如下:
改进:如果点谁谁在上面。
class newLabel(QLabel):
def mousePressEvent(self, evt):
self.raise_()
或
class newQWidget(QWidget):
def mousePressEvent(self, evt):
sub_widget = self.childAt(evt.x(), evt.y())
if sub_widget:
sub_widget.raise_()
需要调整控件Z轴顺序。
PS:不知道函数里面放什么变量,就先创建一个提示类型的变量,再逐层追溯。
class QIcon(__sip.wrapper):
"""
QIcon()
QIcon(QPixmap)
QIcon(QIcon)
QIcon(str)
QIcon(QIconEngine)
QIcon(Any)
"""
icon = QIcon("star.png")
window.setWindowIcon(icon)
print(window.windowIcon())
setWindowState(state)
Qt.WindowNoState,无状态;(默认)
Qt.WindowMinimized,最小化;
Qt.WindowMaximized,最大化;
Qt.WindowFullScreen,全屏;(通过CTRL+alt+delete在任务管理器里关)
Qt.WindowActive,活动窗口。(多个窗口时谁后show谁在上面,也可用setWindowState设置)
windowState()
print(window.windowState() == Qt.WindowNoState)
window.setWindowState(Qt.WindowMinimized)
window.setWindowState(Qt.WindowMaximized)
window.setWindowState(Qt.WindowFullScreen)
window.show()
window2.show()
window.setWindowState(Qt.WindowActive)
最终还是窗口1在上面。
控制 自定义标题栏的最大小化、关闭等等
使用时可不使用show()方法。
showFullScreen():全屏显示,不包含窗口框架。
showMaximized():最大化,包括窗口框架。
showMinimized():最小化。
showNormal():正常。
例子:双击窗口最大化
class newWidget(QWidget):
def mouseDoubleClickEvent(self, evt):
if evt.button() == Qt.LeftButton:
self.showMaximized()
判定(比如最大化和正常显示的最大化功能按键长得不一样)
isMinimized():是否是最小化窗口;
isMaximized():是否是最大化窗口;
isFullScreen():是否全屏。
例:双击,若已经是最大化就正常化,否则最大化。
class newWidget(QWidget):
def mouseDoubleClickEvent(self, evt):
if evt.button() == Qt.LeftButton:
if self.isMaximized():
self.showNormal()
else:
self.showMaximized()
window.setWindowFlags(Qt.WindowStaysOnTopHint)
windowFlags()
其中参数Qt.WindowStaysOnTopHint可以是:
窗口样式
Qt.Widget:默认参数,是一个窗口或控件。有父控件,就是一般控件;没有父控件,则是窗口。
窗口包括窗口边框和标题栏(图标、标题、最小化、最大化、关闭)。
Qt.Window:是一个窗口,有窗口边框和标题。
标题栏(图标、标题、最小化、最大化、关闭)
Qt.Dialog:是一个对话框窗口,有窗口边框和标题栏。
标题栏(图标、标题、问号、关闭)
Qt.Sheet:是一个窗口或部件Macintosh表单。(跟上面有啥区别???)
Qt.Drawer:是一个窗口或部件Macintosh抽屉。
Qt.Popup:是一个弹出式顶层窗口。
Qt.Tool:是一个工具窗口。
Qt.ToolTip:是一个提示窗口,没有标题栏和窗口边框。
Qt.SplashScreen:是一个欢迎窗口,是QSplashScreen构造函数的默认值。(和上面的区别?)
Qt.SubWindow:是一个子窗口。
顶层窗口外观标志
Qt.MSWindowsFixedSizeDialogHint:窗口无法调整大小。
Qt.FramelessWindowHint:窗口无边框。
Qt.CustomizeWindowHint:有边框但无标题栏和按钮,不能移动和拖动。
Qt.WindowTitleHint:添加标题栏和一个关闭按钮。
Qt.WindowSystemMenuHint:添加系统目录和一个关闭按钮。
Qt.WindowMaximizeButtonHint:激活最大化和关闭按钮,禁止最小化按钮。
Qt.WindowMinimizeButtonHint:激活最小化和关闭按钮,禁止最大化按钮。
Qt.WindowMinMaxButtonsHint:激活最小化,最大化和关闭按钮。
Qt.WindowCloseButtonHint:添加一个关闭按钮。
Qt.WindowContextHelpButtonHint:添加问号和关闭按钮,同对话框。
Qt.WindowStaysOnTopHint:窗口始终处于顶层位置(QQ登录界面)。
Qt.WindowStaysOnBottomHint:窗口始终处于底层位置。
注意:窗口是没有父控件的控件,即顶层控件。控件一般指非窗口控件。
调整整个应用程序窗口外观。
创建一个窗口,要求:无边框无标题栏,窗口半透明,自定义最小化、最大化、关闭按钮,支持拖拽用户区移动。
无边框设置
方法一:调用方法
window.setWindowFlags(Qt.FramelessWindowHint)
方法二:创建控件时就进行设置
window = QWidget(flags=Qt.FramelessWindowHint)
自定义最小化、最大化、关闭按钮发挥作用:
这里选择方法一,因为监听信号是对事件的高级封装,更贴近于开发人员。
最大化的问题:
最大化按钮应该和还原互相转换。——写槽函数判断当前状态。
# 0.导入包和模块
from PyQt5.Qt import *
import sys
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = QWidget()
# flags=Qt.FramelessWindowHint
window.setWindowFlags(Qt.FramelessWindowHint)
window.setWindowOpacity(0.5)
# 添加三个子控件 - 窗口右上角
# 2.2 设置控件
window.setWindowTitle("顶层窗口案例")
window.resize(500, 500)
top_margin = 5
# 添加三个子控件 - 窗口右上角
close_btn = QPushButton(window)
close_btn.setText("关闭")
close_btn_w = close_btn.width()
window_w = window.width()
close_btn_x = window_w - close_btn_w
close_btn_y = top_margin
close_btn.move(close_btn_x, close_btn_y)
max_btn = QPushButton(window)
max_btn.setText("最大化")
max_btn_w = max_btn.width()
max_btn_x = close_btn_x - max_btn_w
max_btn_y = top_margin
max_btn.move(max_btn_x, max_btn_y)
min_btn = QPushButton(window)
min_btn.setText("最小化")
min_btn_w = min_btn.width()
min_btn_x = max_btn_x - min_btn_w
min_btn_y = top_margin
min_btn.move(min_btn_x, min_btn_y)
def max_normal():
if window.isMaximized():
window.showNormal()
max_btn.setText("最大化")
else:
window.showMaximized()
max_btn.setText("恢复")
close_btn.pressed.connect(window.close)
max_btn.pressed.connect(max_normal)
min_btn.pressed.connect(window.showMinimized)
# 2.3 展示控件
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
代码太乱:封装重构(封装为类)
PS:为什么按钮之间有宽度?因为自动调整的宽度和获取的宽度可能不等。若想没有间隙,需要自设宽度。
# 0.导入包和模块
from PyQt5.Qt import *
import sys
class Window(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setWindowFlags(Qt.FramelessWindowHint)
self.setWindowOpacity(1)
# 2.2 设置控件
self.setWindowTitle("顶层窗口案例")
self.resize(500, 500)
self.setup_ui()
# 添加子控件
def setup_ui(self):
# 公共数据
top_margin = 5
btn_w = 80
btn_h = 40
# 添加三个子控件 - 窗口右上角
close_btn = QPushButton(self)
close_btn.setText("关闭")
close_btn.resize(btn_w, btn_h)
close_btn_w = btn_w
window_w = self.width()
close_btn_x = window_w - close_btn_w
close_btn_y = top_margin
close_btn.move(close_btn_x, close_btn_y)
max_btn = QPushButton(self)
max_btn.setText("最大化")
max_btn.resize(btn_w, btn_h)
max_btn_w = btn_w
max_btn_x = close_btn_x - max_btn_w
max_btn_y = top_margin
max_btn.move(max_btn_x, max_btn_y)
min_btn = QPushButton(self)
min_btn.setText("最小化")
min_btn.resize(btn_w, btn_h)
min_btn_w = btn_w
min_btn_x = max_btn_x - min_btn_w
min_btn_y = top_margin
min_btn.move(min_btn_x, min_btn_y)
def max_normal():
if self.isMaximized():
self.showNormal()
max_btn.setText("最大化")
else:
self.showMaximized()
max_btn.setText("恢复")
max_btn.pressed.connect(max_normal)
min_btn.pressed.connect(self.showMinimized)
close_btn.pressed.connect(self.close)
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = Window()
# 2.3 展示控件
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
close_btn.pressed.connect(self.close)一句有警告,不知道为什么??放外面是没错的啊
报错:Expected type ‘Union[(…) -> None, pyqtBoundSignal]’, got ‘() -> bool’ instead
最大化以后标题位置不对。——监听窗口尺寸的变化。
# 0.导入包和模块
from PyQt5.Qt import *
import sys
class Window(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setWindowFlags(Qt.FramelessWindowHint)
self.setWindowOpacity(1)
# 公共数据保存为属性
self.top_margin = 5
self.btn_w = 80
self.btn_h = 40
# 2.2 设置控件
self.setWindowTitle("顶层窗口案例")
self.resize(500, 500)
self.setup_ui()
# 添加子控件
def setup_ui(self):
# 添加三个子控件 - 窗口右上角
close_btn = QPushButton(self)
self.close_btn = close_btn
close_btn.setText("关闭")
close_btn.resize(self.btn_w, self.btn_h)
max_btn = QPushButton(self)
self.max_btn = max_btn
max_btn.setText("最大化")
max_btn.resize(self.btn_w, self.btn_h)
min_btn = QPushButton(self)
self.min_btn = min_btn
min_btn.setText("最小化")
min_btn.resize(self.btn_w, self.btn_h)
def max_normal():
if self.isMaximized():
self.showNormal()
max_btn.setText("最大化")
else:
self.showMaximized()
max_btn.setText("恢复")
max_btn.pressed.connect(max_normal)
min_btn.pressed.connect(self.showMinimized)
close_btn.pressed.connect(self.close)
def resizeEvent(self, QResizeEvent):
close_btn_w = self.btn_w
window_w = self.width()
close_btn_x = window_w - close_btn_w
close_btn_y = self.top_margin
self.close_btn.move(close_btn_x, close_btn_y)
max_btn_w = self.btn_w
max_btn_x = close_btn_x - max_btn_w
max_btn_y = self.top_margin
self.max_btn.move(max_btn_x, max_btn_y)
min_btn_w = self.btn_w
min_btn_x = max_btn_x - min_btn_w
min_btn_y = self.top_margin
self.min_btn.move(min_btn_x, min_btn_y)
# 1.创建应用程序对象
app = QApplication(sys.argv)
# 2.1 创建控件
window = Window()
# 2.3 展示控件
window.show()
# 3.进入消息循环
sys.exit(app.exec_())
跨方法引用:将变量定义为self属性
拖拽用户区移动
def mousePressEvent(self, evt):
# 记录初始两个坐标点
if evt.button() == Qt.LeftButton:
self.move_flag = True
self.window_x = self.x()
self.window_y = self.y()
self.mouse_x = evt.globalX()
self.mouse_y = evt.globalY()
def mouseMoveEvent(self, evt):
# 计算移动向量
if self.move_flag:
current_x = evt.globalX()
current_y = evt.globalY()
window_cx = current_x - self.mouse_x + self.window_x
window_cy = current_y - self.mouse_y + self.window_y
self.move(window_cx, window_cy)
def mouseReleaseEvent(self, evt):
self.move_flag = False
注意:要判定是否需要移动,先按下再移动,释放时标记重置。
if evt.button() == Qt.LeftButton:
这句加不加括号的错了好几次。
比如:在没输入账号或密码之前登录按钮不能被点击。
btn = QPushButton(window)
btn.setText("按钮")
btn.pressed.connect(lambda: print("按钮被点击"))
print(btn.isEnabled())
btn.setEnabled(False)
结果:按钮是灰色了。
setVisible(bool):设置控件是否可见,传递的参数值为True也不一定可见。
马甲(本质还是在调用setVisible):
为了监听绘制事件是否执行,先重写函数:
class Window(QWidget):
def paintEvent(self, evt):
super().paintEvent(evt)
print("窗口被绘制了")
注释掉show以后就不会打印窗口被绘制了,同理也不显示窗口,若调用setVisibleha函数,上述两个行为又会被恢复。
# window.show()
window.setVisible(True)
绘制时是先绘制父控件,再绘制子控件的。如果父控件隐藏,子控件不可能画出来。
class Window(QWidget):
def paintEvent(self, evt):
super().paintEvent(evt)
print("窗口被绘制了")
class Btn(QPushButton):
def paintEvent(self, evt):
super().paintEvent(evt)
print("按钮被绘制了")
如果要绘制窗口但隐藏按钮:
btn.hide()
window.setHidden(False)
注意:hide()和show()都不用传bool参数。
隐藏不是擦除(delete),而是重新绘制了一遍,比如通过点击来隐藏按钮:
btn.pressed.connect(lambda: btn.setVisible(False))
首先不停地重新绘制窗口和按钮,点击了按钮后,只绘制了一次窗口然后停止。
注意:visible代表控件最终的状态, 相对于用户的肉眼(被其他控件遮挡也属于可见)。
hide可理解为相对于父控件是否可见,参数是True还是False。隐藏的一定是不可见的,反之不然。
例如:
print(btn.isHidden())
print(btn.isVisible())
1、若先显示window顺便显示btn。False,True。
2、若不显示window,从而btn也无法显示。False,False。
print(btn.isVisibleTo(window))
True.
也就是如果父控件显示时,子控件能否跟着被显示。
btn.setVisible(False)
print(btn.isVisibleTo(window))
False.
window.setWindowTitle("交互状态[*]")
window.resize(500, 500)
window.setWindowModified(True)
print(window.isWindowModified())
PS:设置了窗口标题后,*会在修改时自动出现,[]不会显示。[*]可以放后面也可以放前面,但符号只能是*不能是其他。
应用:记事本编辑后左上角就会出现这个,并且在关闭时提示是否要保存。
isActiveWindow():与用户正在交互的那个窗口返回值为True,与画面上下层无关。
close():默认也是起隐藏控件的作用,并不会释放。
btn = Btn(window)
btn.setText("按钮")
btn.close()
btn.destroyed.connect(lambda: print("按钮被释放了"))
输出:空白。
setAttribute(Qt.WA_DeleteOnClose, True):但如果修改属性,关闭时删除,就会被释放,但隐藏时仍然不会释放。
btn.setAttribute(Qt.WA_DeleteOnClose, True)
btn.close()
btn.destroyed.connect(lambda: print("按钮被释放了"))
输出:“按钮被释放了”
合适的时候,设置不用的状态来控制交互逻辑。
创建一个窗口,包含一个文本框、一个按钮、一个标签。要求:默认状态下,标签隐藏,文本框和按钮显示,按钮设置为不可用状态;当文本框有内容时,让按钮可用,否则不可用==(比如没有输入账号时不允许点击登录)==;当文本框内容为Sz时,点击按钮则显示标签,并展示内容为登录成功,否则显示为失败。
涉及知识点:
文本框的创建,QLineEdit类;文本框监测内容变更,textChanged信号;文本框内容的获取,text()方法;按钮状态的设置。
textchanged的定义参数有个p_str,用来传递修改后的字符串。
def textChanged(self, p_str): # real signature unknown; restored from __doc__
""" textChanged(self, str) [signal] """
pass
所以,文本框有无内容可以通过字符串长度来判断。
# 0.导入包和模块
from PyQt5.Qt import *
# 继承类、定义自己的方法
class Window(QWidget):
# 初始化
def __init__(self):
super(Window, self).__init__()
self.setWindowTitle("交互状态案例")
self.resize(500, 500)
self.setup_ui()
# 存放所有子控件以及子控件的配置操作
def setup_ui(self):
label = QLabel(self)
label.setText("标签")
label.move(100, 50)
label.hide()
le = QLineEdit(self)
# le.setText("文本框默认输入")
le.move(100, 100)
def text_cao(text):
# if len(text) > 0:
# btn.setEnabled(True)
# else:
# btn.setEnabled(False)
btn.setEnabled(len(text))
le.textChanged.connect(text_cao)
btn = QPushButton(self)
btn.setText("登录")
btn.move(100, 150)
btn.setEnabled(False)
def check():
if le.text() == "Sz":
label.setText("登陆成功")
else:
label.setText("登陆失败")
label.adjustSize()
label.show()
btn.pressed.connect(check)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
注意:btn.setEnabled(len(text)),setEnabled的参数可以传给text。还有 label.adjustSize() 如果不设置,只会保持原始label的宽度导致新的内容无法全面显示。
效果:鼠标停在控件身上时,展示在状态栏的一段文本(状态栏:主窗口的最下方的横条,显示用户的操作状态)。
window = QMainWindow()
window.setWindowTitle("信息提示")
window.resize(500, 500)
window.statusBar()
window.setStatusTip("这是窗口")
print(window.statusTip())
# 打印状态提示的内容
PS:QMainWindow()组合窗口,其中一些控件是懒加载(用到的时候才创建)。
也可以对子控件设置,谁调用了这个方法,谁就显示在状态栏。
label.setStatusTip("这是标签")
toolTip()
setToolTip(str)
显示时长控制:
toolTipDuration()
setToolTipDuration(msec),单位是毫秒。
效果:鼠标悬停在控件上一会后, 展示在旁边。
label = QLabel(window)
label.setText("标签")
label.setStatusTip("这是标签")
label.setToolTip("这就是一个提示标签")
label.setToolTipDuration(2000)
print("提示内容是:", label.toolTip())
print("提示时长是:", label.toolTipDuration())
效果:切换到"查看这是啥"模式,点击该控件时显示。
# 设置whatsthis模式
window.setWindowFlags(Qt.WindowContextHelpButtonHint)
label.setWhatsThis("这是啥?不就是个标签签吗")
print(label.whatsThis())
先点问号触发”这是啥“模式,再去点击label标签可以显示提示。
setFocus():指定控件获取焦点。
setFocusPolicy(Policy):设置焦点获取策略。
Policy:
clearFocus():取消焦点。
le1 = QLineEdit(window)
le1.move(50, 50)
le2 = QLineEdit(window)
le2.move(50, 100)
le3 = QLineEdit(window)
le3.move(50, 150)
默认是第一个控件获得焦点:
# le2默认获取焦点
le2.setFocus()
# 获取焦点的方式只能是点击(le1,le3仍正常)
le2.setFocusPolicy(Qt.ClickFocus)
# 获取焦点的方式只能是Tab
le2.setFocusPolicy(Qt.TabFocus)
# 获取焦点两种方式(默认)
le2.setFocusPolicy(Qt.StrongFocus)
# 不用上述两种方式获取焦点
le2.setFocusPolicy(Qt.NoFocus)
# 默认不用le2来设置焦点,对le1没用
le2.clearFocus()
如果直接输入:
# 获取当前窗口内部,所有子控件中获取焦点的那个控件
print(window.focusWidget())
会输出None。
这是因为焦点的获取是在执行过程中实现的,不是展示窗口时实现的,所以应该在点击窗口时再查找哪个控件获取焦点。
class Window(QWidget):
def mousePressEvent(self, evt):
print(self.focusWidget())
class Window(QWidget):
def mousePressEvent(self, evt):
print("当前焦点:", self.focusWidget())
self.focusNextChild()
self.focusPreviousChild()
self.focusNextPrevChild(True)
self.focusNextPrevChild(False)
# 1-3-2
QWidget.setTabOrder(le1, le3)
QWidget.setTabOrder(le3, le2)
le2完了会自动切回le1。
结合程序的业务逻辑,来调整焦点的操作。
比如:输入用户名和密码时焦点在不同的文本框,可用鼠标点击或Tab来切换,选中后周围会有蓝色光环,只有获取焦点的控件才能与用户交互。
setAttribute(Qt :: WidgetAttribute 属性,bool on = true)
QWidget :: mask()const
返回当前在小部件上设置的遮罩。如果没有设置掩码,返回值将是一个空白区域。
另请参阅setMask(),clearMask(),QRegion :: isEmpty()和形状时钟示例。
setAcceptDrops()
acceptDrops()
scroll(int dx,int dy)
repaint()
addAction(QAction * action)
removeAction(),insertAction(),actions()
keyboardGrabber()
mouseGrabber()
grabMouse()
nextInFocusChain()
previousInFocusChain()
releaseKeyboard()
setShortcutAutoRepeat(int id,bool enable = true)
setShortcutEnabled(int id,bool enable = true)
grabShortcut()和releaseShortcut()
mapFrom(const QWidget * parent,const QPoint& pos)
mapFromGlobal(const QPoint& pos)const
将全局屏幕坐标pos转换为小部件坐标。
将小部件坐标pos转换为父项的坐标系。在父母不能为0,且必须调用控件的父。
又见mapFrom(),mapToParent(),mapToGlobal(),和underMouse()。
QPoint QWidget :: mapToGlobal(const QPoint& pos)const
布局控件,摆放的更加好看方便。
创建一个窗口, 包含两个标签。要求:两个标签垂直摆放。
作用:
在实际的应用中,经常需要对某个控件的颜色外观,如背景、前景色等,进行设置。Qt中提供的调色板QPalette类就是专门用于管理控件的外观显示。
QPalette类相当于对话框或控件的调色板,管理着控件和窗体的所有颜色。
每个窗体和控件都包含一个QPalette对象,在显示时,对其做相应的设置即可。
设置颜色的方法:
注:
ColorGroup:
ColorRole:
设置控件样式,使得控件更加好看。
windowTitleChanged(QString):窗口标题改变信号;
windowIconChanged(QIcon):窗口图标改变信号;
customContextMenuRequested(QPoint):自定义上下文菜单请求信号。
参考博客: