Linux 版 (精华区)

发信人: pilot (不打Quake专心论文), 信区: Linux
标  题: Qt tutorial 10 (Translated by ePilot-=V=-)
发信站: 哈工大紫丁香 (2000年06月11日14:19:10 星期天), 转信

因为我已经把这个Qt作为我的本科毕业论文的英汉翻译部分,所以肯定会在近期内翻
译完毕.

Subject Qt tutorial -------"Smooth as Silk"
Posted by kensou
Translated by ePilot-=V=-

第十章: Smooth as Silk

在这个例程中我们使用位图(pixmap)来消除闪烁,而且加入了对炮台发射炮弹的力度控
制.

lcdrange.h LCDRange类定义
/****************************************************************
**
** Definition of LCDRange class, Qt tutorial 10
**
****************************************************************/
#ifndef LCDRANGE_H
#define LCDRANGE_H
#include <qwidget.h>
class QScrollBar;
class QLCDNumber;
class LCDRange : public QWidget
{
    Q_OBJECT

    public:
        LCDRange( QWidget *parent=0, const char *name=0 );
        int value() const;

    public slots:
        void setValue( int );
        void setRange( int minVal, int maxVal );

    signals:
        void valueChanged( int );

    protected:
        void resizeEvent( QResizeEvent * );

    private:
        QScrollBar  *sBar;
        QLCDNumber  *lcd;
};
#endif // LCDRANGE_H

lcdrange.cpp LCDRange类实现文件
/****************************************************************
**
** Implementation of LCDRange class, Qt tutorial 10
**
****************************************************************/
#include "lcdrange.h"
#include <qscrollbar.h>
#include <qlcdnumber.h>
LCDRange::LCDRange( QWidget *parent, const char *name )
: QWidget( parent, name )
{
    lcd  = new QLCDNumber( 2, this, "lcd"  );
    lcd->move( 0, 0 );
    sBar = new QScrollBar( 0, 99,                       // range
                           1, 10,                       // line/page steps
                           0,                           // inital value
                           QScrollBar::Horizontal,      // orientation
                           this, "scrollbar" );
    connect( sBar, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)) );
    connect( sBar, SIGNAL(valueChanged(int)), SIGNAL(valueChanged(int)) );
}
int LCDRange::value() const
{
    return sBar->value();
}
void LCDRange::setValue( int value )
{
    sBar->setValue( value );
}
void LCDRange::setRange( int minVal, int maxVal )
{
    if ( minVal < 0 || maxVal > 99 || minVal > maxVal ) {
        warning( "LCDRange::setRange(%d,%d)\n"
                 "\tRange must be 0..99\n"
                 "\tand minVal must not be greater than maxVal",
                 minVal, maxVal );
        return;
    }
    sBar->setRange( minVal, maxVal );
}
void LCDRange::resizeEvent( QResizeEvent * )
{
    lcd->resize( width(), height() - 16 - 5 );
    sBar->setGeometry( 0, lcd->height() + 5, width(), 16 );
}

cannon.h CannonField类定义
/****************************************************************
**
** Definition of CannonField class, Qt tutorial 10
**
****************************************************************/
#ifndef CANNON_H
#define CANNON_H
#include <qwidget.h>
class CannonField : public QWidget
{
    Q_OBJECT

    public:
        CannonField( QWidget *parent=0, const char *name=0 );
        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:
        void  paintCannon( QPainter * );
        QRect cannonRect() const;
        int ang;
        int f;
};
#endif // CANNON_H

cannon.cpp CannonField类实现文件
/****************************************************************
**
** Implementation CannonField class, Qt tutorial 10
**
****************************************************************/
#include "cannon.h"
#include <qpainter.h>
#include <qpixmap.h>
CannonField::CannonField( QWidget *parent, const char *name )
: QWidget( parent, name )
{
    ang = 45;
    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(), FALSE );
    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 )
{
    QRect updateR = e->rect();
    QPainter p;
    p.begin( this );
    if ( updateR.intersects( cannonRect() ) )
        paintCannon( &p );
    p.end();
}
const QRect barrel_rect(33, -4, 15, 8);
void CannonField::paintCannon( QPainter *p )
{
    QRect    cr = cannonRect();
    QPixmap  pix( cr.size() );
    QPainter tmp;
    pix.fill( this, cr.topLeft() );
    tmp.begin( &pix );
    tmp.setBrush( blue );
    tmp.setPen( NoPen );
    tmp.translate( 0, pix.height() - 1 );
    tmp.drawPie( QRect( -35,-35, 70, 70 ), 0, 90*16 );
    tmp.rotate( -ang );
    tmp.drawRect( barrel_rect );
    tmp.end();
    p->drawPixmap( cr.topLeft(), pix );
}
QRect CannonField::cannonRect() const
{
    QRect r( 0, 0, 50, 50 );
    r.moveBottomLeft( rect().bottomLeft() );
    return r;
}

