QWT基础教程之refreshtest

2020-2-1

今天我们介绍的这个例子要比之前的几个复杂一些,如下是其效果图。

本次案例的文件不止一个,如下是其文件目录树

.
|
|-----Headers
|       |---cicularbuffer.h
|       |---mainwindow.h
|       |---panel.h
|       |---plot.h
|       |---setting.h
|
|-----Sources
|       |---cicularbuffer.cpp
|       |---main.cpp
|       |---mainwindow.cpp
|       |---panel.cpp
|       |---plot.cpp
|    

上面的目录树只包含了.c和.h文件。一个头文件对应一个类。下面我会逐个介绍每一个类的具体实现。

CircularBuffer类,其类图如下

这个类继承自QwtSeriesData,属性都是私有的,行为都是公有的,下面我们到circularbuffer.cpp文件中看这些属性与方法的实现。

构造函数CircularBuffer

CircularBuffer::CircularBuffer( double interval, size_t numPoints ):
    d_y( NULL ),
    d_referenceTime( 0.0 ),
    d_startIndex( 0 ),
    d_offset( 0.0 )
{
    fill( interval, numPoints );
}

主要做了一些初始化工作

fill实现

void CircularBuffer::fill( double interval, size_t numPoints )
{
    if ( interval <= 0.0 || numPoints < 2 )
        return;

    d_values.resize( numPoints );
    d_values.fill( 0.0 );

    if ( d_y )
    {
        d_step = interval / ( numPoints - 2 );
        for ( size_t i = 0; i < numPoints; i++ )
            d_values[i] = d_y( i * d_step );
    }

    d_interval = interval;
}

为d_values填充数据

setFunction实现

void CircularBuffer::setFunction( double( *y )( double ) )
{
    d_y = y;
}

设置数据生成函数

setReferenceTime

void CircularBuffer::setReferenceTime( double timeStamp )
{
    d_referenceTime = timeStamp;

    const double startTime = ::fmod( d_referenceTime, d_values.size() * d_step );

    d_startIndex = int( startTime / d_step ); // floor
    d_offset = ::fmod( startTime, d_step );
}

设置参考时间

referenceTime实现

double CircularBuffer::referenceTime() const
{
    return d_referenceTime;
}

获取参考时间

size实现

size_t CircularBuffer::size() const
{
    return d_values.size();
}

获取数据尺寸

sample实现

QPointF CircularBuffer::sample( size_t i ) const
{
    const int size = d_values.size();

    int index = d_startIndex + i;
    if ( index >= size )
        index -= size;

    const double x = i * d_step - d_offset - d_interval;
    const double y = d_values.data()[index];

    return QPointF( x, y );
}

获取样本数据

boundingRect实现

QRectF CircularBuffer::boundingRect() const
{
    return QRectF( -1.0, -d_interval, 2.0, d_interval );
}

获取数据矩形,如果对数据矩形这个概念不太理解,可以去官方文档里查一下QRectF这个类,或者上网上搜一下别人的理解。

Settings类,下面是它的类图

这个类主要是用来存储一些配置信息,代码如下所示

class Settings
{
public:
    enum FunctionType
    {
        NoFunction = -1,

        Wave,
        Noise
    };

    enum UpdateType
    {
        RepaintCanvas,
        Replot
    };

    Settings()
    {
        grid.pen = Qt::NoPen;
        grid.pen.setCosmetic( true );

        curve.brush = Qt::NoBrush;
        curve.numPoints = 1000;
        curve.functionType = Wave;
        curve.paintAttributes = 0;
        curve.renderHint = 0;
        curve.lineSplitting = true;

        canvas.useBackingStore = false;
        canvas.paintOnScreen = false;
        canvas.immediatePaint = true;
#ifndef QWT_NO_OPENGL
        canvas.openGL = false;
#endif

        updateType = RepaintCanvas;
        updateInterval = 20;
    }

    struct gridSettings
    {
        QPen pen;
    } grid;

    struct curveSettings
    {
        QPen pen;
        QBrush brush;
        uint numPoints;
        FunctionType functionType;
        int paintAttributes;
        int renderHint;
        bool lineSplitting;
    } curve;

    struct canvasSettings
    {
        bool useBackingStore;
        bool paintOnScreen;
        bool immediatePaint;

#ifndef QWT_NO_OPENGL
        bool openGL;
#endif
    } canvas;

    UpdateType updateType;
    int updateInterval;
};

这个类没啥好说的,不过这种方法可以学习一下:把配置信息封装到一个类中。

Panel类,它的类图就不画了,直接看类定义

class Panel: public QTabWidget
{
    Q_OBJECT

public:
    Panel( QWidget * = NULL );

