系列总链接:
QT5.14.2 官方例子 - 学习系列
https://blog.csdn.net/qq_22122811/article/details/108007519
模拟时钟窗口示例展示了如何绘制自定义窗口的内容
这个例子演示了如何使用QPainter的变换和缩放特性使绘图更加容易
AnalogClockWindow类提供了一个带有时针和分针的时钟,每隔几秒钟自动更新一次。我们使用了栅格窗口示例中的栅格窗口,并重新实现了渲染函数来绘制时钟面;
具体讲解在:
Analog Clock Window Example | Qt GUI 5.15.0
https://doc.qt.io/qt-5/qtgui-analogclock-example.html
首先绘画时钟,分钟:
void AnalogClockWindow::render(QPainter *p)
{
static const QPoint hourHand[3] = {
QPoint(7, 8),
QPoint(-7, 8),
QPoint(0, -40)
};
static const QPoint minuteHand[3] = {
QPoint(7, 8),
QPoint(-7, 8),
QPoint(0, -70)
};
QColor hourColor(127, 0, 127);
QColor minuteColor(0, 127, 127, 191);
在设置画家并绘制时钟之前,我们首先定义两个QPoints列表和两个QColors,它们将用于时针和分针。分针的颜色的alpha值为191,这意味着它的不透明率为75%。
p->setRenderHint(QPainter::Antialiasing);
我们调用QPainter::setRenderHint()与QPainter::Antialiasing打开反锯齿。这使得斜线的绘制更加平滑。
p->translate(width() / 2, height() / 2);
int side = qMin(width(), height());
p->scale(side / 200.0, side / 200.0);
第一步:平移将原点移动到窗口的中心;
弟二步:缩放操作确保以下绘图操作缩放到适合该窗口。为了使我们的代码更简单,我们将绘制一个固定大小的时钟面,它将被定位和缩放,以便它位于窗口的中心。
我们还确定了窗口最短边的长度,这样我们就可以把钟面装进窗口。
绘图人员负责在渲染期间进行的所有转换,并确保所有内容都被正确绘制。让绘图人员处理转换通常比执行手动计算更容易。
补充:
scale的作用: 改变QPainter的刻度长度,如果假设默认刻度为(1,1),这时修改刻度为(2,2), 则QPainter执行drawRect(0,0,100,100), 都是画100的正方行,则后者的实际边长就是前者的两倍;
参考:https://blog.csdn.net/liji_digital/article/details/52357224?utm_source=blogxgwz5
p->setPen(Qt::NoPen);
p->setBrush(hourColor);
我们将钢笔设置为Qt::NoPen,因为我们不想要任何轮廓,并且我们使用一个实心笔刷,颜色适合显示小时。笔刷用于填充多边形和其他几何形状。
QTime time = QTime::currentTime();
p->save();
p->rotate(30.0 * ((time.hour() + time.minute() / 60.0)));
p->drawConvexPolygon(hourHand, 3);
p->restore();
我们在旋转之前和之后保存和恢复变换矩阵,因为我们想放置分针而不需要考虑任何之前的旋转。
分析:rotate(int angle):绕着原点旋转指定角度;因为一个时钟刻度间隔为360/12=30°,所以当前小时需旋转角度为:30*(小时数+分钟数占用的小时数)
drawConvexPolygon(绘制组点,点个数): 绘制多边形;
分钟数占用小时数:即10分钟占用了10/60的小时数;
rotate函数介绍参考:
https://www.cnblogs.com/lifexy/p/9245913.html
p->setPen(hourColor);
for (int i = 0; i < 12; ++i)
{
p->drawLine(88, 0, 96, 0);
p->rotate(30.0);
}
我们在钟的边缘画上每小时的刻度。我们绘制每个标记,然后旋转坐标系统,以便画家为下一个标记做好准备。
p->setPen(Qt::NoPen);
p->setBrush(minuteColor);
p->save();
p->rotate(6.0 * (time.minute() + time.second() / 60.0));
p->drawConvexPolygon(minuteHand, 3);
p->restore();
分针的旋转方式与时针相似。
p->setPen(minuteColor);
for (int j = 0; j < 60; ++j)
{
if ((j % 5) != 0)
p->drawLine(92, 0, 96, 0);
p->rotate(6.0);
}
同样,我们在时钟边缘画上标记,但这次是用来表示分钟。我们跳过5的倍数,以避免在小时标记上面画分钟标记。
class AnalogClockWindow : public RasterWindow
{
public:
AnalogClockWindow();
protected:
void timerEvent(QTimerEvent *) override;
void render(QPainter *p) override;
private:
int m_timerId;
};
AnalogClockWindow::AnalogClockWindow()
{
setTitle("Analog Clock");
resize(200, 200);
m_timerId = startTimer(1000);
}
我们在窗口上设置了标题,并将大小调整为合理的大小。然后我们启动一个计时器,我们将使用它来每秒钟重新绘制时钟。
void AnalogClockWindow::timerEvent(QTimerEvent *event)
{
if (event->timerId() == m_timerId)
renderLater();
}
作为startTimer调用的结果,timerEvent函数每秒钟都会被调用一次。利用基类中的便利,我们计划重新绘制窗口。
void RasterWindow::renderLater()
{
requestUpdate();
}
renderLater为基类RasterWindow的函数,它调用了requestUpdate(),这个函数为QWindow的函数(槽),它触发了事件信号
QEvent::UpdateRequest
bool RasterWindow::event(QEvent *event)
{
if (event->type() == QEvent::UpdateRequest) {
renderNow();
return true;
}
return QWindow::event(event);
}
接着RasterWindow的事件捕捉器event就捕捉到了该事件,刷新绘画时钟刻度表renderNow();
void RasterWindow::renderNow()
{
if (!isExposed())
return;
QRect rect(0, 0, width(), height());
m_backingStore->beginPaint(rect);
QPaintDevice *device = m_backingStore->paintDevice();
QPainter painter(device);
painter.fillRect(0, 0, width(), height(), QGradient::NightFade);
render(&painter);
painter.end();
m_backingStore->endPaint();
m_backingStore->flush(rect);
}
renderNow函数可分为四步理解:
第一:确认绘画设备为 QBackingStore *m_backingStore;
第二:用黑色刷新该钟表的区域:
painter.fillRect(0, 0, width(), height(), QGradient::NightFade);
第三步:调用绘画函数开始绘画钟表;
render(&painter);
第四步:释放painter资源,将给定区域从指定窗口刷新到屏幕上。
painter.end();
m_backingStore->endPaint();
m_backingStore->flush(rect);
void QBackingStore::endPaint()
绘画结束。
您应该在使用paintDevice()绘制结束后调用这个函数。
void QBackingStore::flush(const QRegion ®ion, QWindow *window = nullptr, const QPoint &offset = QPoint())
将给定区域从指定窗口刷新到屏幕上。
该窗口必须是这个backingstore表示的顶级窗口,或者该窗口的非瞬态子窗口。传递nullptr回到使用backingstore的顶级窗口。
如果窗口是子窗口,则区域应该在子窗口坐标中,并且偏移量应该是子窗口相对于backingstore的顶级窗口的偏移量。