在这个例子中,我们介绍画一个pixmap来除去闪烁。我们也会加入一个力量控制。
- lcdrange.h包含LCDRange类定义
- lcdrange.cpp包含LCDRange类实现
- cannon.h包含CannonField类定义
- cannon.cpp包含CannonField类实现
- main.cpp包含MyWidget和main
一行一行地解说
cannon.h:
CannonField
现在除了角度又多了一个力量值。
int angle() const {return ang;}
int force() const {return f;}
public slots:
void setAngle(int degrees);
void setForce(int newton);
signals:
void angleChanged(int);
void forceChanged(int);
力量的接口的实现和角度一样。
private:
QRect cannonRect() const;
我们把加农炮封装的矩形的定义放到了一个单独的函数中。
int ang;
int f;
};
力量被存储到一个整数f中。
cannon.cpp:
#include <QPixmap>
我们包含了QPixmap
类定义。
CannonField::CannonField(QWidget *parent, Qt::WindowFlags name)
: QWidget(parent, name)
{
ang = 45;
f = 0;
setAutoFillBackground(true);
setPalette(QPalette(QColor(250, 250, 200)));
}
力量(f)被初始化为0。
void CannonField::setAngle(int degrees)
{
if (degrees < 5)
degrees = 5;
if (degrees > 70)
degrees = 70;
if (ang == degrees)
return;
ang = degrees;
repaint(cannonRect());
emit angleChanged(ang);
}
我们在setAngle()
函数中做了一个小的改变。它只重画窗口部件中含有加农炮的一小部分。
void CannonField::setForce(int newton)
{
if (newton < 0)
newton = 0;
if (f == newton)
return;
f = newton;
emit forceChanged(f);
}
setForce()
的实现和setAngle()
很相似。唯一的不同是因为我们不显示力量值,我们不需要重画窗口部件。
void CannonField::paintEvent(QPaintEvent *e)
{
if (!e->rect().intersects(cannonRect()))
return;
我们现在用只重画需要刷新得部分来优化绘画事件。首先确保只有当事件区域与炮台矩形 cannonRect()
相交时才进行绘制,避免不必要的重绘。
QRect cr = cannonRect(); //矩形更新
QPixmap pix(cr.size()); //位图双缓冲
然后,我们创建一个临时的pixmap
,我们用来不闪烁地画。所有的绘画操作都在这个pixmap
中完成,并且之后只用一步操作来把这个pixmap
画到屏幕上。
这是不闪烁绘画的本质:一次准确地在每一个像素上画。更少,你会得到绘画错误。更多,你会得到闪烁。在这个例子中这个并不重要——当代码被写时,仍然是很慢的机器导致闪烁,但以后不会再闪烁了。我们由于教育目的保留了这些代码。
pix.fill(Qt::transparent); //使用透明像素图
填充pixmap
QPainter p(&pix); //&pix,在pixmap上画
p.setBrush(Qt::blue);
p.setPen(Qt::NoPen);
p.translate(0, pix.height()-1); //将原点(0,0)变化为(0, pix.height()-1)
p.drawPie(QRect(-35, -35, 70, 70), 0, 90*16); //绘图,开始角度为0,弧长为90*16(四分之一圆)
p.rotate(-ang);
p.drawRect(QRect(33, -4, 15, 8));
p.end();
我们就像第九章中一样画,但是现在我们是在pixmap
上画。
在这一点上,我们有一个绘画工具变量和一个pixmap
看起来相当正确,但是我们还没有在屏幕上画呢。
p.begin(this); //在主窗口部件上画
p.drawPixmap(cr.topLeft(), pix); //把pixmap的一部分绘制到屏幕上。像素映射pix,原点就放在cr.topLeft()处。
所以我们在CannonField
上面打开绘图工具并在这之后画这个pixmap
。
这就是全部了。在顶部和底部各有一对线,并且这个代码是100%不闪烁的。
QRect CannonField::cannonRect() const
{
QRect r(0, 0, 50, 50);
r.moveBottomLeft(rect().bottomLeft());
return r;
}
这个函数返回一个在窗口部件坐标中封装加农炮的矩形。首先我们创建一个50*50大小的矩形,然后移动它,使它的左下角和窗口部件自己的左下角相等。 void QRect::moveBottomLeft ( const QPoint & p ) 设置矩形的左下坐标为p,而大小不被改变。
QWidget::rect()
函数在窗口部件自己的坐标(左上角是0,0)中返回窗口部件封装的矩形。
main.cpp:
MyWidget::MyWidget(QWidget *parent, Qt::WindowFlags name)
: QWidget(parent, name)
{
构造函数也是一样,但是已经加入了一些东西。
LCDRange *lcdForce = new LCDRange;
lcdForce->setRange(10, 50);
lcdForce->setValue(25);
我们加入了第二个LCDRange
,用来设置力量,并且初始化力量的值为25。
connect(lcdForce, SIGNAL(valueChanged(int)), cannonField, SLOT(setForce(int)));
connect(cannonField, SIGNAL(forceChanged(int)), lcdForce, SLOT(setValue(int)));
我们把lcdForce
窗口部件和cannonField
窗口部件连接起来,就和我们对angle
窗口部件做的一样。
QVBoxLayout *leftBox = new QVBoxLayout;
grid->addLayout(leftBox, 1, 0);
leftBox->addWidget(lcdRange);
leftBox->addWidget(lcdForce);
在第九章,我们把angle
放到了布局的左下单元。现在我们想在这个单元中放入两个窗口部件,所一个我们用了一个垂直的盒子,把这个垂直的盒子放到这个网格单元中,并且把angle
和force
放到这个垂直的盒子中。
行为
闪烁已经走了,并且我们还有一个力量控制。
练习
让加农炮的炮筒的大小依赖于力量。
把加农炮放到右下角。 (点击这里)
试着加入一个更好的键盘接口。例如,用+
和-
来增加或者减少力量,用enter
来发射。提示:QAccel
和在LCDRange
中新建addStep()
和subtractStep()
,就像QSlider::addStep()
。如果你被左面和右面键所苦恼(我就是!),试着都改变!
现在你可以进行第十一章了。
lcdrange.h:
#ifndef LCDRANGE_H
#define LCDRANGE_H
#include <QWidget>
class QSlider;
class LCDRange : public QWidget
{
Q_OBJECT
public:
explicit LCDRange(QWidget *parent = 0, Qt::WindowFlags name = Qt::WindowFlags());
int value() const;
public slots:
void setValue(int);
void setRange(int minVal, int maxVal);
signals:
void valueChanged(int);
private:
QSlider *slider;
};
#endif // LCDRANGE_H
lcdrange.cpp:
#include "lcdrange.h"
#include <QVBoxLayout>
#include <QLCDNumber>
#include <QSlider>
LCDRange::LCDRange(QWidget *parent, Qt::WindowFlags name)
: QWidget(parent, name)
{
QLCDNumber *lcd = new QLCDNumber(2);
slider = new QSlider(Qt::Horizontal);
slider->setRange(0, 99);
slider->setValue(0);
connect(slider, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)));
connect(slider, SIGNAL(valueChanged(int)), SIGNAL(valueChanged(int)));
setFocusProxy(slider); //设置这个窗口部件的焦点为slider
QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget(lcd);
layout->addWidget(slider);
setLayout(layout);
}
int LCDRange::value() const
{
return slider->value();
}
void LCDRange::setValue(int value)
{
slider->setValue(value);
}
void LCDRange::setRange(int minVal, int maxVal)
{
if (minVal < 0 || maxVal > 99 || minVal > maxVal)
{
qWarning( "LCDRange::setRange(%d,%d)\n"
"\tRange must be 0~99\n"
"\tand minVal must not be greater than maxVal",
minVal, maxVal);
return;
}
slider->setRange(minVal, maxVal);
}
cannon.h:
#ifndef CANNON_H
#define CANNON_H
#include <QWidget>
class CannonField : public QWidget
{
Q_OBJECT
public:
CannonField(QWidget *parent = 0, Qt::WindowFlags name = Qt::WindowFlags());
int angle() const {return ang;}
int force() const {return f;}
public slots:
void setAngle(int degrees);
void setForce(int newton);
signals:
void angleChanged(int);
void forceChanged(int);
protected:
void paintEvent(QPaintEvent *); // 窗口刷新/重绘
private:
QRect cannonRect() const;
int ang; //角度
int f;
};
#endif // CANNON_H
cannon.cpp:
#include "cannon.h"
#include <QPaintEvent>
#include <QPainter>
#include <QPixmap>
CannonField::CannonField(QWidget *parent, Qt::WindowFlags name)
: QWidget(parent, name)
{
ang = 45;
f = 0;
setAutoFillBackground(true);
setPalette(QPalette(QColor(250, 250, 200)));
}
void CannonField::setAngle(int degrees)
{
if (degrees < 5)
degrees = 5;
if (degrees > 70)
degrees = 70;
if (ang == degrees)
return;
ang = degrees;
repaint(cannonRect());
emit angleChanged(ang);
}
void CannonField::setForce(int newton)
{
if (newton < 0)
newton = 0;
if (f == newton)
return;
f = newton;
emit forceChanged(f);
}
void CannonField::paintEvent(QPaintEvent *e)
{
if (!e->rect().intersects(cannonRect()))
return;
QRect cr = cannonRect(); //矩形更新
QPixmap pix(cr.size()); //位图双缓冲
pix.fill(Qt::transparent); //使用透明像素图
QPainter p(&pix); //&pix,在pixmap上画
p.setBrush(Qt::blue);
p.setPen(Qt::NoPen);
p.translate(0, pix.height()-1); //将原点(0,0)变化为(0, pix.height()-1)
p.drawPie(QRect(-35, -35, 70, 70), 0, 90*16); //绘图,开始角度为0,弧长为90*16(四分之一圆)
p.rotate(-ang);
p.drawRect(QRect(33, -4, 15, 8));
p.end();
p.begin(this); //在屏幕上画
p.drawPixmap(cr.topLeft(), pix); //把pixmap的一部分绘制到屏幕上。像素映射pix,原点就放在cr.topLeft()处。
}
QRect CannonField::cannonRect() const
{
QRect r( 0, 0, 50, 50 );
r.moveBottomLeft( rect().bottomLeft());
return r;
}
main.cpp:
#include <QApplication>
#include <Qpushbutton>
#include <QVBoxLayout>
#include <QGridLayout>
#include <Qfont>
#include "lcdrange.h"
#include "cannon.h"
class MyWidget : public QWidget
{
public:
MyWidget(QWidget *parent=0, Qt::WindowFlags name = Qt::WindowFlags());
};
MyWidget::MyWidget(QWidget *parent, Qt::WindowFlags name)
: QWidget(parent, name)
{
QPushButton *quit = new QPushButton("Quit");
quit->setFont(QFont("Times", 18, QFont::Bold));
connect(quit, SIGNAL(clicked()), qApp, SLOT(quit()));
LCDRange *lcdRange = new LCDRange;
lcdRange->setRange(5, 70);
LCDRange *lcdForce = new LCDRange;
lcdForce->setRange(10, 50);
CannonField *cannonField = new CannonField;
connect(lcdRange, SIGNAL(valueChanged(int)), cannonField, SLOT(setAngle(int)));
connect(cannonField, SIGNAL(angleChanged(int)), lcdRange, SLOT(setValue(int)));
connect(lcdForce, SIGNAL(valueChanged(int)), cannonField, SLOT(setForce(int)));
connect(cannonField, SIGNAL(forceChanged(int)), lcdForce, SLOT(setValue(int)));
QGridLayout *grid = new QGridLayout;
grid->addWidget(quit, 0, 0);
grid->addWidget(cannonField, 1, 1);
grid->setColumnStretch(1, 10);
QVBoxLayout *layout = new QVBoxLayout;
layout->addLayout(grid);
setLayout(layout);
QVBoxLayout *leftBox = new QVBoxLayout;
grid->addLayout(leftBox, 1, 0);
leftBox->addWidget(lcdRange);
leftBox->addWidget(lcdForce);
lcdRange->setValue(60);
lcdRange->setFocus(); //设置lcdRange获得键盘焦点
lcdForce->setValue(25);
}
int main(int argc, char **argv)
{
QApplication a(argc, argv);
MyWidget w;
w.setGeometry(100, 100, 500, 400);
w.show();
return a.exec();
}