1、QT基础学习知识整理1.重要内容:1)信号和槽机制2)事件处理机制3)布局管理器4)绘图5)网络6)多线程7)其他(定时器,中文显示)2.1一个简单的例子不可免俗的,从最简单的基本窗口产生开始介绍,窗口标题就叫作FirstQt!好了,请新增一个目录hello,并在当中使用任一编辑器来编辑一个hello.cpp的档案,内容如下:hello.cpp#include#includeintmain(intargc,char*argv)QApplicationapp(argc,argv);QLabel*label=newQLabel(Hello
2、!World!Orz.);label-setWindowTitle(FirstQt!);label-resize(200,50);label-show();returnapp.exec();要使用Qt的组件,必须含入(include)相对应的头文件,程序的第一行包含的QApplication与Qlabel头文件,稍后才可以使用QApplication与QLabel两个组件的定义类别。每个Qt窗口程序,都必须有且只能有一个QApplication对象,它管理了整个应用程序所需的资源,QLabel是Qt的图型组件之一,继承自QWidget
3、,Widget这个名称来自WindowGadget,表示可视的使用者接口组件,可接受使用者的动作操作,文字画面、按钮、滚动条、工具列甚至容器(Container)等都是一种WidgetC+程序从main开始,再来进行Qt组件的初始化动作,在第一行中:QApplicationapp(argc,argv);QApplication负责程序的初始、结束及处理事件(event)的循环等,并提供基本的窗口外观,这个外观与系统的桌面环境有关,例如标题列的样式、窗口外观、系统功能键等,在不同的操作系统桌面环境下,会有各自不同的外观,QApplication对象接受命令
4、列自变量作为它的自变量,像是如果您没有设定窗口标题,且会使用执行文件的名称作为窗口标题名称,可以使用的自变量与其作用,可以查询Qt在线文件关于QApplication类别的说明。接着建立QLabel组件,它用以显示所指定的文字(在这边指定了Hello!World!Orz.),setWindowTitle()用以设定窗口标题,如果不设定标题,则会自动以程序的文件名称作为标题,resize()方法用以设定组件的长、宽,单位是像素(Pixel),Qt的组件预设是不可视的,所以要使用show()方法将之显示岀来。在最后一行,执行QApplication的exec()方
5、法,这将程序的控制权交给了QApplication,exec()方法会提供一个事件处理循环,窗口显示之后会不断倾听(listen)事件,像是键盘、鼠标等动作所引发的事件,写好程序存盘之后,就可以开始进行make的动作,必须先产生Makefile,Qt提供了qmake程序来协助建立Makefile,它可以自动根据目前目录下档案产生*.pro的专案档(projectfile):qmake-project然后根据项目档产生Makefileqmake接下来就可以进行make:makemake完成之后,可以在debug目录下找到产生的hello.exe档案,直接执行:h
6、ello如果您要可以在Windows下直接doubleclick就执行程序,记得系统环境变量中要设定包括Qt安装目录下的bin目录,执行时的参考画面如下所示:HeUDiWorHEOrz如果要建构release版本,则使用make时指定-f与Makefile名称,例如:make-fMakefile.ReleasePATHI*1HrstQt!回则您可以在release数据夹下看到建构好的档案。QLabel支持HTML卷标,如果您把程序改为以下的内容:#ineludevQApplication#ineludeintmain(inta
7、rgc,char*argv)QApplicationapp(argc,argv);QLabel*label=newQLabel(vfontcolor=blueHello!World!Orz.v/fontv/h1);label-setWindowTitle(FirstQt!);label-resize(200,50);label-show();returnapp.exec();重新建置之后,执行结果将显示如下:ft.FirstQtlHeUo!WorId!Orz.2.2信号和槽机制信号和槽用于对象间的通讯。信号//槽机制是QtQt的一个中心特征并且也
8、许是QtQt与其它框架非常不同的核心特性。在图形用户界面编程中,我们经常希望一个窗口部件的一个变化被通知给另一个窗口部件。更一般地,我们希望任何一类的对象可以和其它对象进行通讯。例如,如果我们正在解析一个XMLXML文件,当我们遇到一个新的标签时,我们也许希望通知列表视图我们正在用来表达XMLXML文件的结构。较老的工具包使用一种被称作回调的通讯方式来实现同一目的。回调是指一个函数的指针,所以如果你希望一个处理函数通知你一些事件,你可以把另一个函数(回调)的指针传递给处理函数。处理函数在适当的时候调用回调。回调有两个主要缺点。首先他们不是类型安全的。我们从来都不能确定处理函数
9、使用了正确的参数来调用回调。其次回调和处理函数是非常强有力地联系在一起的,因为处理函数必须知道要调用哪个回调。、connect(Objectssignall,Object2slotl)_connect)Objectl,signall,Object2,slot2)slotlSl0t2connectiObjectsignals,Objectl,slotl)厂ObjEct4、slotlconmectfObjects,signall,Object4,slot3)一个关于一些信号和槽连接的摘要图在QtQt中我们有一种可以替代回调的技术。我们使用信号和槽。当一个特定
10、事件发生的时候,一个信号被发射。QtQt的窗口部件有很多预定义的信号,但是我们总是可以通过继承来加入我们自己的信号。槽就是一个可以被调用处理特定信号的函数。QtQt的窗口部件又很多预定义的槽,但是通常的习惯是你可以加入自己的槽,这样你就可以处理你所感兴趣的信号。信号和槽的机制是类型安全的:一个信号的签名必须与它的接收槽的签名相匹配。(实际上一个槽的签名可以比它接收的信号的签名少,因为它可以忽略额外的签名。)因为签名是一致的,编译器就可以帮助我们检测类型不匹配。信号和槽是宽松地联系在一起的:一个发射信号的类不用知道也不用注意哪个槽要接收这个信号。QtQt的信号和槽的机制可以保证如果你把一
12、接受,并也将介绍Qt组件的parent/child关系,这个程序将建立一个LCD数字显示组件,以及一个拉杆组件,LCD数字将反应目前拉杆的Objectlsignallsignal2Objects1signallCObject3signallslotlSlot2SlOt3进度:#include#include#include#includeintmain(intargc,char*argv)QApplicationapp(argc,argv);QWidget*parent=newQWidget;parent-setWindowTitle(Signa
13、l&Slot);parent-setMinimumSize(240,140);parent-setMaximumSize(240,140);QLCDNumber*lcd=newQLCDNumber(parent);lcd-setGeometry(70,20,100,30);QSlider*slider=newQSlider(Qt:Horizontal,parent);slider-setRange(0,99);slider-setValue(0);slider-setGeometry(70,70,100,30);QObject:connec
14、t(slider,SIGNAL(valueChanged(int),lcd,SLOT(display(int);parent-show();returnapp.exec();在Qt中建立Widget时,要建立在heap区(即以new的方式),Qt会自动管理parent下child的delete,让您不用亲自管理具有parent/child关系的Widget建构与删除,这可以避免memoryleak,您要delete的只有那些没有parent的对象,如果您将对象建立在stack区,程序将可能会有错误发生。QWidget是
15、Qt中所有使用者图形接口组件的父类别,可在屏幕上绘制自身,可接受鼠标、键盘等接口操作,一个QWidget可以指定它的parent为哪个组件,而这也表示child可显示的范围将是在parent之内,parent没有显示的话,子组件也不会显示。没有指定parent的QWidget是一个独立窗口(window)。在程序中建立了一个QWidget实例,并设定它的标题名称,以及可拉动的最大(setMaximumSize)最小(setMinimumSize)尺寸,由于都设定为240X140像素大小,所以这个窗口就变为不可变动大小的了,也可以只使用一个setF
16、ixedSize()方法来设定:QWidget*parent=newQWidget;parent-setWindowTitle(Signal&Slot);parent-setMinimumSize(240,140);parent-setMaximumSize(240,140);这个QWidget没有指定parent,所以它是一个独立窗口,接下来的QLCDNumbe实例建立时,指定了这个QWidget为它的parent,所以QLCDNumbe被置入了QWidget之中成为child,可显示的范围限制在parent的边界大小之
17、中,它在parent中的位置为X:70、丫:20,长为100、宽为30(setGeometry():QLCDNumber*lcd=newQLCDNumber(parent);lcd-setGeometry(70,20,100,30);接下来的QSlider实例在建立时,也指定了这个QWidget为它的parent,程序中设定QSlider为水平拉杆(Qt:Horizontal),可拉动的数值范围为0到99(setRange(),目前拉杆光标值为0(setValue(),而它在parent中的位置为X:70、Y:70,
18、长为100、宽为30:QSlider*slider=newQSlider(Qt:Horizontal,parent);slider-setRange(0,99);slider-setValue(0);slider-setGeometry(70,70,100,30);当您拉动QSlider的光标,造成光标值变动时会发岀valueChanged(int)Signal,参数int表示Signal带有一个整数值,在这表示QSlider的游标值一并被发岀,QLCDNumbe的display(int)Slot接受Signal所带来的整数值,可以在QLCD
19、Numbe显示数字:QObject:connect(slider,SIGNAL(valueChanged(int),led,SLOT(display(int);一个程序执行时的参考画面如下所示:注意:1.Signal与Slot的签名基本上要相同,但若Signal的参数多于Slot的参数,则额外的参数会被Slot忽略。Signal&Slot2.一个Signal可以关联多个Slot函数。3.Signal也可以和Signal关联。(消息消息)4.Qt管理的对象必须继承QObject类别,以提供Qt对象的Meta讯息,若要实
22、ject子类实例的event()函式,event()这个函式本身通常不直接处理事件,而是基于所传送的事件类型,分派给处理特定类型的事件处理者(EventHandler)。QEvent是Qt中所有事件的基础类别,最常见的事件类型皆为其子类别,像是鼠标事件的QMouseEvent、键盘事件的QKeyEvent、缩放事件的QResizeEvent等,这些子类别事件皆加入其特定的函数,像是鼠标事件的x()、y()函数指岀发生鼠标事件时,鼠标光标的x、y坐标,键盘事件的key()函数可以取得目前所按下的按键常数。以图型组件来说,通常我们会继承QWidget或其子
24、#include#includeclassEventLabel:publicQLabelprotected:voidmouseMoveEvent(QMouseEvent*event);voidmousePressEvent(QMouseEvent*event);voidmouseReleaseEvent(QMouseEvent*event);;voidEventLabel:mouseMoveEvent(QMouseEvent*event)QStringmsg;msg.sprintf(Move:(%d,%d)v/center,eve
25、nt-x(),event-y();this-setText(msg);voidEventLabel:mousePressEvent(QMouseEvent*event)QStringmsg;msg.sprintf(Press:(%d,%d),event-x(),event-y();this-setText(msg);voidEventLabel:mouseReleaseEvent(QMouseEvent*event)QStringmsg;msg.sprintf(Release:(%d,%d),event-x(),event-y
26、();this-setText(msg);intmain(intargc,char*argv)QApplicationapp(argc,argv);EventLabel*label=newEventLabel;label-setWindowTitle(MouseEventDemo);label-resize(300,200);label-show();returnapp.exec();执行时的参考画面如下所示:注意:Qt的事件跟Signal、Slot机制是不同的。Signal与Slot的机制是同步的(Synchronous),S
27、ignal是由对象发出的,使用QObject的connect()连接对象上定义的Slot来立即处理。Qt的事件可以是异步的(Asynchronous)的,QT使用一个事件队列来维护,新的事件产生时基本上会被排到队列的尾端,前一个事件处理完成,再从队列的前端取出下一个队列来处理,必要的时候,Qt的事件也可以是同步的,而事件还可以使用事件过滤器进行过滤处理。2)事件类型及处理者不同类型的事件,都有对应的事件处理函数,它们接受QEvent的特定子类别实例作为自变量,像是下例中mousePressEvent()事件处理函数上的QMouseEvent,您可以针对事
28、件的某些状况作特定处理,而其它未处理的状况,则呼叫父类别对应的的事件处理函式,让父类别预先定义的事件处理可以完成:voidCustomLabel:mousePressEvent(QMouseEvent*event)if(event-button()=Qt:LeftButton)/处理左键按下/.else/由父类别所定义的事件处理函式来事件QLabel:mousePressEvent(event);voidCustomLabel:mouseReleaseEvent(QMouseEvent*event)/鼠标放开事件处理.事实上,每个可传递的事件都有ac
29、cept()与igore()两个方法,用以告知Qt应用程序,这个事件处理者是否接受或忽略此一事件,如果事件处理者中呼叫事件的accept(),则事件不会再进一步传播,若呼叫了ignore(),则Qt应用程序会尝试寻找另一个事件的接受者,您可以藉由isAccepted()方法得知事件是否被接受。一般来说,除了QCloseEvent之外,很少直接呼叫accept()或ignore(),如果您接受事件,则在事件处理者当中实作对事件的处理(如上例的if陈述句),如果您不接受事件,则直接呼叫父类别的事件实作(如上例的else陈述句),对于QW
30、idget来说,预设的实作是:voidQWidget:keyPressEvent(QKeyEvent*event)event-ignore();由于QWidget预设的实作是呼叫ignore(),这让事件可以向父组件传播。QCloseEvent则建议直接呼叫accept()与ignore(),accept()方法会继续关闭的操作,ignore()则会取消关闭的操作:voidMainWindow:closeEvent(QCloseEvent*event)if(continueToClose()event-accept();elseevent-ig
31、nore();QObject的event()方法通常用于分派事件,但在某些情况下,您希望在事件分派给其它事件处理者之前,先行作一些处理,则可以重新定义event()方法,例如在窗口程序中,Tab键按下时希望其将焦点移至下一个图型组件,而不是直接让目前焦点的图形组件直接处理Tab键,则您可以在继承QWidget子类别时,重新定义其event()方法,例如:boolCustomWidget:event(QEvent*event)if(event-type()=QEvent:KeyPress)QKeyEvent*keyEvent=static_c
32、ast(event);if(keyEvent-key()=Qt:Key_Tab)/处理Tab键returntrue;returnQWidget:event(event);在执行时期想要知道所取得之QEvent类型,可以使用QEvent的type()方法取得常数值,并与QEvent:Type作比对。3)事件过滤器Qt将事件封装为QEvent实例之后,会呼叫QObject的event()方法并将QEvent实例传送给它,在某些情况下,您希望对象在执行event()处理事件之前,先对一些事件进行处理或过滤,然后再决定是否呼叫event()
33、方法,这个时候您就可以使用事件过滤器。上面提到的对QWidget按键事件的Tab键处理而言,如果您的图形接口中有很多的组件,每个图型组件都要如当中的范例重新定义event()方法,显然是非常没有效率且没什么维护性的方法。您可以自定义一个对象继承QObject(或其子类别),重新定义它的eventFilter()方法,例如您自定义了一个FilterObject,您希望Tab键可以用来将焦点转移至下一个子组件:boolFilterObject:eventFilter(QObject*object,QEvent*event)if(event-type()
35、tallEventFilter()方法,例如:QLineEdit*nameEdit=newQLineEdit;QLineEdit*addressEdit=newQLineEdit;FilterObjectfilter=newFilterObject;nameEdit-installEventFilter(filter);addressEdit-installEventFilter(filter);您也可以将事件过滤器安装在QApplication,在任何的事件发生后呼叫每个对象的event()方法之前,会先经过事件过滤器,这给您更多控制应用程序事件的能
36、力。Qt的事件循环与sendEvent()方法会呼叫QCoreApplication(QApplication的父类别)的notify()以分派事件,如果您想要完全控制Qt应用程序的事件,则可以重新定义notify()方法。可以看出Qt事件处理的五个层次:重新定义事件处理者、重新定义2.4布局管理器设计窗口程序的人都知道,在窗口程序中最麻烦也最难的就是版面配置,每次都为了组件的位置摆放在伤脑筋,当然我们可以通过setGeometry()来设定Widget于parent中的XY位置与长宽,但这样在您窗口缩放时,当中的组件位置并不会适当的自
37、我调整大小、位置(或像是字号自动调整之类的),以配合窗口缩放展现适当的观感。所以窗口程序的解决方案都会提供一些现成的版面配置方式,让您可以不必自行配置组件位置,以下直接看例子,使用QHBoxLayout进行组件的版面配置,这可以让您以水平的方式来摆放组件:#include#include#include#include#includeintmain(intargc,char*argv)QApplicationapp(argc,argv);QWidget*window=newQWidget;window-setWindowTitle(QH
38、BoxLayout);window-resize(250,50);QLCDNumber*lcd=newQLCDNumber;QSpinBox*spinBox=newQSpinBox;spinBox-setRange(0,99);event()方法、为个别对象安装事件过滤器、为QApplication安装事件过滤器,重新定义QCoreApplication的notify()方法。QObject:connect(spinBox,SIGNAL(valueChanged(int),lcd,SLOT(display(int);QHBoxLayout*l
39、ayout=newQHBoxLayout;layout-addWidget(spinBox);layout-addWidget(lcd);window-setLayout(layout);window-show();returnapp.exec();这个程序中,没有自行设定组件的parent/child关系,也没有设定组件的大小、位置,而直接使用QHBoxLayout将组件加入,这会把QHBoxLayout及其管理的组件设成程序中window的子组件,并依QHBoxLayout版面配置策略自动水平配置组件,一个执行的的画面如下所示组件会自动填满窗口,如果您拉动窗
40、口,则当中的组件也会适当的变动大小:QHBoxLayout中组件的加入顺序,就是水平配置由左至右显示的顺序,至于QVBoxLayout的使用方式则与QHBoxLayout类似,再来一个例子改用QVBoxLayout来配置组件位置:#include#include#inelude#include#ineludeintmain(intargc,char*argv)QAppIicationapp(argc,argv);QWidget*window=newQWidget;window-setWindowTitle(QVBoxLayout);w
41、indow-resize(240,100);QLCDNumber*lcd=newQLCDNumber;QSlider*slider=newQSIider(Qt:Horizontal);slider-setRange(0,99);slider-setValue(0);QObject:connect(slider,SIGNAL(valueChanged(int),lcd,SLOT(display(int);QVBoxLayout*layout=newQVBoxLayout(window);layout-addWidget(lcd);layout-
42、addWidget(slider);window-show();returnapp.exec();程序中可以看到,在建立版面配置对象时,也可以直接指定要实施版面配置的对象。拉动时的画面如下所示:2.5绘图QPainter、QPaintEngine、QPaintDevice组成了Qt的绘图系统,QPainter提供低阶的绘图API,在内部使用QPaintEngine作为接口,在QPaintDevice进行绘图,只要是QPaintDevice的子类别,就可以建立QPainter在其上进行图形绘制,像是QWidget、QImage、QPict
43、ure、QPrinter等都是QPaintDevice的子类别。建立QPainter的方式如下,其中qPainterDevice是个指向QPaintDevice子类别的名称:QPainterpainter(qPainterDevice);若是图形组件,通常会重新定义QWidget的paintEvent(),当绘图装置(PaintDevice)需要重绘时,就会发出QPaintEvent并分派给这个方法来处理事件,例如组件出现、被覆盖又重现时,您也可以呼叫repaint()或update(),这也会执行paintEvent()。QPain
44、ter提供各种绘制图形的API,从基本的线绘制、方块、矩形、圆形、渐层到复杂的图片等,QPainter都有提供相对应的API,使用的方式参考QT帮助文档。在这边基本上要先了解的是,(Font)。QPainter的三个基本设定:笔触(Pen)、笔刷(Brush)与字型笔触在Qt中是以QPen作代表,用来于绘制线条或轮廓时决定样式,像是颜色、笔宽、转折、线条样式(实线、曲线、点状线之类的样式)等。笔刷在Qt中是以QBrush作代表,用来于绘制矩形、圆形、扇形等几何图形时决定样式,像是颜色、填满样式、渐层等。以下先看一个简单的程序,了解一下QPai
45、nter的几个API,以及QPenQBrush的使用:#include#include#includeclassPainterWidget:publicQWidgetprotected:/重新定义paintEvent()事件处理voidpaintEvent(QPaintEvent*);/实作事件处理voidPainterWidget:paintEvent(QPaintEvent*event)/建立QPainterQPainterpainter(this);/设定笔触为点状线painter.setPen(Qt:DotLine);/指定x
46、、y、width、height绘制线条painter.drawLine(10,10,100,10);/设定笔刷为蓝色、对角斜线样式painter.setBrush(QBrush(Qt:blue,Qt:BDiagPattern);/指定x、y、width、height绘制矩形painter.drawRect(10,20,100,50);/设定线形渐层,x1,y1为起点,x2,y2为终点QLinearGradientgradient(50,100,300,350);/设定渐层颜色过渡gradient.setColorAt(0.0,Q
47、t:white);gradient.setColorAt(0.2,Qt:green);gradient.setColorAt(1.0,Qt:black);/以渐层对象建立笔刷painter.setBrush(QBrush(gradient);/绘制圆角矩形painter.drawRoundRect(10,80,100,50);/绘制扇形,单位为1/16角度,下例为45度到300度painter.drawPie(10,150,100,50,45*16,300*16);/绘制图片painter.drawPixmap(150,10,QPix
48、map(caterpillar.jpg);/绘制填满图形painter.drawTiledPixmap(150,170,185,25,QPixmap(caterpillar_smaill.gif);intmain(intargc,char*argv)QApplicationapp(argc,argv);PainterWidgetpWidget;pWidget.setWindowTitle(QPainter);pWidget.resize(350,200);pWidget.show();returnapp.exec();2.6网络QHttp是Qt所提供有关网
54、QUrl的path()来取得路径讯息,如果路径讯息中没有包括文件名,就使用预设的index.html作为请求的对象及下载后存盘时的档名,要使用QHttp来请求档案时,必须使用setHost()来设定主机及连接端口信息,接着使用get()方法发岀请求,并告知下载的档案要到用哪个QFile来存盘。当QHttp所有请求处理完毕后,会发岀done()的Signal,程序中将之连接至HttpGet的done()来处理,处理完成之后,再发岀finished()的Signal。以下写个简单的程序来测试HttpGet:main.cpp#includevQCor
58、一个QPixmap画圆,显示两个同时进行的流程:*CircieThread.h在Qt中要实现线程功能,可以继承个线程,则建构这个自订的对象,并执行QThread类别,并重新定义start()方法。run()方法,之后要启动一#ifndefCIRCLETHREAD_H#defineCIRCLETHREADH#includeclassQLabel;classQPixmap;classCircleThread:publicQThreadQ_OBJECTpublic:CircleThread(QLabel*label,QPixmap*pixmap,int
59、y);protected:voidrun();private:QLabel*label;QPixmap*pixmap;inty;#endifCircleThread建构函式中,QPixmap是QLabel将显示的图片,而y值是画圆时的位置,CircleThread实作如下:*CircleThread.cpp#includeCircleThread.h#include#include#includeCircleThread:CircleThread(QLabel*label,QPixmap*pixmap,inty)this-label=lab
60、el;this-pixmap=pixmap;this-y=y;voidCircleThread:run()QPainterpainter(pixmap);for(inti=10;isetPixmap(*pixmap);QThread:msleep(500);在run()方法中,将在QPixmap上建构QPainter,然后依序画10个圆,接着将画好的QPixmap再次设置给QLabel,以重新在QLabel上显示新的绘制画面。QThread:msleep()可以令目前的线程暂停所设置的毫秒数。您可以撰写以下的程式来使用CircleThread:main.cpp#includevQApplicat