|
12.2 Qt/Embedded开发入门
12.2.1 Qt/Embedded介绍
1.架构
Qt/Embedded以原始Qt为基础,并做了许多出色的调整以适用于嵌入式环境。Qt/Embedded通过Qt API与Linux I/O设施直接交互,成为嵌入式Linux端口。同Qt/X11相比,Qt/Embedded很省内存,因为它不需要一个X服务器或是Xlib库,它在底层抛弃了X lib,采用framebuffer(帧缓冲)作为底层图形接口。同时,将外部输入设备抽象为keyboard和mouse输入事件。Qt/Embedde的应用程序可以直接写内核缓冲帧,这避免开发者使用繁琐的Xlib/Server系统。图12.1所示比较了Qt/Embedded与Qt/X11的架构区别。
使用单一的API进行跨平台的编程可以有很多好处。提供嵌入式设备和桌面计算机环境下应用的公司可以培训开发人员使用同一套工具开发包,这有利于开发人员之间共享开发经验与知识,也使得管理人员在分配开发人员到项目中的时候增加灵活性。更进一步来说,针对某个平台而开发的应用和组件也可以销售到Qt支持的其他平台上,从而以低廉的成本扩大产品的市场。
(1)窗口系统。
一个Qt/Embedded窗口系统包含了一个或多个进程,其中的一个进程可作为服务器。该服务进程会分配客户显示区域,以及产生鼠标和键盘事件。该服务进程还能够提供输入方法和一个用户接口给运行起来的客户应用程序。该服务进程其实就是一个有某些额外权限的客户进程。任何程序都可以在命令行上加上“-qws”的选项来把它作为一个服务器运行。
客户与服务器之间的通信使用共享内存的方法实现,通信量应该保持最小,例如客户进程直接访问帧缓冲来完成全部的绘制操作,而不会通过服务器,客户程序需要负责绘制它们自己的标题栏和其他式样。这就是Qt/Embedded库内部层次分明的处理过程。客户可以使用QCOP通道交换消息。服务进程简单的广播QCOP消息给所有监听指定通道的应用进程,接着应用进程可以把一个插槽连接到一个负责接收的信号上,从而对消息做出响应。消息的传递通常伴随着二进制数据的传输,这是通过一个QDataStream类的序列化过程来实现的,有关这个类的描述,请读者参考相关资料。
QProcess类提供了另外一种异步的进程间通信机制。它用于启动一个外部的程序并且通过写一个标准的输入和读取外部程序的标准输出和错误码来和它们通信。
(2)字体
Qt/Embedded支持4种不同的字体格式:True Type字体(TTF),Postscript Type1字体,位图发布字体(BDF)和Qt的预呈现(Pre-rendered)字体(QPF)。Qt还可以通过增加Qfont-
Factory的子类来支持其他字体,也可以支持以插件方式出现的反别名字体。
每个TTF或者TYPE1类型的字体首次在图形或者文本方式的环境下被使用时,这些字体的字形都会以指定的大小被预先呈现出来,呈现的结果会被缓冲。根据给定的字体尺寸(例如10或12点阵)预先呈现TTF或者TYPE1类型的字体文件并把结果以QPF的格式保存起来,这样可以节省内存和CPU的处理时间。QPF文件包含了一些必要的字体,这些字体可以通过makeqpf工具取得,或者通过运行程序时加上“-savefonts”选项获取。如果应用程序中使用到的字体都是QPF格式,那么Qt/Embedded将被重新配置,并排除对TTF和TYPE1类型的字体的编译,这样就可以减少Qt/Embedded的库的大小和存储字体的空间。例如一个10点阵大小的包含所有ASCII字符的QPF字体文件的大小为1300字节,这个文件可以直接从物理存储格式映射成为内存存储格式。
Qt/Embedded的字体通常包括Unicode字体的一部分子集,ASCII和Latin-1。一个完整的16点阵的Unicode字体的存储空间通常超过1MB,我们应尽可能存储一个字体的子集,而不是存储所有的字,例如在一个应用中,仅仅需要以Cappuccino字体、粗体的方式显示产品的名称,但是却有一个包含了全部字形的字体文件。
(3)输入设备及输入法。
Qt/Embedded 3.0支持几种鼠标协议:BusMouse、IntelliMouse,Microsoft和MouseMan.Qt/
Embedded还支持NECVr41XX和iPAQ的触摸屏。通过从QWSMouseHandler或者Qcalibra-
tedMouseHandler派生子类,开发人员可以让Qt/Embedded支持更多的客户指示设备。
Qt/Embedded支持标准的101键盘和Vr41XX按键,通过子类化QWSKeyboardHandler可以让Qt/Embedded支持更多的客户键盘和其他的非指示设备。
对于非拉丁语系字符(例如阿拉伯、中文、希伯来和日语)的输入法,需要把它写成过滤器的方式,并改变键盘的输入。输入法的作者应该对全部的Qt API的使用有完整的认识。在一个无键盘的设备上,输入法成了惟一的输入字符的手段。Qtopia提供了4种输入方法:笔迹识别器、图形化的标准键盘、Unicode键盘和基于字典方式提取的键盘。
(4)屏幕加速
通过子类化QScreen和QgfxRaster可以实现硬件加速,从而为屏幕操作带来好处。Troll-
tech提供了Mach64和Voodoo3视频卡的硬件加速的驱动例子,同时可以按照协议编写其他的驱动程序。
2.Qt的开发环境
Qt/Embedded的开发环境可以取代那些我们熟知的UNIX和Windows开发工具。它提供了几个跨平台的工具使得开发变得迅速和方便,尤其是它的图形设计器。UNIX下的开发者可以在PC机或者工作站使用虚拟缓冲帧,从而可以模仿一个和嵌入式设备的显示终端大小,像素相同的显示环境。
嵌入式设备的应用可以在安装了一个跨平台开发工具链的不同的平台上编译。最通常的做法是在一个UNIX系统上安装跨平台的带有libc库的GNU C++编译器和二进制工具。在开发的许多阶段,一个可替代的做法是使用Qt的桌面版本,例如通过Qt/X11或是Qt/Windows来进行开发。这样开发人员就可以使用他们熟悉的开发环境,例如微软公司的Visual C++或者Borland C++。在UNIX操作系统下,许多环境也是可用的,例如Kdevelop,它也支持交互式开发。
如果Qt/Embedded的应用是在UNIX平台下开发的话,那么它就可以在开发的机器上以一个独立的控制台或者虚拟缓冲帧的方式来运行,对于后者来说,其实是有一个X11的应用程序虚拟了一个缓冲帧。通过指定显示设备的宽度、高度和颜色深度,虚拟出来的缓冲帧将和物理的显示设备在每个像素上保持一致。这样每次调试应用时开发人员就不用总是刷新嵌入式设备的Flash存储空间,从而加速了应用的编译、链接和运行周期。运行Qt的虚拟缓冲帧工具的方法是在Linux的图形模式下运行以下命令:
qvfb (回车)
当Qt嵌入式的应用程序要把显示结果输出到虚拟缓冲帧时,我们在命令行运行这个程序,并在程序名后加上-qws的选项。例如:$> hello–qws。
3.Qt的支撑工具
Qt包含了许多支持嵌入式系统开发的工具,有两个最实用的工具是qmake和Qt designer(图形设计器)。
n qmake是一个为编译Qt/Embedded库和应用而提供的Makefile生成器。它能够根据一个工程文件(.pro)产生不同平台下的Makefile文件。qmake支持跨平台开发和影子生成,影子生成是指当工程的源代码共享给网络上的多台机器时,每台机器编译链接这个工程的代码将在不同的子路径下完成,这样就不会覆盖别人的编译链接生成的文件。qmake还易于在不同的配置之间切换。
n Qt图形设计器可以使开发者可视化地设计对话框而不需编写代码。使用Qt图形设计器的布局管理可以生成能平滑改变尺寸的对话框。
qmake和Qt图形设计器是完全集成在一起的。
12.2.2 Qt/Embedded信号和插槽机制
1.机制概述
信号和插槽机制是Qt的核心机制,要精通Qt编程就必须对信号和插槽有所了解。信号和插槽是一种高级接口,应用于对象之间的通信,它是Qt的核心特性,也是Qt区别于其他工具包的重要地方。信号和插槽是Qt自行定义的一种通信机制,它独立于标准的C/C++语言,因此要正确地处理信号和插槽,必须借助一个称为moc(Meta Object Compiler)的Qt工具,该工具是一个C++预处理程序,它为高层次的事件处理自动生成所需要的附加代码。
所谓图形用户接口的应用就是要对用户的动作做出响应。例如,当用户单击了一个菜单项或是工具栏的按钮时,应用程序会执行某些代码。大部分情况下,是希望不同类型的对象之间能够进行通信。程序员必须把事件和相关代码联系起来,这样才能对事件做出响应。以前的工具开发包使用的事件响应机制是易崩溃的,不够健壮的,同时也不是面向对象的。
以前,当使用回调函数机制把某段响应代码和一个按钮的动作相关联时,通常把那段响应代码写成一个函数,然后把这个函数的地址指针传给按钮,当那个按钮被单击时,这个函数就会被执行。对于这种方式,以前的开发包不能够确保回调函数被执行时所传递进来的函数参数就是正确的类型,因此容易造成进程崩溃。另外一个问题是,回调这种方式紧紧地绑定了图形用户接口的功能元素,因而很难进行独立的开发。
信号与插槽机制是不同的。它是一种强有力的对象间通信机制,完全可以取代原始的回调和消息映射机制。在Qt中信号和插槽取代了上述这些凌乱的函数指针,使得用户编写这些通信程序更为简洁明了。信号和插槽能携带任意数量和任意类型的参数,它们是类型完全安全的,因此不会像回调函数那样产生core dumps。
图12.2 对象间信号与插槽的关系
所有从QObject或其子类(例如Qwidget)派生的类都能够包含信号和插槽。当对象改变状态时,信号就由该对象发射(emit)出去了,这就是对象所要做的全部工作,它不知道另一端是谁在接收这个信号。这就是真正的信息封装,它确保对象被当作一个真正的软件组件来使用。插槽用于接收信号,但它们是普通的对象成员函数。一个插槽并不知道是否有任何信号与自己相连接。而且,对象并不了解具体的通信机制。
用户可以将很多信号与单个插槽进行连接,也可以将单个信号与很多插槽进行连接,甚至将一个信号与另外一个信号相连接也是可能的,这时无论第一个信号什么时候发射,系统都将立刻发射第二个信号。总之,信号与插槽构造了一个强大的部件编程机制。
2.信号与插槽实现实例
(1)信号。
当某个信号对其客户或所有者内部状态发生改变时,信号就被一个对象发射。只有定义了这个信号的类及其派生类才能够发射这个信号。当一个信号被发射时,与其相关联的插槽将被立刻执行,就像一个正常的函数调用一样。信号-插槽机制完全独立于任何GUI事件循环。只有当所有的槽返回以后发射函数(emit)才返回。如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,这些槽将会一个接一个地执行,但是它们执行的顺序将会是随机的、不确定的,用户不能人为地指定哪个先执行、哪个后执行。
Qt的signals关键字指出进入了信号声明区,随后即可声明自己的信号。例如,下面定义了3个信号:
signals:
void mySignal();
void mySignal(int x);
void mySignalParam(int x,int y);
在上面的定义中,signals是Qt的关键字,而非C/C++的。接下来的一行void mySignal()定义了信号mySignal,这个信号没有携带参数;接下来的一行void mySignal(int x)定义了重名信号mySignal,但是它携带一个整形参数,这有点类似于C++中的虚函数。从形式上讲信号的声明与普通的C++函数是一样的,但是信号却没有函数体定义。另外,信号的返回类型都是void。信号由moc自动产生,它们不应该在.cpp文件中实现。
(2)插槽。
插槽是普通的C++成员函数,可以被正常调用,它们惟一的特殊性就是很多信号可以与其相关联。当与其关联的信号被发射时,这个插槽就会被调用。插槽可以有参数,但插槽的参数不能有缺省值。
插槽是普通的成员函数,因此与其他的函数一样,它们也有存取权限。插槽的存取权限决定了谁能够与其相关联。同普通的C++成员函数一样,插槽函数也分为3种类型,即public slots、private slots和protected slots。
n public slots:在这个区内声明的槽意味着任何对象都可将信号与之相连接。这对于组件编程非常有用,用户可以创建彼此互不了解的对象,将它们的信号与槽进行连接以便信息能够正确地传递。
n protected slots:在这个区内声明的槽意味着当前类及其子类可以将信号与之相连接。这适用于那些槽,它们是类实现的一部分,但是其界面接口却面向外部。
n private slots:在这个区内声明的槽意味着只有类自己可以将信号与之相连接。这适用于联系非常紧密的类。
插槽也能够被声明为虚函数,这也是非常有用的。插槽的声明也是在头文件中进行的。例如,下面声明了3个插槽:
public slots:
void mySlot();
void mySlot(int x);
void mySignalParam(int x,int y);
(3)信号与插槽关联。
通过调用QObject对象的connect()函数可以将某个对象的信号与另外一个对象的插槽函数或信号相关联,当发射者发射信号时,接收者的槽函数或信号将被调用。
该函数的定义如下所示:
bool QObject::connect (const QObject * sender, const char * signal,const QObject * receiver, const char * member) [static]
这个函数的作用就是将发射者sender对象中的信号signal与接收者receiver中的member插槽函数联系起来。当指定信号signal时必须使用Qt的宏SIGNAL(),当指定插槽函数时必须使用宏SLOT()。如果发射者与接收者属于同一个对象的话,那么在connect()调用中接收者参数可以省略。
n 信号与插槽相关联。
下例定义了两个对象:标签对象label和滚动条对象scroll,并将valueChanged()信号与标签对象的setNum()插槽函数相关联,另外信号还携带了一个整型参数,这样标签总是显示滚动条所处位置的值。
QLabel *label = new QLabel;
QScrollBar *scroll = new QScrollBar;
QObject::connect(scroll, SIGNAL(valueChanged(int)),label, SLOT(setNum(int)));
n 信号与信号相关联。
在下面的构造函数中,MyWidget创建了一个私有的按钮aButton,按钮的单击事件产生的信号clicked()与另外一个信号aSignal()进行关联。这样,当信号clicked()被发射时,信号aSignal()也接着被发射。如下所示:
class MyWidget : public QWidget
{
public:
MyWidget();
...
signals:
void aSignal();
...
private:
...
QPushButton *aButton;
};
MyWidget::MyWidget()
{
aButton = new QPushButton(this);
connect(aButton, SIGNAL(clicked()), SIGNAL(aSignal()));
}
(4)解除信号与插槽关联。
当信号与槽没有必要继续保持关联时,用户可以使用disconnect()函数来断开连接。其定义如下所示:
bool QObject::disconnect (const QObject * sender, const char * signal,const Object * receiver, const char * member) [static]
这个函数断开发射者中的信号与接收者中的槽函数之间的关联。
有3种情况必须使用disconnect()函数。
n 断开与某个对象相关联的任何对象。
当用户在某个对象中定义了一个或者多个信号,这些信号与另外若干个对象中的槽相关联,如果想要切断这些关联的话,就可以利用这个方法,非常简洁。如下所示:
disconnect(myObject, 0, 0, 0)
或者
myObject->disconnect()
n 断开与某个特定信号的任何关联。
这种情况是非常常见的,其典型用法如下所示:
disconnect(myObject, SIGNAL(mySignal()), 0, 0)
或者
myObject->disconnect(SIGNAL(mySignal()))
n 断开两个对象之间的关联。
这也是非常常用的情况,如下所示:
disconnect(myObject, 0, myReceiver, 0)
或者
myObject->disconnect(myReceiver)
注意
| 在disconnect()函数中0可以用作一个通配符,分别表示任何信号、任何接收对象、接收对象中的任何槽函数。但是发射者sender不能为0,其他3个参数的值可以等于0。
|
12.2.3 搭建Qt/Embedded开发环境
一般来说,用Qt/Embedded开发的应用程序最终会发布到安装有嵌入式Linux操作系统的小型设备上,所以使用装有Linux操作系统的PC机或者工作站来完成Qt/Embedded开发当然是最理想的环境,此外Qt/Embedded也可以安装在UNIX或Windows系统上。这里就以在Linux操作系统中安装为例进行介绍。
这里需要有3个软件安装包:tmake工具安装包、Qt/Embedded安装包和Qt的X11版的安装包。
n tmake1.11或更高版本:生成Qt/Embedded应用工程的Makefile文件。
n Qt/Embedded:Qt/Embedded安装包。
n Qt 2.3.2 for X11:Qt的X11版的安装包,产生X11开发环境所需要的两个工具。
注意
| 这些软件安装包都有许多不同的版本,由于版本的不同会导致这些软件在使用时可能引起的冲突,为此必须依照一定的安装原则,Qt/Embedded安装包的版本必须比Qt for X11的安装包的版本新,这是因为Qt for X11的安装包中的两个工具uic和designer产生的源文件会和Qt/Embedded的库一起被编译链接,因此要本着“向前兼容”的原则,Qt for X11 的版本应比Qt/Embedded的版本旧。
|
1.安装tmake
用户使用普通的解压缩即可,注意要将路径添加到全局变量中去,如下所示:
tar zxvf tmake-1.11.tar.gz
export TMAKEDIR=$PWD/tmake-1.11
export TMAKEPATH=$TMAKEDIR/lib/qws/linux-x86-g++
export PATH=$TMAKEDIR/bin:$PATH
2.安装Qt/Embedded 2.3.7
这里使用常见的解压命令及安装命令即可,要注意这里的路径与不同的系统有关,读者要根据实际情况进行修改。另外,这里的configure命令带有参数“-qconfig –qvfb –depths 4816,32”分别为指定Qt嵌入式开发包生成虚拟缓冲帧工具qvfb,并支持4、8、16、32 位的显示颜色深度。另外读者也可以在configure的参数中添加“-system”、“-jpeg”或“gif”命令,使Qt/Embedded平台能支持jpeg、gif格式的图形。
Qt/Embedded开发包有5种编译范围的选项,使用这些选项可控制Qt生成的库文件的大小。如命令make sub-src指定按精简方式编译开发包,也就是说有些Qt类未被编译。其他编译选项的具体用法可通过“./configure–help”命令查看。精简方式的安装步骤如下所示:
tar zxvf qt-embedded-2.3.7.tar.gz
cd qt-2.3.7
export QTDIR=$PWD
export QTEDIR=$QTDIR
export PATH=$QTDIR/bin:$PATH
export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH
./configure -qconfig local-qvfb -depths 4,8,16,32
make sub-src
3.安装Qt/X11 2.3.2
与上一步类似,用户也可以在configure后添加一定的参数,如“-no-opengl”或“-no-xfs”,可以键入命令“./configure –help”来获得一些帮助信息。
tar xfz qt-x11-2.3.2.tar.gz
cd qt-2.3.2
export QTDIR=$PWD
export PATH=$QTDIR/bin:$PATH
export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH
./configure -no-opengl
make
make -C tools/qvfb
mv tools/qvfb/qvfb bin
cp bin/uic $QTEDIR/bin
12.2.4 Qt/Embedded窗口部件
Qt提供了一整套的窗口部件。它们组合起来可用于创建用户界面的可视元素。按钮、菜单、滚动条、消息框和应用程序窗口都是窗口部件的实例。因为所有的窗口部件既是控件又是容器,因此Qt的窗口部件不能任意地分为控件和容器。通过子类化已存在的Qt部件或少数时候必要的全新创建,自定义的窗口部件能很容易地创建出来。
窗口部件是QWidget或其子类的实例,用户自定义的窗口通过子类化得到,如图12.3所示。
图12.3 源自QWidget的类层次结构
一个窗口部件可包含任意数量的子部件。子部件在父部件的区域内显示。没有父部件的部件是顶级部件(比如一个窗口),通常在桌面的任务栏上有它们的入口。Qt不在窗口部件上施加任何限制。任何部件都可以是顶级部件,任何部件都可以是其他部件的子部件。通过自动或手动(如果你喜欢)使用布局管理器可以设定子部件在父部件区域中的位置。如果父部件被停用、隐藏或删除,则同样的动作会应用于它的所有子部件。
1.Hello窗口实例
下面是一个显示“Hello Qt/Embedded!”的程序的完整代码:
#include <qapplication.h>
#include <qlabel.h>
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QLabel *hello=new QLabel
("<font color=blue>Hello""<i>Qt Embedded!</i></font>",0);
app.setMainWidget(hello);
hello->show();
return app.exec();
}
2.常见通用窗口组合
Qt中还有一些常见的通用窗口,它们使用了Windows风格显示,图12.5、12.6、12.7、12.8分别描述了常见的一些通用窗口的组合使用。图12.4是该Hello窗口的运行效果图:
图12.4 Hello窗口运行效果图 图12.5 使用QHBox排列一个标签和一个按钮
图12.6 使用了QButtonGroup的两个单选框和两个复选框 图12.7 QGroupBox组合图示
图12.8使用了QGroupBox进行排列的日期类QDateTimeEdit、一个行编辑框类QLine-
Edit、一个文本编辑类QTextEdit和一个组合框类QComboBox。
图12.9是以QGrid排列的一个QDial、一个QProgressBar、一个QSpinBox、一个QScrollBar、一个QLCDNumber和一个QSlider。
图12.10是以QGrid排列的一个QIconView、一个QListView、一个QListBox和一个QTable。
图12.8 QGrid组合图示1 图12.9 QGrid组合图示2 图12.10 钟表部件图示
3.自定义窗口
开发者可以通过子类化QWidget或它的一个子类创建他们自己的部件或对话框。为了举例说明子类化,下面提供了数字钟部件的完整代码。
钟表部件是一个能显示当前时间并自动更新的LCD。一个冒号分隔符随秒数的流逝而闪烁,如图12.10所示。
Clock从QLCDNumber部件继承了LCD功能。它有一个典型部件类所拥有的典型构造函数,带有可选的parent和name参数(如果设置了name参数,测试和调试会更容易)。系统有规律地调用从QObject继承的timerEvent()函数。
它在clock.h中定义如下所示:
#include <qlcdnumber.h>
class Clock:public QLCDNumber
{
public:
Clock(QWidget *parent=0,const char *name=0);
protected:
void timerEvent(QTimerEvent *event);
private:
void showTime();
bool showingColon;
};
构造函数showTime()是用当前时间初始化钟表,并且告诉系统每1000ms调用一次timerEvent()来刷新LCD的显示。在showTime()中,通过调用QLCDNumber::display()来显示当前时间。每次调用showTime()来让冒号闪烁时,冒号就被空白代替。
clock.cpp的源码如下所示:
#include <qdatetime.h>
#include "clock.h"
Clock::Clock(QWidget *parent,const char *name)
:QLCDNumber(parent,name),showingColon(true)
{
showTime();
startTimer(1000);
}
void Clock::timerEvent(QTimerEvent *)
{
showTime();
}
void Clock::showTime()
{
QString timer=QTime::currentTime().toString().left(5);
if (!showingColon)
{
<p> time[2]=& |
-
522c13b5eb73f-thumb.png
(9.79 KB, 下载次数: 18)
Qt图形编程基础之:Qt/Embedded开发入门
-
522c13b3e7863-thumb.png
(1.56 KB, 下载次数: 30)
Qt图形编程基础之:Qt/Embedded开发入门
-
522c13b42fee5-thumb.png
(2.08 KB, 下载次数: 18)
Qt图形编程基础之:Qt/Embedded开发入门
-
522c13b467eea-thumb.png
(1.34 KB, 下载次数: 24)
Qt图形编程基础之:Qt/Embedded开发入门
-
522c13b50a3e5-thumb.png
(19.42 KB, 下载次数: 16)
Qt图形编程基础之:Qt/Embedded开发入门
-
522c13b5628a4-thumb.png
(19.98 KB, 下载次数: 24)
Qt图形编程基础之:Qt/Embedded开发入门
-
522c13b755f0c-thumb.png
(55.67 KB, 下载次数: 19)
Qt图形编程基础之:Qt/Embedded开发入门
-
522c13b84af4c-thumb.png
(9.7 KB, 下载次数: 27)
Qt图形编程基础之:Qt/Embedded开发入门
-
522c13b6b3af9-thumb.png
(12.09 KB, 下载次数: 21)
Qt图形编程基础之:Qt/Embedded开发入门
-
522c13b825671-thumb.png
(9.05 KB, 下载次数: 17)
Qt图形编程基础之:Qt/Embedded开发入门
|