main.cpp 包含MyWidget类及main函数
/****************************************************************
**
** Qt tutorial 10
**
****************************************************************/
#include <qapplication.h>
#include <qpushbutton.h>
#include <qscrollbar.h>
#include <qlcdnumber.h>
#include <qfont.h>
#include "lcdrange.h"
#include "cannon.h"
class MyWidget : public QWidget
{
    public:
        MyWidget( QWidget *parent=0, const char *name=0 );

    protected:
        void resizeEvent( QResizeEvent * );

    private:
        QPushButton *quit;
        LCDRange    *angle;
        LCDRange    *force;
        CannonField *cannonField;
};
MyWidget::MyWidget( QWidget *parent, const char *name )
: QWidget( parent, name )
{
    setMinimumSize( 500, 355 );
    quit = new QPushButton( "Quit", this, "quit" );
    quit->setGeometry( 10, 10, 75, 30 );
    quit->setFont( QFont( "Times", 18, QFont::Bold ) );
    connect( quit, SIGNAL(clicked()), qApp, SLOT(quit()) );
    angle  = new LCDRange( this, "angle" );
    angle->setRange( 5, 70 );
    angle->setGeometry( 10, quit->y() + quit->height() + 10, 75, 130 );
    force  = new LCDRange( this, "force" );
    force->setRange( 10, 50 );
    force->setGeometry( 10, angle->y() + angle->height() + 10, 75, 130 );
    cannonField = new CannonField( this, "cannonField" );
    cannonField->move( angle->x() + angle->width() + 10, angle->y() );
    cannonField->setBackgroundColor( QColor( 250, 250, 200) );
    connect( angle,SIGNAL(valueChanged(int)), cannonField,SLOT(setAngle(int)));
    connect( cannonField,SIGNAL(angleChanged(int)), angle,SLOT(setValue(int)));
    connect( force,SIGNAL(valueChanged(int)), cannonField,SLOT(setForce(int)));
    connect( cannonField,SIGNAL(forceChanged(int)), force,SLOT(setValue(int)));
    angle->setValue( 60 );
    force->setValue( 25 );
}
void MyWidget::resizeEvent( QResizeEvent * )
{
    cannonField->resize( width()  - cannonField->x() - 10,
                         height() - cannonField->y() - 10 );
}
int main( int argc, char **argv )
{
    QApplication::setColorSpec( QApplication::CustomColor );
    QApplication a( argc, argv );
    MyWidget w;
    w.setGeometry( 100, 100, 500, 355 );
    a.setMainWidget( &w );
    w.show();
    return a.exec();
}

Makefile Make文件,包含了支持signal/slot的内容
#############################################################################
# $Id: Makefile,v 2.6 1998/02/27 14:03:18 hanord Exp $
#
# Win32 Makefile, requires Microsoft nmake.
#
# Copyright (C) 1998 by Troll Tech AS.  All rights reserved.
#
#############################################################################
PROJECT = t10
!INCLUDE $(QTDIR)\Makefile.inc

逐行解释

cannon.h
这次加入了力度控制。

    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:
    void  paintCannon( QPainter * );
    QRect cannonRect() const;
我们把画炮台的代码放到一个单独的函数里,定义炮管矩形区域的代码也一样(放到一个
单独的函数里).

    int ang;
    int f;
};
f是力度值。

cannon.cpp

#include <qpixmap.h>
包含了QPixmap的类定义.

