Qt教程二.docx
《Qt教程二.docx》由会员分享,可在线阅读,更多相关《Qt教程二.docx(46页珍藏版)》请在冰豆网上搜索。
Qt教程二
Qt教程二
这个教程会提供一个比第一个教程更加“真实世界”的Qt编程实例。
它介绍了Qt编程的许多方面,介绍了创建菜单(包括最近使用文件列表)、工具条和对话框、载入和保存用户设置,等等。
如果你对Qt很陌生,如果你还没有阅读过如何学习Qt,请阅读一下。
∙介绍
∙“大图片”
∙数据元素
∙主体很容易
∙实现图形用户界面
∙画布控制
∙文件处理
∙获得数据
∙设置选项
∙项目文件
∙完成
介绍
在这个教程中,我们将会开发一个叫做chart的单一应用程序,它根据用户输入的数据来显示简单的饼形和条形图表。
这个教程提供了一个应用程序开发的概述,包含了一些代码片断和与之相配的解释。
应用程序完整的源程序在examples/chart。
“大图片”
chart程序允许用户创建、保存、载入和直观化简单的数据组。
每一个用户给出的数据元素都可以被给定颜色和饼形块或条形的样式、一些标签文本和文本的位置和颜色。
Element类用来代表数据元素。
程序包含一个调入图表视窗的简单的main.cpp。
这个图表视窗有一个提供访问程序功能的菜单条和工具条。
程序还提供了两个对话框,一个设置选项,另一个用来创建和编辑数据组。
这两个对话框都是由图表视窗的菜单选项或者工具条按钮调用的。
图表视窗的主窗口部件是QCanvasView,它显示一个我们用来画饼形图或条形图的QCanvas。
我们继承QCanvasView来获得一些特定的行为。
同样我们因为需要比标准类提供的稍多一些,所以我们继承了QCanvasText类(用来在画布上放置文本条目)。
项目文件,chart.pro,用来创建可以用来连编应用程序的Makefile。
数据元素
我们将使用一个叫Element的类来存储和访问数据元素。
(由element.h展开。
)
private:
doublem_value;
QColorm_valueColor;
intm_valuePattern;
QStringm_label;
QColorm_labelColor;
doublem_propoints[2*MAX_PROPOINTS];
每一个元素都有一个值。
每一个值都会被使用一种特定的颜色和填充样式来图形化地显示。
值也许会有一个和它们关联的标签,标签会被使用标签的颜色来画,并且对于每一种类型的图表都有一个存储在m_propoints数组中的一个(相对)位置。
#include
#include
#include
#include
尽管Element是一个纯粹的内部数据类,它包含了四个Qt类。
Qt经常被认为是一个纯粹的图形用户界面工具包,但它也提供了一些用来支持应用程序编程的绝大多数方面的非图形用户界面类。
我们使用qcolor.h可以使我们在Element类中控制绘图颜色和文本颜色。
qnamespace.h的用处稍微有些模糊。
绝大多数Qt类都继承于含有各种各样的枚举的Qt这个超级类。
Element类不继承于Qt,所以我们需要包含qnamespace.h来访问这些Qt枚举名称。
另一个替代的方案就是使Element成为Qt的一个子类。
我们包含qstring.h用来使用Qt的Unicode字符串。
为了方便,我们类型定义一个Element的矢量容器,这就是我们为什么把qvaluevector.h头文件放到这里的原因。
typedefQValueVectorElementVector;
Qt提供了大量的容器,一些是基于值的,比如QValueVector,其它一些基于指针。
(请看集合类。
)这里我们只是类型定义了一个容器类型,我们将在ElementVector中保存每一个元素数据组。
constdoubleEPSILON=0.0000001;//必须>INVALID。
元素也许只能是正的值。
因为我们使用双精度实数来存储值,我们不能很容易地拿它们和零作比较。
所以我们指定一个值,EPSILON,它和零非常接近,并且任何比EPSILON大的值可以被认为是正的和有效的。
classElement
{
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(doublevalue=INVALID,QColorvalueColor=Qt:
:
gray,
intvaluePattern=Qt:
:
SolidPattern,
constQString&label=QString:
:
null,
QColorlabelColor=Qt:
:
black){
init(value,valueColor,valuePattern,label,labelColor);
for(inti=0;im_propoints[i]=NO_PROPORTION;
}
构造函数为Element类的所有成员变量提供了默认值。
新的元素总是有没有位置的标签文本。
我们是用init()函数是因为我们也提供了一个set()函数,除了比例位置它做的和构造函数一样。
boolisValid()const{returnm_value>EPSILON;}
因为我们正在把Element存储到一个固定大小的矢量中,所以我们需要检测一个特定元素是否有效(比如应该被用来计算和显示)。
通过isValid()函数很容易到达这一目的。
(由element.cpp展开。
)
doubleElement:
:
proX(intindex)const
{
Q_ASSERT(index>=0&&indexreturnm_propoints[2*index];
}
这里对Element的所有成员都提供了读取函数和设置函数。
proX()和proY()读取函数和setProX()和setProY()设置函数用来读取和设置一个用来确定比例位置所适用的图表的类型索引。
这也就是说用户可以为用于竖直条图表、水平条图表和饼形图表的相同数据组设定不同的标签位置。
注意我们也使用Q_ASSERT宏来提供对图表类型索引的预先情况测试,(请看调试)。
读写数据元素
(由element.h展开。
)
Q_EXPORTQTextStream&operator<<(QTextStream&,constElement&);
Q_EXPORTQTextStream&operator>>(QTextStream&,Element&);
为了使我们的Element类更加独立,我们提供了<<和>>的操作符重载,这样Element就可以被文本流读写。
我们也可以很容易地使用二进制流,但是使用文本可以让用户使用文本编辑器来维护他们的数据,可以更容易的使用脚本语言来产生和过滤。
(由element.cpp展开。
)
#include"element.h"
#include
#include
我们对于操作符的实现需要包含qtextstream.h和qstringlist.h。
constcharFIELD_SEP=':
';
constcharPROPOINT_SEP=';';
constcharXY_SEP=',';
我们用来存储数据的格式是用冒号来分隔字段,用换行来分隔记录。
比例点用分号间隔,它们的x和y使用逗号分隔。
字段的顺序是值、值的颜色、值的样式、标签颜色、标签点、标签文本。
比如:
20:
#ff0000:
14:
#000000:
0.767033,0.412946;0,0.75;0,0:
Red:
withcolons:
!
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:
MagentaorViolet
我们阅读Element数据的方式中对于文本标签中的空白符和字段间隔符都没有问题。
QTextStream&operator<<(QTextStream&s,constElement&element)
{
s<<<<for(inti=0;i:
MAX_PROPOINTS;++i){
s<s<<(i==Element:
:
MAX_PROPOINTS-1?
FIELD_SEP:
PROPOINT_SEP);
}
s<returns;
}
写元素就是一直向前。
每一个成员后面都被写一个字段间隔符。
点被写成由逗号间隔的(XY_SEP)x和y的组合,每一对由PROPOINT_SEP分隔符分隔。
最后一个字段是标签和接着的换行符。
QTextStream&operator>>(QTextStream&s,Element&element)
{
QStringdata=s.readLine();
element.setValue(Element:
:
INVALID);
interrors=0;
boolok;
QStringListfields=QStringList:
:
split(FIELD_SEP,data);
if(fields.count()>=4){
doublevalue=fields[0].toDouble(&ok);
if(!
ok)
errors++;
QColorvalueColor=QColor(fields[1]);
if(!
valueColor.isValid())
errors++;
intvaluePattern=fields[2].toInt(&ok);
if(!
ok)
errors++;
QColorlabelColor=QColor(fields[3]);
if(!
labelColor.isValid())
errors++;
QStringListpropoints=QStringList:
:
split(PROPOINT_SEP,fields[4]);
QStringlabel=data.section(FIELD_SEP,5);
if(!
errors){
element.set(value,valueColor,valuePattern,label,labelColor);
inti=0;
for(QStringList:
:
iteratorpoint=propoints.begin();
i:
MAX_PROPOINTS&&point!
=propoints.end();
++i,++point){
errors=0;
QStringListxy=QStringList:
:
split(XY_SEP,*point);
doublex=xy[0].toDouble(&ok);
if(!
ok||x<=0.0||x>=1.0)
errors++;
doubley=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);
}
}
}
returns;
}
为了读取一个元素我们读取一条记录(比如一行)。
我们使用QStringList:
:
split()来把数据分成字段。
因为标签中有可能包含FIELD_SEP字符,所以我们使用QString:
:
section()来获得从最后一个字段到这一行结尾的所有文本。
如果获得了足够的字段和值,颜色和样式数据是有效的,我们使用Element:
:
set()来把这些数据写到元素中,否则我们就会设置这个元素为INVALID。
然后我们对点也是这样。
如果x和y比例是有效的并且在范围内,我们将会为元素设置它们。
如果一个或两个比例是无效的,它们将认为值为零,这样是不合适的,所以我们将会改变无效的(和超出范围的)比例点的值为NO_PROPORTION。
我们的Element类现在足够用来存储、维护和读写元素数据了。
我们也创建了一个元素矢量类型定义来存储一个元素的集合。
我们现在已经准备好通过我们的用户来生成、编辑和可视化他们的数据组来生成main.cpp和用户界面。
如果要获得更多的有关Qt的数据流工具请看QDataStream操作符格式,和任何一个被提及的和你所要存储的东西相似的Qt类的源代码。
主体很容易
(main.cpp。
)
#include
#include"chartform.h"
intmain(intargc,char*argv[])
{
QApplicationapp(argc,argv);
QStringfilename;
if(app.argc()>1){
filename=app.argv()[1];
if(!
filename.endsWith(".cht"))
filename=QString:
:
null;
}
ChartForm*cf=newChartForm(filename);
app.setMainWidget(cf);
cf->show();
app.connect(&app,SIGNAL(lastWindowClosed()),cf,SLOT(fileQuit()));
returnapp.exec();
}
我们把main()函数保持得很简单,很小。
我们创建一个QApplication对象并且传递给它命令行参数。
我们也允许用户通过chartmychart.cht来调用程序,所以如果他们已经添加了一个文件名,我们就把它传递给构造函数。
图表窗口中的大多数行为我们将在下一步进行评论。
实现图形用户界面
chart程序提供了通过排列在中央窗口部件周围的菜单和工具条来访问选项,和一个通常的文档在中央的风格的CanvasView。
(由chartform.h展开。
)
classChartForm:
publicQMainWindow
{
Q_OBJECT
public:
enum{MAX_ELEMENTS=100};
enum{MAX_RECENTFILES=9};//必须不超过9
enumChartType{PIE,VERTICAL_BAR,HORIZONTAL_BAR};
enumAddValuesType{NO,YES,AS_PERCENTAGE};
ChartForm(constQString&filename);
~ChartForm();
intchartType(){returnm_chartType;}
voidsetChanged(boolchanged=true){m_changed=changed;}
voiddrawElements();
QPopupMenu*optionsMenu;//为什么是公有的?
请看canvasview.cpp。
privateslots:
voidfileNew();
voidfileOpen();
voidfileOpenRecent(intindex);
voidfileSave();
voidfileSaveAs();
voidfileSaveAsPixmap();
voidfilePrint();
voidfileQuit();
voidoptionsSetData();
voidupdateChartType(QAction*action);
voidoptionsSetFont();
voidoptionsSetOptions();
voidhelpHelp();
voidhelpAbout();
voidhelpAboutQt();
voidsaveOptions();
private:
voidinit();
voidload(constQString&filename);
boolokToClear();
voiddrawPieChart(constdoublescales[],doubletotal,intcount);
voiddrawVerticalBarChart(constdoublescales[],doubletotal,intcount);
voiddrawHorizontalBarChart(constdoublescales[],doubletotal,intcount);
QStringvalueLabel(constQString&label,doublevalue,doubletotal);
voidupdateRecentFiles(constQString&filename);
voidupdateRecentFilesMenu();
voidsetChartType(ChartTypechartType);
QPopupMenu*fileMenu;
QAction*optionsPieChartAction;
QAction*optionsHorizontalBarChartAction;
QAction*optionsVerticalBarChartAction;
QStringm_filename;
QStringListm_recentFiles;
QCanvas*m_canvas;
CanvasView*m_canvasView;
boolm_changed;
ElementVectorm_elements;
QPrinter*m_printer;
ChartTypem_chartType;
AddValuesTypem_addValues;
intm_decimalPlaces;
QFontm_font;
};
我们创建了一个QMainWindow的子类ChartForm。
我们的子类使用了Q_OBJECT宏来支持Qt的信号和槽机制。
公有接口是很少的,被显示的图表类型能够被追溯,图表可以被标记为“changed”(这样用户在退出的时候会被提示保存),并且图表可以要求拖拽自己(drawElements())。
我们已经把选项菜单设为公有,因为我们也会把这个菜单作为画布视图的关联菜单。
QCanvas类用来绘制二维矢量图。
QCanvasView类用来在一个应用程序的图形用户界面中实现一个画布的视图。
我们所有的绘制操作都发生在画布上,但是事件(比如鼠标点击)却发生在画布视图中。
每一个动作都被一个私有槽实现,比如fileNew()、optionsSetData()等等。
我们也需要相当多的私有函数和数据成员,当我们执行这些实现的时候,我们来看看这些。
为了方便和编译速度的原因,图表视窗的实现被分为三个文件,chartform.cpp实现图形用户界面,chartform_canvas.cpp实现画布处理和chartform_files.cpp实现文件处理。
我们会依次评论每一个。
图表视窗图形用户界面
(由chartform.cpp展开。
)
#include"images/file_new.xpm"
#include"images/file_open.xpm"
#include"images/options_piechart.xpm"
ch