    Settings settings() const;
    void setSettings( const Settings & );

Q_SIGNALS:
    void settingsChanged( const Settings & );

private Q_SLOTS:
    void edited();

private:
    QWidget *createPlotTab( QWidget * );
    QWidget *createCanvasTab( QWidget * );
    QWidget *createCurveTab( QWidget * );

    SpinBox *d_numPoints;
    SpinBox *d_updateInterval;
    QComboBox *d_updateType;

    QComboBox *d_gridStyle;
    CheckBox *d_paintCache;
    CheckBox *d_paintOnScreen;
    CheckBox *d_immediatePaint;
#ifndef QWT_NO_OPENGL
    CheckBox *d_openGL;
#endif

    QComboBox *d_curveType;
    CheckBox *d_curveAntialiasing;
    CheckBox *d_curveClipping;
    CheckBox *d_curveFiltering;
    CheckBox *d_lineSplitting;
    SpinBox  *d_curveWidth;
    QComboBox *d_curvePen;
    CheckBox *d_curveFilled;
};

一目了然,Panel继承自QTabWidget,下面我们去看看它的成员函数的实现

Panel的构造函数

Panel::Panel( QWidget *parent ):QTabWidget( parent )
{
    setTabPosition( QTabWidget::West );
    //这个函数是QTabWidget的成员函数,它用来控制Tab列表在什么位置

    addTab( createPlotTab( this ), "Plot" );
    addTab( createCanvasTab( this ), "Canvas" );
    addTab( createCurveTab( this ), "Curve" );
    //添加三个Tab

    setSettings( Settings() );
    //初始化每个面板上的每个控件属性

    connect( d_numPoints, SIGNAL( valueChanged( int ) ), SLOT( edited() ) );
    connect( d_updateInterval, SIGNAL( valueChanged( int ) ), SLOT( edited() ) );
    connect( d_curveWidth, SIGNAL( valueChanged( int ) ), SLOT( edited() ) );

    connect( d_paintCache, SIGNAL( stateChanged( int ) ), SLOT( edited() ) );
    connect( d_paintOnScreen, SIGNAL( stateChanged( int ) ), SLOT( edited() ) );
    connect( d_immediatePaint, SIGNAL( stateChanged( int ) ), SLOT( edited() ) );
#ifndef QWT_NO_OPENGL
    connect( d_openGL, SIGNAL( stateChanged( int ) ), SLOT( edited() ) );
#endif

    connect( d_curveAntialiasing, SIGNAL( stateChanged( int ) ), SLOT( edited() ) );
    connect( d_curveClipping, SIGNAL( stateChanged( int ) ), SLOT( edited() ) );
    connect( d_curveFiltering, SIGNAL( stateChanged( int ) ), SLOT( edited() ) );
    connect( d_lineSplitting, SIGNAL( stateChanged( int ) ), SLOT( edited() ) );
    connect( d_curveFilled, SIGNAL( stateChanged( int ) ), SLOT( edited() ) );

    connect( d_updateType, SIGNAL( currentIndexChanged( int ) ), SLOT( edited() ) );
    connect( d_gridStyle, SIGNAL( currentIndexChanged( int ) ), SLOT( edited() ) );
    connect( d_curveType, SIGNAL( currentIndexChanged( int ) ), SLOT( edited() ) );
    connect( d_curvePen, SIGNAL( currentIndexChanged( int ) ), SLOT( edited() ) );
    //信号槽连接
}

这里需要关注的是,把多个控件的信号绑定到了同一个槽函数edited上。

createPlotTab实现

QWidget *Panel::createPlotTab( QWidget *parent )
{
    QWidget *page = new QWidget( parent );

    d_updateInterval = new SpinBox( 0, 1000, 10, page );
    d_numPoints = new SpinBox( 10, 1000000, 1000, page );

    d_updateType = new QComboBox( page );
    d_updateType->addItem( "Repaint" );
    d_updateType->addItem( "Replot" );

    int row = 0;

    QGridLayout *layout = new QGridLayout( page );

    layout->addWidget( new QLabel( "Updates", page ), row, 0 );
    layout->addWidget( d_updateInterval, row, 1 );
    layout->addWidget( new QLabel( "ms", page ), row++, 2 );

    layout->addWidget( new QLabel( "Points", page ), row, 0 );
    layout->addWidget( d_numPoints, row++, 1 );

    layout->addWidget( new QLabel( "Update", page ), row, 0 );
    layout->addWidget( d_updateType, row++, 1 );

    layout->addLayout( new QHBoxLayout(), row++, 0 );

    layout->setColumnStretch( 1, 10 );
    layout->setRowStretch( row, 10 );

    return page;
}

