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中的事件过滤器并能够灵活运用