Pyqt学习——day1
作者:互联网
布局
QVBoxLayout、QHBoxLayout、QFormLayout、QGridLayout
四种,比较简单。对于比较简单的应用(不维护的)用Qt Designer可以很快完成设计。但是如果是需要维护的,我建议自己手写。
注意:组件加入到某布局,会自动变更父节点
paintEvent
当要自定义组件时,可以继承QWidget,然后重写PainEvent。
# clock.py
from PyQt5.QtCore import QPoint, QSize, Qt, QTime, QTimer
from PyQt5.QtGui import QColor, QPainter, QPolygon, QRegion
from PyQt5.QtWidgets import QAction, QApplication, QWidget
from pyqt5_plugins.examplebuttonplugin import QtGui
class MyClock(QWidget):
minuteColor = QColor(0, 127, 127, 191)
def __init__(self, parent=None, R=200):
super().__init__(parent)
timer = QTimer()
timer.timeout.connect(self.paintEvent)
timer.start(1000)
self.resize(R * 2, R * 2)
self.dragPosition = None
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# 逻辑窗口
W, H = self.width(), self.height()
miniSize = min(H, W)
painter.setViewport((W - miniSize) // 2, (H - miniSize) // 2, miniSize, miniSize)
painter.setWindow(-100, -100, 200, 200)
# 画刻度
painter.save()
pen = painter.pen()
pen.setWidth(2)
pen.setColor(MyClock.minuteColor)
painter.setPen(pen)
for i in range(12):
painter.drawLine(0, -82 if i % 3 == 0 else -90, 0, -95)
painter.rotate(30)
# 画指针
painter.restore()
painter.save()
time = QTime.currentTime()
hour, minute, second = time.hour(), time.minute(), time.second()
# 时针
pen = painter.pen()
pen.setWidth(1)
brush = painter.brush()
brush.setColor(Qt.black)
brush.setStyle(Qt.SolidPattern)
painter.setBrush(brush)
painter.rotate(int((minute / 60 + hour % 12) * 30))
painter.drawPolygon(QPolygon([QPoint(3, 4), QPoint(-3, 4), QPoint(0, -35)]))
# 分针
painter.restore()
pen.setWidth(1)
painter.rotate(minute * 6 + second // 10)
painter.drawPolygon(QPolygon([QPoint(1, 8), QPoint(-1, 8), QPoint(0, -88)]))
maskedRegion = QRegion((self.width() - miniSize) // 2, (self.height() - miniSize) // 2, miniSize, miniSize,
QRegion.Ellipse)
self.setMask(maskedRegion)
def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None:
if event.buttons() == Qt.LeftButton:
self.move(event.globalPos() - self.dragPosition)
event.accept()
def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
if event.button() == Qt.LeftButton:
self.dragPosition = event.globalPos() - self.frameGeometry().topLeft()
event.accept()
if __name__ == '__main__':
app = QApplication([])
c = MyClock()
c.show()
app.exec_()
# painter.py
from PyQt5.QtCore import QPoint, QRect, QSize, Qt
from PyQt5.QtGui import QImage, QPainter, QPen, qRgb, QMouseEvent
from PyQt5.QtWidgets import QApplication, QWidget
from pyqt5_plugins.examplebuttonplugin import QtGui
"""
1. paintEvent 一次性渲染画面
2. 如果需要做局部更新,可以采用用QImage存储画面,再由paintEven渲染的作法
"""
class Drawer(QWidget):
def __init__(self, parent=None):
super(Drawer, self).__init__(parent)
self.resize(640, 480)
self.screen = QImage(self.width(), self.height(), QImage.Format_RGB888)
self.screen.fill(Qt.black)
self.lastPos = None
self.drawing = False
def paintEvent(self, event: QtGui.QPaintEvent) -> None:
painter = QPainter(self)
painter.drawImage(event.rect(), self.screen, event.rect())
def mousePressEvent(self, e: QMouseEvent) -> None:
if e.button() == Qt.LeftButton:
self.drawing = True
self.lastPos = e.pos()
self.drawLineTo(e.pos())
def mouseMoveEvent(self, e: QtGui.QMouseEvent) -> None:
if self.drawing:
self.drawLineTo(e.pos())
def mouseReleaseEvent(self, e: QtGui.QMouseEvent) -> None:
if e.button() == Qt.LeftButton:
self.drawing = False
self.drawLineTo(e.pos())
def drawLineTo(self, newPos):
painter = QPainter(self.screen)
painter.setRenderHint(QPainter.Antialiasing)
pen = painter.pen()
pen.setColor(Qt.white)
painter.setPen(pen)
painter.drawLine(self.lastPos, newPos)
radius = pen.width() // 2 + 2
self.update(QRect(self.lastPos, newPos).normalized().adjusted(-radius, -radius, +radius, +radius))
self.lastPos = newPos
def resizeEvent(self, e: QtGui.QResizeEvent) -> None:
if self.width() > self.screen.width() or self.height() > self.screen.height():
newH = max(self.height() + 128, self.screen.height())
newW = max(self.width() + 128, self.screen.width())
if self.screen.height() == newH and self.screen.width() == newW:
return
newScreen = QImage(newW, newH, QImage.Format_RGB888)
newScreen.fill(Qt.black)
painter = QPainter(newScreen)
painter.drawImage(QPoint(0, 0), self.screen)
self.screen = newScreen
if __name__ == '__main__':
app = QApplication([])
w = Drawer()
w.show()
app.exec_()
两个案例都是重写了paintEvent来实现绘制画面的。而不同的是clock.py在绘制时需要的只是当前的时间;而painter.py则不同,需要有先前的画面信息,所以借助了QImage来存储,各种划线操作也是直接对QImage进行,而paintEvent只是QImage中的内容贴上去。
多线程
将耗时比较长的处理都扔到子线程,否者在运行期间会使得界面无响应。
QMutex是一个互斥变量,当只能单个线程访问时,可以使用;
而QMutexLocker则时对QMutex做了一层封装:开始运行某个函数,在创建QMutexLocker时,申请构造函数传入的QMutex;而该函数运行结束,则将局部变量QMutexLocker销毁,在QMutexLocker的析构函数里释放QMutex,使用简单,但会减低并发性。
QSemaphore时QMutex的升级版, 可以完成大部分操作;
创建方式:继承QThread + 重写run、继承Object + moveToThread(还没有研究差别)
同步方式:QMutex、QMutexLocker、QSemaphore、QWaitCondition
退出:可以使用self.thread.quit() self.thread.wait()
来等待子线程退出后继续执行
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout
from PyQt5.QtCore import QThread, pyqtSignal, QObject
class Worker(QObject):
resultReady = pyqtSignal(int)
def __init__(self):
super(Worker, self).__init__()
print("初始化时(子)", QThread.currentThread())
self.res = 0
def doWork(self):
QThread.sleep(3)
self.res += 1
print(f"生产了: {self.res}")
self.resultReady.emit(self.res)
print(f"运行时(子): {QThread.currentThread()}")
def do_printChild(self):
print(f"运行时(子): {QThread.currentThread()}")
class Controller(QWidget):
parentCall = pyqtSignal(bool)
def __init__(self):
super(Controller, self).__init__()
self.__initUI()
self.__initThread()
self.__connectSignalAndSlots()
def __initUI(self):
# 界面
vbox = QVBoxLayout()
self.setLayout(vbox)
self.btnRun = QPushButton("run")
self.btnPrintMain = QPushButton("printMain")
self.btnPrintChild = QPushButton("printChild")
self.btnCloseChild = QPushButton("closeChild")
self.btnCallDirect = QPushButton("callDirect")
vbox.addWidget(self.btnRun)
vbox.addWidget(self.btnPrintMain)
vbox.addWidget(self.btnPrintChild)
vbox.addWidget(self.btnCloseChild)
vbox.addWidget(self.btnCallDirect)
def __initThread(self):
print("初始化时(主)", QThread.currentThread())
self.worker = Worker()
self.thread = QThread()
self.worker.moveToThread(self.thread)
self.thread.start()
def __connectSignalAndSlots(self):
self.btnRun.clicked.connect(self.worker.doWork)
self.btnPrintMain.clicked.connect(self.do_printMain)
self.btnPrintChild.clicked.connect(self.worker.do_printChild)
self.worker.resultReady.connect(self.doReady)
self.btnCloseChild.clicked.connect(self.do_closeChild)
self.btnCallDirect.clicked.connect(self.do_btnCallDirect)
def do_btnCallDirect(self):
print("直接调用")
self.worker.do_printChild()
def do_closeChild(self):
self.thread.quit()
self.thread.wait() # 阻塞主线程,直到停止
print("阻塞完了")
def doReady(self, val):
print(f"响应(主):{QThread.currentThread()}")
def do_printMain(self):
print(f"运行时(主):{QThread.currentThread()}")
if __name__ == '__main__':
app = QApplication([])
w = Controller()
w.show()
app.exec_()
ProducerAndConsumer.py
import random
from PyQt5.QtCore import QCoreApplication, QSemaphore, QThread, QObject, QMutex, pyqtSignal
TOTAL_NEED = 20 # 总共需要的数量
MAX_STOCK = 3 # 最大的存货量
curStock = [] # 当前存货物
canProduceSemaphore = QSemaphore(MAX_STOCK)
canConsumeSemaphore = QSemaphore(0)
mutex = QMutex()
# 继承QThread实现
class Producer(QThread):
def __init__(self):
super(Producer, self).__init__()
def run(self):
for i in range(TOTAL_NEED):
# 申请生产指标
canProduceSemaphore.acquire()
# 模拟生产
QThread.msleep(int(random.random() * 1000))
# 申请独占仓库后加入
mutex.lock()
curStock.append(i)
print(f"生产了: 包子-{i}", flush=True)
mutex.unlock()
# 释放一个消耗指标
canConsumeSemaphore.release()
# 继承QObject实现
class Consumer(QObject):
stopSing = pyqtSignal()
def __init__(self):
super(Consumer, self).__init__()
# 可以用其他名字
def work(self):
for i in range(TOTAL_NEED):
# 申请
canConsumeSemaphore.acquire()
# 申请独占
mutex.lock()
index = curStock.pop(0)
mutex.unlock()
# 可以继续生产
canProduceSemaphore.release()
# 模拟消耗
print(f"吃了: 包子-{index}", flush=True)
QThread.msleep(int(random.random())*1000 + 500)
self.stopSing.emit()
if __name__ == '__main__':
app = QCoreApplication([])
producer = Producer()
consumer = Consumer()
# 继承QObject用moveToThread实现
thread = QThread()
consumer.moveToThread(thread)
thread.started.connect(consumer.work)
consumer.stopSing.connect(thread.finished)
thread.start()
producer.start()
app.exec_()
当使用继承QObject+moveToThread的方法,移动到子线程后的对象,一旦通过信号量调用,就能在子线程中运行;而如果直接用函数调用,则会在使用该函数的线程(一般是主线程)执行。
标签:__,self,Pyqt,day1,学习,pen,def,QThread,painter 来源: https://blog.csdn.net/qq_43641752/article/details/121875329