这里要重点掌握的是SpinBox、QComboBox以及QGridLayout的使用

槽函数edited实现

void Panel::edited()
{
    const Settings s = settings();
    Q_EMIT settingsChanged( s );
}

当控件内容发生变化时,edited会被调用,先读取配置信息,然后发出一个信号settingChanged,供其它程序使用

setSettings函数实现

void Panel::setSettings( const Settings &s )
{
    d_numPoints->setValue( s.curve.numPoints );
    d_updateInterval->setValue( s.updateInterval );
    d_updateType->setCurrentIndex( s.updateType );

    switch( s.grid.pen.style() )
    {
        case Qt::NoPen:
        {
            d_gridStyle->setCurrentIndex( 0 );
            break;
        }
        case Qt::DashLine:
        {
            d_gridStyle->setCurrentIndex( 2 );
            break;
        }
        default:
        {
            d_gridStyle->setCurrentIndex( 1 ); // Solid
        }
    }

    d_paintCache->setChecked( s.canvas.useBackingStore );
    d_paintOnScreen->setChecked( s.canvas.paintOnScreen );
    d_immediatePaint->setChecked( s.canvas.immediatePaint );
#ifndef QWT_NO_OPENGL
    d_openGL->setChecked( s.canvas.openGL );
#endif

    d_curveType->setCurrentIndex( s.curve.functionType );
    d_curveAntialiasing->setChecked(
        s.curve.renderHint & QwtPlotCurve::RenderAntialiased );

    d_curveClipping->setChecked(
        s.curve.paintAttributes & QwtPlotCurve::ClipPolygons );
    d_curveFiltering->setChecked(
        s.curve.paintAttributes & QwtPlotCurve::FilterPoints );

    d_lineSplitting->setChecked( s.curve.lineSplitting );

    d_curveWidth->setValue( s.curve.pen.width() );
    d_curvePen->setCurrentIndex(
        s.curve.pen.style() == Qt::SolidLine ? 0 : 1 );
    d_curveFilled->setChecked( s.curve.brush.style() != Qt::NoBrush );
}

供其它函数调用。

Plot类

类定义如下

class Plot: public QwtPlot
{
    Q_OBJECT

public:
    Plot( QWidget* = NULL );

public Q_SLOTS:
    void setSettings( const Settings & );

protected:
    virtual void timerEvent( QTimerEvent *e );

private:
    void alignScales();

    QwtPlotGrid *d_grid;
    QwtPlotCurve *d_curve;

    QwtSystemClock d_clock;
    double d_interval;

    int d_timerId;

    Settings d_settings;
};

构造函数

Plot::Plot( QWidget *parent ):
    QwtPlot( parent ),
    d_interval( 10.0 ), // seconds
    d_timerId( -1 )
{
    // Assign a title
    setTitle( "Testing Refresh Rates" );

    QwtPlotCanvas *canvas = new QwtPlotCanvas();
    //新建画布
    canvas->setFrameStyle( QFrame::Box | QFrame::Plain );
    canvas->setLineWidth( 1 );
    canvas->setPalette( Qt::white );
    //设置画布属性

    setCanvas( canvas );

    alignScales();

    // Insert grid
    d_grid = new QwtPlotGrid();
    d_grid->attach( this );
    //插入网格

    // Insert curve
    d_curve = new QwtPlotCurve( "Data Moving Right" );
    d_curve->setPen( Qt::black );
    d_curve->setData( new CircularBuffer( d_interval, 10 ) );
    d_curve->attach( this );
    //插入曲线

    // Axis
    setAxisTitle( QwtPlot::xBottom, "Seconds" );
    setAxisScale( QwtPlot::xBottom, -d_interval, 0.0 );

    setAxisTitle( QwtPlot::yLeft, "Values" );
    setAxisScale( QwtPlot::yLeft, -1.0, 1.0 );
    //插入坐标轴

    d_clock.start();
    //时钟启动
    setSettings( d_settings );
}

setSettings函数实现