CannonField::CannonField( QWidget *parent, const char *name )
: QWidget( parent, name )
{
    ang = 45;
    f   = 0;
}
力度值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(), FALSE );
    emit angleChanged( ang );
}
setAngle()作了一点小改动:只重画组件区域中的炮管部分,也就是真正需要重画的部分,
FALSE参数的意义是指在向组件发送重画消息之前不擦除原来的图像.

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 )
{
    QRect updateR = e->rect();
    QPainter p;
    p.begin( this );
    if ( updateR.intersects( cannonRect() ) )
        paintCannon( &p );
    p.end();
}
通过优化重画事件处理函数我们可以只重画真正需要刷新的部分.首先自QPaintEvent
取得需要重画的矩形区域参数,然后我们判断要求刷新的矩形区域是否与炮管区域相交.
如果相交,就重画炮管.注意我们把QPainter指针作为参数传给了paintCannon()函数.
(相交指至少有一个像素为两个矩形所共有)

const QRect barrel_rect(33, -4, 15, 8);
把炮管区域定义为常量.

void CannonField::paintCannon( QPainter *p )
{
    QRect    cr = cannonRect();
取得炮管矩形区域并用cr来保存.

QPixmap  pix( cr.size() );
这里我们第一次碰到QPixmap:用于进行无闪烁绘图的"幕后"绘图类.我们创建了一个与
炮管尺寸一样的位图(pixmap)实例.通过它来画炮管并显示到屏幕上.这种手段我们一般
称之为“双缓冲”,可以有效的防止画面闪烁.

QPainter tmp;
创建一个临时的QPainter对象tmp,用来在pixmap中画图.

pix.fill( this, cr.topLeft() );
当一个pixmap创建时被随机的色彩所填充,所以我们要做的第一件事就是使用组件背景
色重新填充pixmap.同时必须指定pixmap在组件中的位置(cr.topLeft()).于是就能适于
任何类型(这里指颜色)的背景(参见background pixmaps).

tmp.begin( &pix );
tmp.setBrush( blue );
tmp.setPen( NoPen );
就像前一章一样用蓝色的brush和不可见的pen在pixmap上绘图,这次通过重载QPainter
函数setPen(),setBrush()来更快捷的设置brush,pen的属性.

tmp.translate( 0, pix.height() - 1 );
tmp.drawPie( QRect( -35,-35, 70, 70 ), 0, 90*16 );
tmp.rotate( -ang );
tmp.drawRect( barrel_rect );
tmp.end();
这些画炮管的代码与前一章的例子非常相似.不同之处在于这一次translate()是相对于
pixmap的高度(建议复习一下上一章的translate()是怎么调用的),以及这一次炮管的矩
形区域是一个常量.

p->drawPixmap( cr.topLeft(), pix );
最后,我们调用drawPixmap()把这个pixmap显示到屏幕上(in one lightning fast
operation).

QRect CannonField::cannonRect() const
{
    QRect r( 0, 0, 50, 50 );
    r.moveBottomLeft( rect().bottomLeft() );
    return r;
}
这个函数返回组件坐标中的炮管矩形区域.首先创建一个50x50的矩形,然后设置这个矩
形的左下角与组件的左下角重合.rect()返回相对于本身坐标系统的矩形区域值(左上角
是0,0).

main.cpp

class MyWidget : public QWidget
{
    public:
        MyWidget( QWidget *parent=0, const char *name=0 );
    protected:
        void resizeEvent( QResizeEvent * );
    private:
        QPushButton *quit;
        LCDRange    *angle;
        LCDRange    *force;
        CannonField *cannonField;
};
增加了一个LCDRange用于设置显示力度值.

force  = new LCDRange( this, "force" );
force->setRange( 10, 50 );
force->setGeometry( 10, angle->y() + angle->height() + 10, 75, 130 );
把力度LCDRange组件放到角度LCDRange组件的下面,设置调整范围为10..50.

connect( force,SIGNAL(valueChanged(int)), cannonField,SLOT(setForce(int)));
connect( cannonField,SIGNAL(forceChanged(int)), force,SLOT(setValue(int)));
就像角度组件一样,我们把force和cannonField链接起来.

force->setValue( 25 );
程序启动时力度值初始化为25.

程序行为

闪烁消失了.
加入了力度控制.

练习

使炮管尺寸随发射力度而变化.
把炮台改到窗口右下角.

你现在可以进入第十一章了.

--


               〓〓★〓〓

        比别人飞的更高,更快,更强!

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: mtnews.hit.edu.cn]
※ 修改:·pilot 於 06月11日14:25:24 修改本文·[FROM: mtnews.hit.edu.cn]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:205.960毫秒