您的位置:首页 > 编程语言 > Qt开发

PyQt5 - QWidgets部件进阶教程之塑形时钟

2016-03-12 22:48 363 查看
废话

定义ShapedClock类
最终代码

最终效果

视窗模板注意事项

问题说明

废话

部件模板通过限制绘制的可用区域,来自定义顶层部件的形状。在一些视窗系统中,设置一些窗口标志可以使窗口装饰关闭(如标题栏、窗口矿建、按钮等),这样就能实现创建指定形状的视窗。该案例中,我们使用这一特性来创建一个模拟时钟的圆形视窗。

当然该案例的视窗不提供文件菜单或关闭按钮,我们提供一个包含退出口的环境菜单,这样就能使案例关闭,在窗口上点击鼠标右键就可以打开这个菜单。

定义ShapedClock类

ShapedClock类基于AnalogClock类案例,本例中不再重新展示相同代码说明,如有需要可参考链接。

class ShapedClock(QWidget):
def __init__(self):
super(ShapedClock, self).__init__()

self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowSystemMenuHint)

self.timer = QTimer(self)
self.timer.timeout.connect(self.update)
self.timer.start(1000)

self.quitAction = QAction('E&xit', self)
self.quitAction.setShortcut('Ctrl+Q')
self.quitAction.triggered.connect(qApp.quit)
self.addAction(self.quitAction)

self.setContextMenuPolicy(Qt.ActionsContextMenu)
self.setToolTip('Drag the clock with the left mouse button.\n '
'Use the right mouse button to open a context menu.')
self.setWindowTitle('Shaped Analog Clock')


paintEvent()的重写与AnalogClock类相同,实现sizeHint(),这样就不用明确的调整部件大小,同样为调整尺寸事件提供一个事件处理程序,如果时钟调整大小,这允许我们更新模板。

视窗不包含标题栏,我们重写mouseMoveEvent()和mousePressEvent(),这可以实现时钟在屏幕内拖动,dragPosition变量让我们保持追踪用户最后在部件上点击的位置。

设置一个timer,并连接到部件的update()槽

我们告知视窗管理器,部件不再用视窗框架装饰,这通过在部件上设置FramelessWindowHint标志实现。然后我们需要提供一个用户在屏幕内移动时钟的方法。鼠标按钮事件交由mousePressEvent()操作。

def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.dragPosition = event.globalPos() - self.frameGeometry().topLeft()
event.accept()

def mouseMoveEvent(self, event):
if event.buttons() and Qt.LeftButton:
self.move(event.globalPos() - self.dragPosition)
event.accept()


如果鼠标左键点击在部件上,我们记录‘部件框架的顶-左位置’和‘点鼠标点击时的点’之间的全局坐标位移。如果用户点住鼠标左键时移动,则会使用位移。当我们作用在事件上后,会通过accept()函数接受它的值。

如果鼠标在部件上移动,会调用mouseMoveEvent()处理程序。如果点住左键移动,部件的顶-左角则移动到一点,该点是从‘全局坐标中的当前光标位置’减去‘拖动位置’获得。如果我们拖动部件,同样接受事件。

def resizeEvent(self, event):
side = max(self.width(), self.height())
maskedRegion = QRegion(self.width() / 2 - side / 2, self.height() / 2 - side / 2, side, side, QRegion.Ellipse)
self.setMask(maskedRegion)

def sizeHint(self):
return QSize(100, 100)


当时在部件中心后绘制圆形钟面后,我们将这个作为模板原型。

缺少视窗框架可能导致用户在一些平台上难以调整部件大小,但这不一定是不可能的。当部件尺寸调整,resizeEvent()函数总是可以确保部件模板更新,另外可以保证当部件首次显示时可以正确设置。

最终我们为部件实现sizeHint(),这样当首次显示时塔克已给出一个合理的默认尺寸。

最终代码

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys

class ShapedClock(QWidget): def __init__(self): super(ShapedClock, self).__init__() self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowSystemMenuHint) self.timer = QTimer(self) self.timer.timeout.connect(self.update) self.timer.start(1000) self.quitAction = QAction('E&xit', self) self.quitAction.setShortcut('Ctrl+Q') self.quitAction.triggered.connect(qApp.quit) self.addAction(self.quitAction) self.setContextMenuPolicy(Qt.ActionsContextMenu) self.setToolTip('Drag the clock with the left mouse button.\n ' 'Use the right mouse button to open a context menu.') self.setWindowTitle('Shaped Analog Clock')

def mousePressEvent(self, event):
if event.button() =
4000
= Qt.LeftButton:
self.dragPosition = event.globalPos() - self.frameGeometry().topLeft()
event.accept()

def mouseMoveEvent(self, event):
if event.buttons() and Qt.LeftButton:
self.move(event.globalPos() - self.dragPosition)
event.accept()

def paintEvent(self, QPaintEvent):
hourHand = [
QPoint(7, 8),
QPoint(-7, 8),
QPoint(0, -40)
]
minuteHand = [
QPoint(7, 8),
QPoint(-7, 8),
QPoint(0, -70)
]

hourColor = QColor(127, 0, 127)
minuteColor = QColor(0, 127, 127, 191)

side = min(self.width(), self.height())
time = QTime.currentTime()

painter = QPainter()
painter.begin(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.translate(self.width() / 2, self.height() / 2)
painter.scale(side / 200.0, side / 200.0)

painter.setPen(Qt.NoPen)
painter.setBrush(hourColor)
painter.save()
painter.rotate(30.0 * (time.hour() + time.minute() / 60))
painter.drawConvexPolygon(QPolygon(hourHand))
painter.restore()

painter.setPen(hourColor)
for i in range(12):
painter.drawLine(88, 0, 96, 0)
painter.rotate(30.0)

painter.setPen(Qt.NoPen)
painter.setBrush(minuteColor)
painter.save()
painter.rotate(6.0 * (time.minute() + time.second() / 60))
painter.drawConvexPolygon(QPolygon(minuteHand))
painter.restore()

painter.setPen(minuteColor)
for i in range(60):
if i % 5 != 0:
painter.drawLine(92, 0, 96, 0)
painter.rotate(6.0)

painter.end()

def resizeEvent(self, event):
side = max(self.width(), self.height())
maskedRegion = QRegion(self.width() / 2 - side / 2, self.height() / 2 - side / 2, side, side, QRegion.Ellipse)
self.setMask(maskedRegion)

def sizeHint(self):
return QSize(100, 100)

app = QApplication(sys.argv)
clock = ShapedClock()
clock.show()
app.exec_()


最终效果



视窗模板注意事项

当QRegion允许创建任意符合区域后,部件模板可以创建成适配大部分异型视窗,甚至允许部件中间显示一个洞!部件模板同样可以使用位图的内容进行构造,来定义部件的不透明的部分。位图有一个alpha通道,可以通过QPixmap.mask()获取。

问题说明

画出来的时钟面我突然发现不圆,不知道是算法的问题,还是视觉差的问题,这里我作为一个Bug记录下来,后续会跟进处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  pyqt PyQt5 Python Qt