主页 | 所有的类 | 主要的类 | 注释的类 | 分组的类 | 函数 |
数据元素
我们将使用一个叫Element的类来存储和访问数据元素。
(由element.h展开。)
private:
double m_value; QColor m_valueColor; int m_valuePattern; QString m_label; QColor m_labelColor; double m_propoints[2 * MAX_PROPOINTS];
每一个元素都有一个值。每一个值都会被使用一种特定的颜色和填充样式来图形化地显示。值也许会有一个和它们关联的标签,标签会被使用标签的颜色来画,并且对于每一种类型的图表都有一个存储在m_propoints数组中的一个(相对)位置。
#include <qcolor.h> #include <qnamespace.h> #include <qstring.h> #include <qvaluevector.h>
尽管Element是一个纯粹的内部数据类,它包含了四个Qt类。Qt经常被认为是一个纯粹的图形用户界面工具包,但它也提供了一些用来支持应用程序编程的绝大多数方面的非图形用户界面类。我们使用qcolor.h可以使我们在Element类中控制绘图颜色和文本颜色。qnamespace.h的用处稍微有些模糊。绝大多数Qt类都继承于含有各种各样的枚举的Qt这个超级类。Element类不继承于Qt,所以我们需要包含qnamespace.h来访问这些Qt枚举名称。另一个替代的方案就是使Element成为Qt的一个子类。我们包含qstring.h用来使用Qt的Unicode字符串。为了方便,我们类型定义一个Element的矢量容器,这就是我们为什么把qvaluevector.h头文件放到这里的原因。
typedef QValueVector<Element> ElementVector;
Qt提供了大量的容器,一些是基于值的,比如QValueVector,其它一些基于指针。(请看集合类。)这里我们只是类型定义了一个容器类型,我们将在ElementVector中保存每一个元素数据组。
const double EPSILON = 0.0000001; // 必须 > INVALID。
元素也许只能是正的值。因为我们使用双精度实数来存储值,我们不能很容易地拿它们和零作比较。所以我们指定一个值,EPSILON,它和零非常接近,并且任何比EPSILON大的值可以被认为是正的和有效的。
class Element { public: enum { INVALID = -1 }; enum { NO_PROPORTION = -1 }; enum { MAX_PROPOINTS = 3 }; // 每个图表类型一个比例值
我们给Element定义了三个公有的枚举变量。INVALID被isValid()函数使用。它是有用的,因为我们将用使用一个固定大小的Element矢量,并且可以通过给定INVALID值来标明未使用的Element。NO_PROPORTION枚举变量用来表明用户还没有定位元素的标签,任何正的比例值都被用来作为与画布大小成比例的文本元素的位置。
如果我们存储每一个标签的实际x和y的位置,当用户每次重新定义主窗口大小(同样也对画布)的时候,文本会保留它的初始(现在是错的)位置。所以我们不存储绝对(x,y)位置,我们存储比例位置,比如x/width和y/height。然后当我们画文本的时候,我们分别用当前的宽度和高度来乘这些位置,这样不管大小如何变化,文本都会被正确定位。比如,如果标签的x位置为300,画布有400像素宽,x的比例值为300/400 = 0.75。
MAX_PROPOINTS枚举变量是有些疑问的。我们对于每一个图表类型的文本标签都要存储x和y的比例。并且我们已经选择把这些比例存储到一个固定大小的数组中。因为我们必须指定所需要的比例对的最大数字。如果我们改变图表类型的数字,这个值就必须被改变,这也就是说Element类和由ChartForm所提供的图表类型的数字是紧密联系的。在一个更大的应用程序中,我们也许使用一个矢量来存储这些点并且根据所能提供的图表类型的数量来动态改变它的大小。
Element( double value = INVALID, QColor valueColor = Qt::gray, int valuePattern = Qt::SolidPattern, const QString& label = QString::null, QColor labelColor = Qt::black ) { init( value, valueColor, valuePattern, label, labelColor ); for ( int i = 0; i < MAX_PROPOINTS * 2; ++i ) m_propoints[i] = NO_PROPORTION; }
构造函数为Element类的所有成员变量提供了默认值。新的元素总是有没有位置的标签文本。我们是用init()函数是因为我们也提供了一个set()函数,除了比例位置它做的和构造函数一样。
bool isValid() const { return m_value > EPSILON; }
因为我们正在把Element存储到一个固定大小的矢量中,所以我们需要检测一个特定元素是否有效(比如应该被用来计算和显示)。通过isValid()函数很容易到达这一目的。
(由element.cpp展开。)
double Element::proX( int index ) const { Q_ASSERT(index >= 0 && index < MAX_PROPOINTS); return m_propoints[2 * index]; }
这里对Element的所有成员都提供了读取函数和设置函数。proX()和proY()读取函数和setProX()和setProY()设置函数用来读取和设置一个用来确定比例位置所适用的图表的类型索引。这也就是说用户可以为用于竖直条图表、水平条图表和饼形图表的相同数据组设定不同的标签位置。注意我们也使用Q_ASSERT宏来提供对图表类型索引的预先情况测试,(请看调试)。
读写数据元素
(由element.h展开。)
Q_EXPORT QTextStream &operator<<( QTextStream&, const Element& ); Q_EXPORT QTextStream &operator>>( QTextStream&, Element& );
为了使我们的Element类更加独立,我们提供了<<和>>的操作符重载,这样Element就可以被文本流读写。我们也可以很容易地使用二进制流,但是使用文本可以让用户使用文本编辑器来维护他们的数据,可以更容易的使用脚本语言来产生和过滤。
(由element.cpp展开。)
#include "element.h" #include <qstringlist.h> #include <qtextstream.h>
我们对于操作符的实现需要包含qtextstream.h和qstringlist.h。
const char FIELD_SEP = ':'; const char PROPOINT_SEP = ';'; const char XY_SEP = ',';
我们用来存储数据的格式是用冒号来分隔字段,用换行来分隔记录。比例点用分号间隔,它们的x和y使用逗号分隔。字段的顺序是值、值的颜色、值的样式、标签颜色、标签点、标签文本。比如:
20:#ff0000:14:#000000:0.767033,0.412946;0,0.75;0,0:Red :with colons:! 70:#00ffff:2:#ffff00:0.450549,0.198661;0.198516,0.125954;0,0.198473:Cyan 35:#0000ff:8:#555500:0.10989,0.299107;0.397032,0.562977;0,0.396947:Blue 55:#ffff00:1:#000080:0.0989011,0.625;0.595547,0.312977;0,0.59542:Yellow 80:#ff00ff:1:#000000:0.518681,0.694196;0.794063,0;0,0.793893:Magenta or Violet
我们阅读Element数据的方式中对于文本标签中的空白符和字段间隔符都没有问题。
QTextStream &operator<<( QTextStream &s, const Element &element ) { s << element.value() << FIELD_SEP << element.valueColor().name() << FIELD_SEP << element.valuePattern() << FIELD_SEP << element.labelColor().name() << FIELD_SEP; for ( int i = 0; i < Element::MAX_PROPOINTS; ++i ) { s << element.proX( i ) << XY_SEP << element.proY( i ); s << ( i == Element::MAX_PROPOINTS - 1 ? FIELD_SEP : PROPOINT_SEP ); } s << element.label() << '\n'; return s; }
写元素就是一直向前。每一个成员后面都被写一个字段间隔符。点被写成由逗号间隔的(XY_SEP)x和y的组合,每一对由PROPOINT_SEP分隔符分隔。最后一个字段是标签和接着的换行符。
QTextStream &operator>>( QTextStream &s, Element &element ) { QString data = s.readLine(); element.setValue( Element::INVALID ); int errors = 0; bool ok; QStringList fields = QStringList::split( FIELD_SEP, data ); if ( fields.count() >= 4 ) { double value = fields[0].toDouble( &ok ); if ( !ok ) errors++; QColor valueColor = QColor( fields[1] ); if ( !valueColor.isValid() ) errors++; int valuePattern = fields[2].toInt( &ok ); if ( !ok ) errors++; QColor labelColor = QColor( fields[3] ); if ( !labelColor.isValid() ) errors++; QStringList propoints = QStringList::split( PROPOINT_SEP, fields[4] ); QString label = data.section( FIELD_SEP, 5 ); if ( !errors ) { element.set( value, valueColor, valuePattern, label, labelColor ); int i = 0; for ( QStringList::iterator point = propoints.begin(); i < Element::MAX_PROPOINTS && point != propoints.end(); ++i, ++point ) { errors = 0; QStringList xy = QStringList::split( XY_SEP, *point ); double x = xy[0].toDouble( &ok ); if ( !ok || x <= 0.0 || x >= 1.0 ) errors++; double y = xy[1].toDouble( &ok ); if ( !ok || y <= 0.0 || y >= 1.0 ) errors++; if ( errors ) x = y = Element::NO_PROPORTION; element.setProX( i, x ); element.setProY( i, y ); } } } return s; }
为了读取一个元素我们读取一条记录(比如一行)。我们使用QStringList::split()来把数据分成字段。因为标签中有可能包含FIELD_SEP字符,所以我们使用QString::section()来获得从最后一个字段到这一行结尾的所有文本。如果获得了足够的字段和值,颜色和样式数据是有效的,我们使用Element::set()来把这些数据写到元素中,否则我们就会设置这个元素为INVALID。然后我们对点也是这样。如果x和y比例是有效的并且在范围内,我们将会为元素设置它们。如果一个或两个比例是无效的,它们将认为值为零,这样是不合适的,所以我们将会改变无效的(和超出范围的)比例点的值为NO_PROPORTION。
我们的Element类现在足够用来存储、维护和读写元素数据了。我们也创建了一个元素矢量类型定义来存储一个元素的集合。
我们现在已经准备好通过我们的用户来生成、编辑和可视化他们的数据组来生成main.cpp和用户界面。
如果要获得更多的有关Qt的数据流工具请看QDataStream操作符格式,和任何一个被提及的和你所要存储的东西相似的Qt类的源代码。 |
Copyright © 2002 Trolltech | Trademarks | 译者:Cavendish | Qt 3.0.5版
|