QT5.14.2 官方例子 - Widget Examples 1: Analog Clock(模拟时钟)

系列总链接:

QT5.14.2 官方例子 - 学习系列

https://blog.csdn.net/qq_22122811/article/details/108007519

 

模拟时钟窗口示例展示了如何绘制自定义窗口的内容

QT5.14.2 官方例子 - Widget Examples 1: Analog Clock(模拟时钟)_第1张图片

这个例子演示了如何使用QPainter的变换和缩放特性使绘图更加容易

AnalogClockWindow类提供了一个带有时针和分针的时钟,每隔几秒钟自动更新一次。我们使用了栅格窗口示例中的栅格窗口,并重新实现了渲染函数来绘制时钟面;

 

具体讲解在:

Analog Clock Window Example | Qt GUI 5.15.0

https://doc.qt.io/qt-5/qtgui-analogclock-example.html

 

难点1:绘画时钟和分钟,以及绘画刻度表盘的刻度;主要对其进行分析:

首先绘画时钟,分钟:

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的倍数,以避免在小时标记上面画分钟标记。

 

难点2:关于时间流动,界面刷新的问题:

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的顶级窗口的偏移量。

你可能感兴趣的:(QT,examples,qt)