void Plot::setSettings( const Settings &s )
{
    if ( d_timerId >= 0 )
        killTimer( d_timerId );

    d_timerId = startTimer( s.updateInterval );

    d_grid->setPen( s.grid.pen );
    d_grid->setVisible( s.grid.pen.style() != Qt::NoPen );

    CircularBuffer *buffer = static_cast<CircularBuffer *>( d_curve->data() );
    if ( s.curve.numPoints != buffer->size() ||
            s.curve.functionType != d_settings.curve.functionType )
    {
        switch( s.curve.functionType )
        {
            case Settings::Wave:
                buffer->setFunction( wave );
                break;
            case Settings::Noise:
                buffer->setFunction( noise );
                break;
            default:
                buffer->setFunction( NULL );
        }

        buffer->fill( d_interval, s.curve.numPoints );
    }

    d_curve->setPen( s.curve.pen );
    d_curve->setBrush( s.curve.brush );

    d_curve->setPaintAttribute( QwtPlotCurve::ClipPolygons,
        s.curve.paintAttributes & QwtPlotCurve::ClipPolygons );
    d_curve->setPaintAttribute( QwtPlotCurve::FilterPoints,
        s.curve.paintAttributes & QwtPlotCurve::FilterPoints );

    d_curve->setRenderHint( QwtPlotItem::RenderAntialiased,
        s.curve.renderHint & QwtPlotItem::RenderAntialiased );

#ifndef QWT_NO_OPENGL
    if ( s.canvas.openGL )
    {
        QwtPlotGLCanvas *plotCanvas = qobject_cast<QwtPlotGLCanvas *>( canvas() );
        if ( plotCanvas == NULL )
        {
            plotCanvas = new GLCanvas();
            plotCanvas->setPalette( QColor( "khaki" ) );

            setCanvas( plotCanvas );
        }
    }
    else
#endif
    {
        QwtPlotCanvas *plotCanvas = qobject_cast<QwtPlotCanvas *>( canvas() );
        if ( plotCanvas == NULL )
        {
            plotCanvas = new QwtPlotCanvas();
            plotCanvas->setFrameStyle( QFrame::Box | QFrame::Plain );
            plotCanvas->setLineWidth( 1 );
            plotCanvas->setPalette( Qt::white );

            setCanvas( plotCanvas );
        }

        plotCanvas->setAttribute( Qt::WA_PaintOnScreen, s.canvas.paintOnScreen );

        plotCanvas->setPaintAttribute(
            QwtPlotCanvas::BackingStore, s.canvas.useBackingStore );
        plotCanvas->setPaintAttribute(
            QwtPlotCanvas::ImmediatePaint, s.canvas.immediatePaint );
    }

    QwtPainter::setPolylineSplitting( s.curve.lineSplitting );

    d_settings = s;
}

最后就是MainWindow类

声明如下

class MainWindow: public QMainWindow
{
    Q_OBJECT

public:
    MainWindow( QWidget *parent = NULL );
    virtual bool eventFilter( QObject *, QEvent * );

private Q_SLOTS:
    void applySettings( const Settings & );

private:
    Plot *d_plot;
    Panel *d_panel;
    QLabel *d_frameCount;
};

构造函数

MainWindow::MainWindow( QWidget *parent ):
    QMainWindow( parent )
{
    QWidget *w = new QWidget( this );

    d_panel = new Panel( w );

    d_plot = new Plot( w );

    QHBoxLayout *hLayout = new QHBoxLayout( w );
    hLayout->addWidget( d_panel );
    hLayout->addWidget( d_plot, 10 );

    setCentralWidget( w );

    d_frameCount = new QLabel( this );
    statusBar()->addWidget( d_frameCount, 10 );

    applySettings( d_panel->settings() );

    connect( d_panel, SIGNAL( settingsChanged( const Settings & ) ),
        this, SLOT( applySettings( const Settings & ) ) );
}

下面是剩下的两个成员函数

bool MainWindow::eventFilter( QObject *object, QEvent *event )
{
    if ( object == d_plot->canvas() && event->type() == QEvent::Paint )
    {
        static int counter;
        static QTime timeStamp;

        if ( !timeStamp.isValid() )
        {
            timeStamp.start();
            counter = 0;
        }
        else
        {
            counter++;

            const double elapsed = timeStamp.elapsed() / 1000.0;
            if ( elapsed >= 1 )
            {
                QString fps;
                fps.setNum( qRound( counter / elapsed ) );
                fps += " Fps";

                d_frameCount->setText( fps );

                counter = 0;
                timeStamp.start();
            }
        }
    }

    return QMainWindow::eventFilter( object, event );
}

void MainWindow::applySettings( const Settings &settings )
{
    d_plot->setSettings( settings );

    // the canvas might have been recreated
    d_plot->canvas()->removeEventFilter( this );
    d_plot->canvas()->installEventFilter( this );
}

重点关注一下事件过滤器

要点总结:

  • 学会封装组件,模块化编程,比如这个例子中,控制面板、数据、画板、配置信息就是一个一个模块。
  • 熟练掌握QT中之间的通信实现方法(公有成员、槽函数、信号)。
  • 知道如何将多个信号绑定到同一个槽函数上
  • 理解QT中的事件过滤器并能够灵活运用
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值