主页 | 所有的类 | 主要的类 | 注释的类 | 分组的类 | 函数

坐标系统

在Qt中的一个绘画设备是一个可画的二维平面。QWidgetQPixmapQPictureQPrinter都是绘画设备。QPainter是一个可以在上面画的对象。

一个绘画设备的默认坐标系统的原点在左上角。X轴由左向右增加,Y轴由上向下增加。对于基于像素的设备单位是像素,对于打印机是点。

一个例子

下面这个图显示了一个绘画设备的左上角的高度放大的部分。

这个矩形和线段是由下面的代码画出来的(在这个图中添加了网格):

    void MyWidget::paintEvent( QPaintEvent * )
    {
        QPainter p( this );
        p.setPen( darkGray );
        p.drawRect( 1,2, 5,4 );
        p.setPen( lightGray );
        p.drawLine( 9,2, 7,7 );
    }

applies to all the relevant functions in QPainter. 请注意drawRect()所画的所有像素是在指定的大小之内(5*4像素)。这一点和其它工具包不同;Qt中你指定的大小包括你所要画的像素。这一点适用于所有QPainter中相关的函数。

相似的,drawLine()调用画了这个线段的两个端点,而不仅仅是一个。

这里是一些和坐标系统关系很近的类:

变换

尽管Qt默认的坐标系统像我们上面描述的那样工作,QPainter也支持任意的变换。

这个变换引擎是一个三步的管道,和下面的Foley & Van Dam and the OpenGL Programming Guide两本书中的模型非常接近。参考那些深度的内容,这里我们给出了一个简单的概述和一个例子。

第一步使用通用变换矩阵。使用这个矩阵来定位你的模型中的对象。Qt提供了像QPainter::rotate()、QPainter::scale()、QPainter::translate()等等方法来操作这个矩阵。

QPainter::save()和QPainter::restore()存储和恢复这个矩阵。你也可以使用QWMatrix对象、QPainter::worldMatrix()和QPainter::setWorldMatrix()来存储和使用指定的矩阵。

第二步使用窗口。这个窗口在模式坐标上说明视图的边界。矩阵定位对象并且QPainter::setWindow()定位这个窗口,决定哪个坐标系统是可见的。(如果你有三维的经验,这个窗口通常是被三维投影出来的。)

第三步使用视口。视口也描述视图的边界,但是是在设备坐标上。视口和窗口描述的是同一个矩形,但是在不同的坐标系统中。

在屏幕上,默认的是你所画的通常合适的整个QWidget或者QPixmap。对于打印这个功能是非常重要的,因为只有很少的打印机可以在真个物理页面上打印。

所以每一个要画的对象被使用QPainter::worldMatrix()变换到模式坐标中,然后被QPainter::window()剪辑,最后被使用QPainter::viewport()定位到所画的设备上。

没有上述一个或两个步骤也是可能做到的。举例来说,比如你的目标是画一些按比例缩放的东西,然后使用QPainter::scale()来达到理想的效果。如果你使用一个大小固定的坐标系统,QPainter::setWindow()是最好的。等等。

这里是一个使用所有这三个机制的一个简单的例子:这个函数在aclock/aclock.cpp例子中画了一个钟表面板。我们建议你在读任何代码之前先编译和运行这个例子。尤其是,试着把这个窗口改变成不同的形状。

    void AnalogClock::drawClock( QPainter *paint )
    {
        paint->save();

首先,我们保存绘画工具的状态,这样的调用函数就保证了不会将要使用的变换干扰。

        paint->setWindow( -500,-500, 1000,1000 );

我们设置一个1000*1000、原点(0,0)在中间的窗口的模式坐标系统。

        QRect v = paint->viewport();
        int d = QMIN( v.width(), v.height() );

这个设备也许不是正方形的,但我们希望钟表是正方形的,所以我们找到它当前的视口并计算它最短的边。

        paint->setViewport( v.left() + (v.width()-d)/2,
                            v.top() + (v.height()-d)/2, d, d );

然后我们设置一个新的正方形视口,把它放在原来的中间。

现在我们做完了我们的视图。从这点上来看,当我们在(0,0)点周围画一个1000*1000的区域,我们所画的东西将会在适合输出设备的最大可能的正方形区域中显示出来。

是我们开始绘图的时候了。

        // time = QTime::currentTime();
        QPointArray pts;

因为我们在画一个钟表,所以我们需要知道时间。pts刚好是一个用来存储一些点的有用的变量。

接下来我们画三个块,一个是时针,一个是分针,最后一个是钟表面板自己。首先我们来画时针:

        paint->save();
        paint->rotate( 30*(time.hour()%12-3) + time.minute()/2 );

我们保存绘画工具,然后根据时针转动的方向旋转它。

        pts.setPoints( 4, -20,0,  0,-20, 300,0, 0,20 );
        paint->drawConvexPolygon( pts );

我们把pts设置为一个四点的多边形,好像时针就在三点钟,然后画上它。因为这个旋转,时针被画到正确的位置。

        paint->restore();

我们恢复被存储哦绘图工具,取消旋转。我们也可以通过调用rotate( -30 ),但是那样也许会导致旋转错误,所以使用save()和restore()是更好的方法。接下来,按大致相同哦方式画分针:

        paint->save();
        paint->rotate( (time.minute()-15)*6 );
        pts.setPoints( 4, -10,0, 0,-10, 400,0, 0,10 );
        paint->drawConvexPolygon( pts );
        paint->restore();

唯一的不同是如何计算要旋转的角度和多边形的形状。

要画的最后一部分就是钟表面板自己。

        for ( int i=0; i<12; i++ ) {
            paint->drawLine( 440,0, 460,0 );
            paint->rotate( 30 );
        }

十二个短的时针刻度之间的间隔是三十度。最后,绘图工具被旋转不是一个非常有用的方法,但是我们已经画完了,所以这也没什么。

        paint->restore();
    }

最后一行的函数是恢复绘图工具,这样绘图工具的调用者就不用担心我们所做的所有变换会影响它。


Copyright © 2002 Trolltech Trademarks 译者:Cavendish
Qt 3.0.5版