| 主页 | 所有的类 | 主要的类 | 注释的类 | 分组的类 | 函数 |
我们在画布上画饼形区域(或者条形图表条),和所有的标签。画布是通过画布视图来呈现给用户的。drawElements()函数被调用从而在需要的时候重新绘制画布。
(由chartform_canvas.cpp展开。)
void ChartForm::drawElements()
{
QCanvasItemList list = m_canvas->allItems();
for ( QCanvasItemList::iterator it = list.begin(); it != list.end(); ++it )
delete *it;
我们在drawElements()中所作的第一件事是删除所有已经存在的画布条目。
// 360 * 16为一个饼形,Qt中使用的是16倍的度数(就是它的一个圆周为360x16)
int scaleFactor = m_chartType == PIE ? 5760 :
m_chartType == VERTICAL_BAR ? m_canvas->height() :
m_canvas->width();
接下来我们根据要绘制的图表的种类来计算比例因子。
double biggest = 0.0;
int count = 0;
double total = 0.0;
static double scales[MAX_ELEMENTS];
for ( int i = 0; i < MAX_ELEMENTS; ++i ) {
if ( m_elements[i].isValid() ) {
double value = m_elements[i].value();
count++;
total += value;
if ( value > biggest )
biggest = value;
scales[i] = m_elements[i].value() * scaleFactor;
}
}
if ( count ) {
// 第二个循环是因为总量和最大的
for ( int i = 0; i < MAX_ELEMENTS; ++i )
if ( m_elements[i].isValid() )
if ( m_chartType == PIE )
scales[i] = (m_elements[i].value() * scaleFactor) / total;
else
scales[i] = (m_elements[i].value() * scaleFactor) / biggest;
我们需要知道这里有多少值、最大的值和值的总和,这样我们就可以正确地按比例创建饼形区域或条形了。我们把比例值存放在scales数组中。
switch ( m_chartType ) {
case PIE:
drawPieChart( scales, total, count );
break;
case VERTICAL_BAR:
drawVerticalBarChart( scales, total, count );
break;
case HORIZONTAL_BAR:
drawHorizontalBarChart( scales, total, count );
break;
}
}
既然我们已经知道了必需的信息,那我们就调用相关绘制函数,传递比例值、总量和计数。
m_canvas->update();
最终我们使用update()更新画布来使所有的变化可视。
我们来回顾一下刚才的这个绘制函数,看到了画布条目如何被生成并放置到画布上,因为这个教程是关于Qt的,而不是关于绘制图表的好的(或者坏的)算法。
void ChartForm::drawHorizontalBarChart(
const double scales[], double total, int count )
{
画水平条形图我们需要一个比例值的数组、总量(这样我们就可以在需要的时候计算并且画出百分比)和这一组值的计数。
double width = m_canvas->width();
double height = m_canvas->height();
int proheight = int(height / count);
int y = 0;
我们重新得到画布的宽度和高度并且计算比例高度(proheight)。我们把初始的y位置设为0。
QPen pen;
pen.setStyle( NoPen );
我们创建一个用来绘制每一个条形(矩形)的画笔,我们把它设置为NoPen,这样就不会画出边框。
for ( int i = 0; i < MAX_ELEMENTS; ++i ) {
if ( m_elements[i].isValid() ) {
int extent = int(scales[i]);
我们在元素矢量中迭代每一个元素,忽略无效的元素。每个条的宽度(它的长度)很简单地就是它的比例值。
QCanvasRectangle *rect = new QCanvasRectangle(
0, y, extent, proheight, m_canvas );
rect->setBrush( QBrush( m_elements[i].valueColor(),
BrushStyle(m_elements[i].valuePattern()) ) );
rect->setPen( pen );
rect->setZ( 0 );
rect->show();
我们为每个条形创建一个新的QCanvasRectangle,它的x位置为0(因为这是一个水平条形图,每个条形都从左边开始),y值从0开始,随着每一个要画的条形的高度增长,一直到我们要画的条形和画布的高度。然后我们设置条形的画刷为用户为元素指定的颜色和样式,设置画笔为我们先前生成的画笔(比如,设置为NoPen)并且我们把条形的Z轴顺序设置为0。最后我们调用show()在画布上绘制条形。
QString label = m_elements[i].label();
if ( !label.isEmpty() || m_addValues != NO ) {
double proX = m_elements[i].proX( HORIZONTAL_BAR );
double proY = m_elements[i].proY( HORIZONTAL_BAR );
if ( proX < 0 || proY < 0 ) {
proX = 0;
proY = y / height;
}
如果用户已经为元素指定了标签或者要求把值(或者百分比)显示出来,我们也要画一个画布文本条目。我们创建我们自己的CanvasText类(请看后面),因为我们想存储每一个画布文本条目中对应元素的索引(在元素矢量中)。我们从元素中得出x和y的比例值。如果其中之一< 0,那么他们还没有被用户定位,所以你必须计算它们的位置。我们标签的x值为0(左)并且y值为条形图的顶部(这样标签的左上角就会在x,y位置)。
label = valueLabel( label, m_elements[i].value(), total );
然后我们调用一个助手函数valueLabel(),它可以返回一个包含标签文本的字符串。(如果用户已经设置相应的选项,valueLabel()函数添加值或者百分比到这个文本的标签。)
CanvasText *text = new CanvasText( i, label, m_font, m_canvas );
text->setColor( m_elements[i].labelColor() );
text->setX( proX * width );
text->setY( proY * height );
text->setZ( 1 );
text->show();
m_elements[i].setProX( HORIZONTAL_BAR, proX );
m_elements[i].setProY( HORIZONTAL_BAR, proY );
然后我们创建一个CanvasText条目,传递给它在元素矢量中这个元素的索引和所要使用的标签、字体和画布。我们设置文本条目的颜色为用户指定的颜色并且设置条目的x和y位置和画布的宽高成比例。我们设置Z轴顺序为1,这样文本条目总是在条形(Z轴顺序为0)的上面(前面)。我们调用show()函数在画布上绘制文本条目,并且设置元素的相对x和y位置。
}
y += proheight;
在绘制完条形和可能存在的标签之后,我们给y增加一定比例的高度用来准备绘制下一个元素。
}
}
}
(由canvastext.h展开。)
class CanvasText : public QCanvasText
{
public:
enum { CANVAS_TEXT = 1100 };
CanvasText( int index, QCanvas *canvas )
: QCanvasText( canvas ), m_index( index ) {}
CanvasText( int index, const QString& text, QCanvas *canvas )
: QCanvasText( text, canvas ), m_index( index ) {}
CanvasText( int index, const QString& text, QFont font, QCanvas *canvas )
: QCanvasText( text, font, canvas ), m_index( index ) {}
int index() const { return m_index; }
void setIndex( int index ) { m_index = index; }
int rtti() const { return CANVAS_TEXT; }
private:
int m_index;
};
我们的CanvasText子类是QCanvasText的一个非常简单的特化。我们所做的一切只是添加一个私有成员m_index,它用来保存和这个文本相关的元素的元素矢量索引,并且提供为这个值提供一个读和写函数。
(由canvasview.h展开。)
class CanvasView : public QCanvasView
{
Q_OBJECT
public:
CanvasView( QCanvas *canvas, ElementVector *elements,
QWidget* parent = 0, const char* name = "canvas view",
WFlags f = 0 )
: QCanvasView( canvas, parent, name, f ),
m_elements( elements ) {}
protected:
void viewportResizeEvent( QResizeEvent *e );
void contentsMousePressEvent( QMouseEvent *e );
void contentsMouseMoveEvent( QMouseEvent *e );
void contentsContextMenuEvent( QContextMenuEvent *e );
private:
QCanvasItem *m_movingItem;
QPoint m_pos;
ElementVector *m_elements;
};
我们需要继承QCanvasView,这样我们就能处理:
为了支持这些,我们存储一个到正在被移动的画布条目的指针和它的最终位置。我们也存储一个到元素矢量的指针。
(由canvasview.cpp展开。)
void CanvasView::contentsContextMenuEvent( QContextMenuEvent * )
{
((ChartForm*)parent())->optionsMenu->exec( QCursor::pos() );
}
当用户调用一个上下文菜单(比如在绝大多数平台通过右键点击),我们把画布视图的父对象(是一个ChartForm)转化为正确的类型,然后用exec()在光标位置执行选项菜单。
void CanvasView::viewportResizeEvent( QResizeEvent *e )
{
canvas()->resize( e->size().width(), e->size().height() );
((ChartForm*)parent())->drawElements();
}
为了改变大小我们简单地改变花布的大小,画布视图就会呈现在视窗客户端区域的宽高中,然后调用drawElements()函数来重新绘制图表。因为drawElements()画的每一件都和画布的宽高有关,所以图表就会被正确地绘制。
当用户想把标签拖拽到他们点击的位置时,就应该拖拽它并在新的位置释放它。
void CanvasView::contentsMousePressEvent( QMouseEvent *e )
{
QCanvasItemList list = canvas()->collisions( e->pos() );
for ( QCanvasItemList::iterator it = list.begin(); it != list.end(); ++it )
if ( (*it)->rtti() == CanvasText::CANVAS_TEXT ) {
m_movingItem = *it;
m_pos = e->pos();
return;
}
m_movingItem = 0;
}
当用户点击鼠标时,我们创建一个鼠标点击“碰撞”(如果有的话)的画布条目的列表。然后我们迭代这个列表并且如果我们发现一个CanvasText条目,我们就把它设置为移动的条目并且记录下它的位置。否则我们设置为不移动条目。
void CanvasView::contentsMouseMoveEvent( QMouseEvent *e )
{
if ( m_movingItem ) {
QPoint offset = e->pos() - m_pos;
m_movingItem->moveBy( offset.x(), offset.y() );
m_pos = e->pos();
ChartForm *form = (ChartForm*)parent();
form->setChanged( true );
int chartType = form->chartType();
CanvasText *item = (CanvasText*)m_movingItem;
int i = item->index();
(*m_elements)[i].setProX( chartType, item->x() / canvas()->width() );
(*m_elements)[i].setProY( chartType, item->y() / canvas()->height() );
canvas()->update();
}
}
当用户拖拽鼠标的时候,移动事件就产生了。如果那里是一个可移动条目,我们从鼠标最后的位置和可移动条目原来的位置计算出位移。我们将新的位置记录为最后的位置。因为图表现在改变了,所以我们调用setChanged(),这样当用户试图退出或者读入已存在的图表时或者创建新的图表,就会被提示是否保存。我们也分别地更新当前图表类型的元素的x和y的比例位置为当前x和y与宽和高的比例。我们知道要更新哪个元件因为当我们创建每个画布文本条目的时候,我们传给它一个这个元素所对应的位置索引。我们继承了QCanvasText,这样我们就可以设置和读取这个索引值。最后我们调用update()来重绘画布。
| QCanvas没有任何视觉效果。为了看到画布的内容,你必须创建一个QCanvasView来呈现画布。如果条目被show()显示,它们就会出现在画布视图中,然后,只有当QCanvas::update()被调用的时候。默认情况下QCanva的背景是白色,并且会在画布上绘制默认的形状,比如QCanvasRectangle、QCanvasEllipse等等,因为它们被白色填充,所以非常推荐使用一个非白色的画刷颜色! |
« 实现图形用户界面 | 目录 | 文件处理 »
| Copyright © 2002 Trolltech | Trademarks | 译者:Cavendish | Qt 3.0.5版
|