2020-1-30
最终运行效果如下所示
在本次教程里,为了方便我们掌握工程的整体架构,我们需要先了解类图
制作:类图制作,我们后面会经常用到类图
下面我们就进入今天的案例介绍。
打开工程文件,我们发现只有一个curvdemo1.cpp文件,但是这个文件要比上一个案例simpleplot大一点,一眼望去不太好掌握整体,所以下面先给出它的架构图
工程的顶层类是MainWin,它继承自QFrame,有一个private属性d_curves,一个public方法MainWin,两个protected方法paintEvent和drawEvent方法,一个private方法shiftDown。下面我们逐个来介绍这些属性与方法的实现。
首先是包含必要的头文件
#include <qwt_scale_map.h>
#include <qwt_plot_curve.h>
#include <qwt_symbol.h>
#include <qwt_math.h>
#include <qcolor.h>
#include <qpainter.h>
#include <qapplication.h>
#include <qframe.h>
然后是声明一些重要的全局变量
//
// Array Sizes
//
const int Size = 27;
//数据点个数
const int CurvCnt = 6;
//将来绘制在QFrame中的曲线条数
//
// Arrays holding the values
//
double xval[Size];
double yval[Size];
//用来存放曲线数据点
QwtScaleMap xMap;
QwtScaleMap yMap;
//用来标识曲线坐标轴范围
定义MainWin类
class MainWin : public QFrame
{
public:
MainWin();
protected:
virtual void paintEvent( QPaintEvent * );
void drawContents( QPainter *p );
private:
void shiftDown( QRect &rect, int offset ) const;
QwtPlotCurve d_curves[CurvCnt];
};
实现构造函数
MainWin::MainWin()
{
int i;
xMap.setScaleInterval( -0.5, 10.5 );
yMap.setScaleInterval( -1.1, 1.1 );
//xMap是QwtScaleMap类,关于这个类,官方文档里是这么解释的:QwtScaleMap提供了从范围的坐标系统到绘图设备的线性坐标系统,反之亦然。
//说白了就是从相对坐标到绝对坐标的转换。举个例子:我在家门口说,我要往南走100m,天上的卫星看到了就会想到,这个人从某个纬度经度出发,移动了多少纬度经度。在这里,我们设置了一个范围,这个范围将来会被映射成绝对坐标绘制到QFrame中。
//
// Frame style
//
setFrameStyle( QFrame::Box | QFrame::Raised );
setLineWidth( 2 );
setMidLineWidth( 3 );
//设置框架属性,QFrame是是Qt中的一个框架类,它绘制框架并且调用一个虚函数drawContents()来填充这个框架。这个函数是被子类重新实现的。这里至少还有两个有用的函数:drawFrame()和frameChanged()。
//
// Calculate values
//
for( i = 0; i < Size; i++ )
{
xval[i] = double( i ) * 10.0 / double( Size - 1 );
yval[i] = qSin( xval[i] ) * qCos( 2.0 * xval[i] );
}
//生成数据点
//
// define curve styles
//
i = 0;
d_curves[i].setSymbol( new QwtSymbol( QwtSymbol::Cross, Qt::NoBrush,
QPen( Qt::black ), QSize( 5, 5 ) ) );
d_curves[i].setPen( Qt::darkGreen );
d_curves[i].setStyle( QwtPlotCurve::Lines );
d_curves[i].setCurveAttribute( QwtPlotCurve::Fitted );
i++;
d_curves[i].setSymbol( new QwtSymbol( QwtSymbol::Ellipse, Qt::yellow,
QPen( Qt::blue ), QSize( 5, 5 ) ) );
d_curves[i].setPen( Qt::red );
d_curves[i].setStyle( QwtPlotCurve::Sticks );
i++;
d_curves[i].setPen( Qt::darkBlue );
d_curves[i].setStyle( QwtPlotCurve::Lines );
i++;
d_curves[i].setPen( Qt::darkBlue );
d_curves[i].setStyle( QwtPlotCurve::Lines );
d_curves[i].setRenderHint( QwtPlotItem::RenderAntialiased );
i++;
d_curves[i].setPen( Qt::darkCyan );
d_curves[i].setStyle( QwtPlotCurve::Steps );
i++;
d_curves[i].setSymbol( new QwtSymbol( QwtSymbol::XCross, Qt::NoBrush,
QPen( Qt::darkMagenta ), QSize( 5, 5 ) ) );
d_curves[i].setStyle( QwtPlotCurve::NoCurve );
i++;
//定义曲线
//
// attach data
//
for( i = 0; i < CurvCnt; i++ )
d_curves[i].setRawSamples( xval, yval, Size );
//为曲线设置数据
}
实现绘图事件函数
void MainWin::paintEvent( QPaintEvent *event )
{
QFrame::paintEvent( event );
//先调用父类的绘图事件
QPainter painter( this );
painter.setClipRect( contentsRect() );
//设置一支画笔用来绘制曲线
drawContents( &painter );
//绘制曲线
}
绘图事件函数是一个Qt中的虚函数,这个函数会自动被调用。
当发生以下情况时会产生绘制事件并调用paintEvent()函数:
1.在窗口部件第一次显示时,系统会自动产生一个绘图事件,从而强制绘制这个窗口部件。
2.当重新调整窗口部件的大小时,系统也会产生一个绘制事件。
3.当窗口部件被其他窗口部件遮挡,然后又再次显示出来的时候,就会对那些隐藏的区域产生一个绘制事件。
同时可以调用QWidget::update()或者QWidget::repaint()来强制产生一个绘制事件。二者的区别是:
repaint()函数会强制产生一个即时的重绘事件,而update()函数只是在Qt下一次处理事件时才调用一次绘制事件。
如果多次调用update(),Qt会把连续多次的绘制事件压缩成一个单一的绘制事件,这样可避免闪烁现象。
//
// REDRAW CONTENTS
//
void MainWin::drawContents( QPainter *painter )
{
int deltay, i;
QRect r = contentsRect();
//获取绘图矩形,关于这个QRect,在官方文档里是这么解释的:一个矩形通常是用一个左上角坐标和一个尺寸来表示。说白了就是,你可以通过这个对象在屏幕上唯一确定一个矩形
deltay = r.height() / CurvCnt - 1;
r.setHeight( deltay );
//重新设置矩形高度
//
// draw curves
//
for ( i = 0; i < CurvCnt; i++ )
{
xMap.setPaintInterval( r.left(), r.right() );
yMap.setPaintInterval( r.top(), r.bottom() );
//设置map(确定相对坐标)
painter->setRenderHint( QPainter::Antialiasing,
d_curves[i].testRenderHint( QwtPlotItem::RenderAntialiased ) );
//设置绘图工具渲染属性
d_curves[i].draw( painter, xMap, yMap, r );
//将曲线用绘图工具绘制在r矩形内,横纵坐标由xMap和yMap确定
shiftDown( r, deltay );
//将绘图矩形区域下移一个高度,准备绘制下一个曲线
}
//
// draw titles
//
r = contentsRect(); // reset r
painter->setFont( QFont( "Helvetica", 8 ) );
const int alignment = Qt::AlignTop | Qt::AlignHCenter;
painter->setPen( Qt::black );
painter->drawText( 0, r.top(), r.width(), painter->fontMetrics().height(),
alignment, "Style: Line/Fitted, Symbol: Cross" );
shiftDown( r, deltay );
painter->drawText( 0, r.top(), r.width(), painter->fontMetrics().height(),
alignment, "Style: Sticks, Symbol: Ellipse" );
shiftDown( r, deltay );
painter->drawText( 0 , r.top(), r.width(), painter->fontMetrics().height(),
alignment, "Style: Lines, Symbol: None" );
shiftDown( r, deltay );
painter->drawText( 0 , r.top(), r.width(), painter->fontMetrics().height(),
alignment, "Style: Lines, Symbol: None, Antialiased" );
shiftDown( r, deltay );
painter->drawText( 0, r.top(), r.width(), painter->fontMetrics().height(),
alignment, "Style: Steps, Symbol: None" );
shiftDown( r, deltay );
painter->drawText( 0, r.top(), r.width(), painter->fontMetrics().height(),
alignment, "Style: NoCurve, Symbol: XCross" );
//绘制一些坐标信息
}
实现矩形下移成员函数shiftDown
void MainWin::shiftDown( QRect &rect, int offset ) const
{
rect.translate( 0, offset );
}
要点总结
- 了解QFrame类
- 了解QwtScaleMap的概念与基本用法
- 理解paintEvent事件在绘图中的作用机制
- 学会通过setRawSamples方法来为曲线设置数据点