第9章 嵌入式Linux用户图形界面编程
9.1 Linux图形开发基础 本节在介绍图形用户界面一般构架的基础上,详细讨论嵌入式图形用户界面开发常见的底层支持库和高级函数库。这些内容都是Linux图形界面开发的基础知识。
GUI的一般架构 图形用户界面GUI(Graphics User Interface)是迄今为止计算机系统中最为成熟的人机交互技术。一个好的图形用户界面的设计不仅要考虑到具体硬件环境的限制,而且还要考虑到用户的喜好等。 由于图形用户界面的引入主要是从用户角度出发的,因此用户自身的主观感受对图形用户界面的评价占了很大比重,比如,易用性、直观性、友好性,等等。另外,从纯技术的角度看,仍然也会有一些标准需要考虑,比如,跨平台性、对硬件的要求等。在嵌入式系统开发和应用中,我们所考虑的问题主要集中在图形用户界面对硬件的要求,以及对硬件类型的敏感性方面,在提供给用户的最终界面方面只是要求简单实用就够了。 虽然不同的GUI系统因为其使用场合或服务目的不同,具体实现互有差异,但是总结起来,一般在逻辑上可以分为以下几个模块:底层I/O设备驱动(显示设备驱动、鼠标驱动、键盘驱动等)、基本图形引擎(画点、画线、区域填充)、消息驱动机制、高层图形引擎(画窗口、画按钮),以及GUI应用程序接口(API)。
底层I/O设备驱动,例如,显示驱动、鼠标驱动、键盘驱动等构成了GUI的硬件基础。由于此类设备的多样性,需要对其进行抽象,并提供给上层一个统一的调用接口;而各类设备驱动则自成一体,形成一个GUI设备管理模块。当然,从操作系统内核的角度看,GUI设备管理模块则是操作系统内核的I/O设备管理的一部分。 基本图形引擎模块完成一些基本的图形操作,如画点、画线、区域填充等。它直接和底层I/O设备打交道,同时,多线程或者多进程机制的引入也为基本图形模块的实现提供了很大的灵活性。 消息不仅是底层I/O硬件和GUI上层进行交互的基础,同时也是各类GUI组件如窗口、按钮等相互作用的重要途径。一个GUI系统的消息驱动机制的效率对该系统的性能,尤其是对响应速度等性能的影响很大。 高级图形引擎模块则在消息传递机制和基本图形引擎的基础上完成对诸如窗口、按钮等的管理。 GUI API则是提供给最终程序员的编程接口,使得他们能够利用GUI体系所提供的GUI高级功能快速开发GUI应用程序。 另外,为了实现GUI系统,一般需要用到操作系统内核提供的功能,如线程机制、进程管理。当然,不可避免地需要用到内存管理、I/O设备管理,甚至还可能有文件管理。 从用户的观点来看,图形用户界面(GUI)是系统的一个至关重要的方面:由于用户通过GUI与系统进行交互,所以GUI应该易于使用并且非常可靠。此外,它不能占用太多的内存,以便在内存受限的微型嵌入式设备上无缝执行。由此可见,它应该是轻量级的,并且能够快速装入。
嵌入式GUI要求简单、直观、可靠、占用资源小且反应快速,以适应系统硬件资源有限的条件。另外,由于嵌入式系统硬件本身的特殊性,嵌入式GUI应具备高度可移植性与可裁减性,以适应不同的硬件条件和使用需求。总体来讲,嵌入式GUI具备以下特点: 体积小; 运行时耗用系统资源小; 上层接口与硬件无关,高度可移植; 高可靠性; 在某些应用场合应具备实时性。 一个能够移植到多种硬件平台上的嵌入式GUI系统,应至少抽象出两类设备:基于图形显示设备(如VGA卡)的图形抽象层GAL(Graphic Abstract Layer)和基于输入设备(如键盘,触摸层等)的输入抽象层IAL(Input Abstract Layer)。GAL层完成系统对具体的显示硬件设备的操作,最大限度地隐藏各种不同硬件的技术实现细节,为程序开发人员提供统一的图形编程接口。IAL层则需要实现对于各类不同输入设备的控制操作,提供统一的调用接口,如图9.2所示。GAL层与IAL层设计概念的引入,可以显著提高嵌入式GUI的可移植性。
嵌入式GUI底层支持库 1.X Window
2.FrameBuffer FrameBuffer是出现在2.2.xx内核中的一种驱动程序接口。由于Linux工作在保护模式,所以用户态进程无法像DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。在使用Framebuffer时,Linux是将显卡置于图形模式下的。Framebuffer就是模仿显卡的功能,相当于抽象的显卡硬件结构,实现了通过Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反映在屏幕上。这种操作是抽象的、统一的。用户不必关心物理显存的位置、换页机制等具体细节,因为这些都是由Framebuffer设备驱动来完成的。 FrameBuffer设备还提供了若干ioctl命令,通过这些命令,可以获得显示设备的一些固定信息(例如显示内存大小)、与显示模式相关的可变信息(例如,分辨率、像素结构、每扫描线的字节宽度),以及伪彩色模式下的调色板信息等。通过FrameBuffer,还可以获得当前内核所支持的加速显示卡的类型(通过固定信息得到),这种类型通常是和特定显示芯片相关的。例如,目前最新的内核(2.4.9)中,就包含有对S3、Matrox、nVidia、3Dfx等流行显示芯片的加速支持。在获得了加速芯片类型之后,应用程序可以将PCI设备的内存I/O(memio)映射到进程的地址空间。这些memio一般是用来控制显示卡的寄存器,通过对这些寄存器的操作,应用程序可以控制特定显卡的加速功能。但由于Framebuffer本身不具备任何运算数据的能力,只是一个提供显示内存和显示芯片寄存器从物理内存映射到进程地址空间中的设备。所以,对于应用程序而言,如果希望在FrameBuffer之上进行图形编程,还需要自己动手完成其他许多工作。举个例子来讲,FrameBuffer就像一张画布,使用什么样的画笔、如何画画,还需要用户自己动手完成。在这种机制下,尽管Framebuffer需要真正的显卡驱动的支持,但由于所有显示任务都由CPU完成,因此CPU负担很重。
3.SVGALib SVGALib是Linux系统中最早出现的非X图形支持库,是Linux下的VGA驱动函数库。虽然它的品质有点低,支援显卡种类也不多,但是有许多的游戏及程序都是用它来做开发,可以算是非官方的标准了。这个库从最初对标准VGA兼容芯片的支持开始,已经发展到对老式SVGA芯片,以及现今流行的高级视频芯片的支持。它为用户提供了在控制台上进行图形编程的接口,使用户可以在PC兼容系统上方便地获得图形支持。但该系统也存在一些不足: SVGALib从最初的vgalib发展而来,保留了老系统的许多接口,而这些接口却不能良好地适应新显示芯片的图形能力。 未能较好地隐藏硬件细节。许多操作,不能自动使用显示芯片的加速能力支持。 可移植性差。SVGALib目前只能运行在x86平台上,除Alpha平台,对其他平台的支持能力较差。 SVGALib作为一个老的图形支持库,目前的应用范围越来越小,尤其在Linux内核增加了FrameBuffer驱动支持之后,有逐渐被其他图形库替代的趋势。 4.LibGGI LibGGI是一个跨平台的绘图库,可以建立一个一般性的图形接口,这个抽象接口连同相关的输入(鼠标、键盘、游戏杆等)接口一起,可以方便地运行在X Window、SVGALib、FrameBuffer等之上。建立在LibGGI之上的应用程序,不经重新编译,就可以在上述这些底层图形接口上运行。 在Linux上,LibGGI是通过调用FrameBuffer或SVGALib来完成图形操作的,可能速度比较慢。但在某些不支持FrameBuffer或vga的系统上,采用LibGGI仍然是一种不错的选择。
嵌入式GUI高级函数库 1.Xlib及其他相关函数库 在X Window系统中进行图形编程时,可以选择直接使用Xlib。Xlib实际上是对底层 X 协议的封装,可通过该函数库进行一般的图形输出。如果用户的X Server支持DGA,则可以通过DGA扩展直接访问显示设备,从而获得加速支持。 2.SDL(Simple DirectMedia Layer) SDL是一个跨平台的多媒体游戏支持库。其中包含了对图形、声音、游戏杆、线程等的支持,目前可以运行在许多平台上。SDL支持图形的功能强大,高级图形处理能力尤为突出,可以实现Alpha混合、透明处理、YUV覆盖、Gamma校正等。在SDL环境中能够非常方便地加载支持OpenGL的Mesa库,从而提供对二维和三维图形的支持。 3.Allegro Allegro是一个专门为x86平台设计的游戏图形库。最初的Allegro运行在DOS环境下,目前也可运行在Linux FrameBuffe控制台、Linux SVGALib、X Window等系统上。Allegro提供了丰富的图形功能,包括矩形填充和样条曲线生成等,而且具有较好的三维图形显示能力。由于Allegro的许多关键代码是采用汇编编写的,所以该函数库具有运行速度快、占用资源少的特点。 4.Mesa3D Mesa3D是一个兼容OpenGL规范的开放源码函数库,是目前Linux上提供专业三维图形支持的惟一选择。Mesa3D也是一个跨平台的函数库,能够运行在X Window、X Window with DGA、BeOS、Linux SVGALib等平台上。 5.DirectFB DirectFB是特别为Linux FrameBuffer加速的一个图形库,正在尝试建立一个兼容GTK(GIMP Toolkit)的嵌入式GUI系统。
9.2 嵌入式Linux图形用户界面简介 Qt/Embedded Qt/Embedded(简称QtE)是一个专门为嵌入式系统设计的图形用户界面的工具包,由挪威Trolltech公司开发,最初作为跨平台的开发工具用于Linux台式机。它支持各种有UNIX 和Microsoft Windows特点的系统平台。Qt/Embedded以原始Qt为基础,许多基于Qt的X Window程序可以非常方便地移植到Qt/Embedded上,因此,自从Qt/Embedded以GPL条款形式发布以来,就有大量的嵌入式Linux开发商转到了Qt/Embedded系统上,比如,韩国的Mizi公司。 Qt/Embedded通过Qt API与Linux I/O设备直接交互,是面向对象编程的理想环境。面向对象的体系结构使代码结构化、可重用并且运行快速,与其他GUI相比,Qt GUI非常快,没有分层,这使得Qt/Embedded成为基于Qt的程序的最紧凑环境。 Qt/Embedded延续了Qt在X上的强大功能,在底层摒弃了X lib,仅采用FrameBuffer作为底层图形接口。同时,将外部输入设备抽象为keyboard和mouse输入事件,底层接口支持键盘、GPM鼠标、触摸屏,以及用户自定义的设备等。 Qt/Embedded类库完全采用C++封装,丰富的控件资源和较好的可移植性是Qt/Embedded最为突出的优点。它的类库接口完全兼容于同版本的Qt-X11,使用X下的开发工具可以直接开发基于Qt/Embedded的应用程序GUI。
Microwindows/Nano-X Microwindows是Century Software的开放源代码项目,设计用于带小型显示单元的微型设备。它有许多针对现代图形视窗环境的功能部件,可被多种平台支持。 Microwindows体系结构是基于客户机/服务器(Client/Server)分层设计的。最底层是屏幕和输入设备,通过驱动程序来与实际硬件交互;中间层提供底层硬件的抽象接口,进行窗口管理;最上层支持两种API:第一种支持Win32/WinCE API,称为Microwindows,另一种支持的API与GDK(GTK+ Drawing Kit)非常相似,用在Linux上称为Nano-X,用于占用资源少的应用程序。 Microwindows支持1、2、4和8bpp(每像素的位数)的衬底显示,以及8、16、24和32 bpp的真彩色显示。Microwindows提供了相对完善的图形功能和一些高级的特性,如Alpha混合、三维支持和TrueType字体支持等。该系统为了提高运行速度,也改进了基于Socket套接字的X实现模式,采用基于消息机制的Server/Client传输机制。Microwindows还支持速度更快的帧缓冲区。 Nano-X服务器占用的存储器资源大约在100KB~150KB。原始Nano-X应用程序的平均大小在30KB~60KB。与Xlib的实现不同,Nano-X仍在每个客户机上同步运行,这意味着一旦发送了客户机请求包,服务器在为另一个客户机提供服务之前一直等待,直到整个包都到达为止。这使服务器代码非常简单,而运行的速度仍非常快。
MiniGUI MiniGUI是自由软件项目(遵循LGPL条款发布),其目标是为基于Linux的实时嵌入式系统提供一个轻量级的图形用户界面支持系统。MiniGUI为实时嵌入式操作系统提供了完善的图形及图形用户界面支持。可移植性设计使得它不论在哪个硬件平台、哪种操作系统上运行,均能为上层应用程序提供一致的应用程序编程接口(API)。 在MiniGUI几年的发展过程中,有许多值得一提的技术创新点包括: 图形抽象层。图形抽象层对顶层API基本没有影响,但大大方便了MiniGUI应用程序的移植、调试等工作。目前包含三个图形引擎,SVGALib、LibGGI,以及直接基于Linux FrameBuffer的Native Engine。 多字体和多字符集支持。这部分通过设备上下文(DC)的逻辑字体(LOGFONT)实现,不管是字体类型还是字符集,都可以非常方便地进行扩充。应用程序在启动时,可切换系统字符集,比如GB、BIG5、EUCKR、UJIS。MiniGUI的这种字符集支持,这种实现更适合于嵌入式系统。 两个不同架构的版本。最初的MiniGUI运行在PThread库之上,这个版本适合于功能单一的嵌入式系统,但存在系统健壮性不够的缺点。在0.9.98版本中,引入了MiniGUI-Lite版本,这个版本在提高系统健壮性的同时,通过一系列创新途径,避免了传统C/S结构的弱点,为功能复杂的嵌入式系统提供了一个高效、稳定的GUI系统。 在MiniGUI 1.1.0版本的开发中,参照SDL和Allegro的图形部分,重新设计了图形抽象层,增强了图形功能,同时增强了MiniGUI-Lite版本的某些特性。增强的MiniGUI-Lite支持层的设计,同一层可以容纳多个同时显示的客户程序,并平铺在屏幕上显示。新的GAL支持硬件加速能力,并能够充分使用显示内存;新GAL之上的新GDI接口得到进一步增强,可以支持Alpha混合、透明位块传输、光栅操作、YUV覆盖、Gamma校正,以及高级图形功能(椭圆、多边形、样条曲线),等等。
OpenGUI OpenGUI在Linux系统上已经应用很长时间了。OpenGUI基于一个用汇编实现的x86图形内核,提供了一个快速、32位、高层的C/C++图形接口。OpenGUI也是一个公开源码(LGPL)项目,最初的名字叫FastGL,只支持256色的线性显存模式。目前,OpenGUI也支持其他显示模式,并且支持多种操作系统平台,比如,MS-DOS、QNX和Linux,等等,不过目前只支持x86硬件平台。 OpenGUI也分为三层:最低层是由汇编编写的快速图形引擎;中间层提供了图形绘制API,包括线条、矩形、圆弧等,并且兼容于Borland的BGI API;第三层用C++编写,提供了完整的GUI对象集。OpenGUI提供了消息驱动的API和BMP文件格式支持,OpenGUI比较适合基于x86平台的实时系统,可移植性稍差,目前的发展也基本停滞。
9.3Qt/Embedded嵌入式图形开发基础 开发Qt的挪威TrollTech公司,主要开发两种产品,提供给嵌入式Linux开发的应用程序平台和跨平台应用程序界面框架,Qtopia和Qt分别是其中具有代表性的两个。 Qtopia是第一个面向嵌入式Linux的全方位应用程序开发平台,已经应用于众多基于Linux的PDA(个人数字助理)设备和智能电话。Qtopia环境包括一个程序发布器和一套支持应用程序开发的程序和库,它还有灵活的输入处理器,包括手写识别、选择板和虚拟键盘,可以很容易地编写新的输入法。Qtopia是夏普公司的Zaurus PDA(如图9.5所示)使用的标准环境。 Qt是一个跨平台C++应用程序开发框架,可以编写单一代码的应用程序,并可在Windows,Linux,UNIX,Mac OS X和嵌入式Linux等不同平台上进行本地化运行,是开放源代码KDE桌面环境的基础。目前,Qt已被成功应用于商业应用程序的开发。 Qt作为Linux桌面环境KDE的基础,与Windows下的MFC类似,Qt的类库等价于MFC的开发库。但是Qt的类库封装了适应不同操作系统的访问细节,支持跨平台的类库,这种优点使得Qt的应用非常广泛。目前Qt可以支持现有的多种操作系统平台,主要有: MS/Windows 95、Windows 98、WindowsNT 4.0、Windows 2000、Windows XP; Unix/X11 Linux、Sun Solaris、HP-UX、Compaq True64Unix、IBM AIX、SGI IRIX和很多其他X11平台; Macintoshi Mac OS X; 带FrameBuffer的嵌入式Linux平台。
Qt/Embedded概述 Qt/Embedded是一个为嵌入式应用定制的用于多种平台图形界面程序开发的C++工具包,以原始Qt为基础,做了许多适用于嵌入式环境的调整,是面向对象编程的理想环境。Qt/Embedded通过Qt API与Linux I/O设备直接交互,面向对象的体系结构使代码结构化、可重用并且运行快速。与其他GUI相比,Qt GUI非常快,没有分层结构,这使得Qt/Embedded成为运行基于Qt的程序的最紧凑环境。Qt/Embedded为带有轻量级窗口系统的嵌入式设备提供了标准的Qt API。面向对象的设计思想,使得它能很好地支持键盘、鼠标和图形加速卡这样的附加设备。通过使用Qt/Embedded,开发者可以感受到在Qt/X11、Qt/Windows和Qt/Mac等不同的版本下使用相同的API编程所带来的便利。 1.Qt的体系结构 Qt的功能建立在所支持平台底层的API上,这使得Qt灵活而高效。Qt是一个“模拟的”多平台工具包,所有窗口部件都由Qt绘制,可以通过重新实现其虚函数来扩展或自定义部件功能。Qt为所支持平台提供底层API,这不同于传统分层的跨平台工具包(如Windows中的MFC)。 Qt是受专业支持的,它可以利用了以下平台:Microsoft Windows、X11、Mac OS X和嵌入式Linux。它使用单一的源代码树,只需简单的在目标平台上重编译就可以把Qt程序转换成可执行程序。Qt/Embedded与Qt/X11的Linux版本的比较如图9.6所示。
Qt/X11使用Xlib与X服务器直接通信,而不使用Xt(X Toolkit)、Motif、Athena或其他工具包。Qt能够自动适应用户的窗口管理器或桌面环境,并且拥有Motif、SGI、CDE、GNOME和KDE的外观。这与大多数其他的UNIX工具包形成鲜明对比,那些工具包常将用户锁定为它们自己的外观。 Qt/Embedded提供了完整的窗口环境,可以直接写入Linux的帧缓存。Qt/Embedded去掉了对X服务器的依赖,而且运行起来比基于X11的Linux设备更快、更省内存。 虽然Qt是一个多平台工具包,但是客户会发现它比个别平台上的工具包更易学、也更有用。许多客户用Qt进行单一平台的开发,因为他们喜欢Qt完全面向对象的做法。
2.窗口系统 一个Qt/Embedded窗口系统包含了一个或多个进程,其中的一个进程可作为服务器,这个服务进程会分配客户显示区域,以及产生鼠标和键盘事件。同时,这个服务进程还能为已经运行的客户程序提供输入方法和用户接口,这个服务进程其实也就是一个有某些额外权限的客户进程。任何程序都可以在命令行上加上“-qws”的选项来把它作为一个服务器运行。 客户与服务器之间的通信使用共享内存的方法实现,通信量应该保持最小。例如,客户进程直接访问帧缓冲来完成全部的绘制操作,而不会通过服务器,客户程序需要负责绘制它们自己的标题栏和其他式样。这就是Qt/Embedded库内部层次分明的处理过程。 Qt/Embedded支持4种不同的字体格式:True Type(TTF)、Postscript Typel、位图发布字体(BDF)和Qt的预呈现(Pre-rendered)字体(QPF)。Qt还可以通过增加QFontFactory的子类来支持其他字体,也可以支持以插件方式出现的反别名字体。 Qt/Embedded支持几种鼠标协议:BusMouse、IntelliMouse、Microsoft和MouseMan。通过QWSMouseHandler或QcalibratedMouseHandler派生子类,可以支持更多的客户指示设备。通过QWSKeyboardMouseHandler,可以支持更多的客户键盘和其他非指示设备。
在一个无键盘的设备上,输入法成了惟一的字符输入手段。Qtopia提供了4种输入法:笔迹识别器、图形化的标准键盘、Unicode键盘和基于字典方式提取的键盘。
创建Qt/Embedded开发环境 基于Qt/Embeded开发的应用程序最终会发布到安装有嵌入式Linux操作系统的小型设备上,使用Linux操作系统的PC或者工作站来完成Qt/Embedded开发是最理想的环境。 1.软件安装包 tmake-1.11.tar; qt-embedded-2.3.7.tar; qt-x11-2.3.2.tar; qtopia-free-1.7.0.tar。 首先准备软件安装包:tmake工具安装包、qt-embedded安装包、qt-x11安装包和具有友好人机界面的qtopia-free安装包。把软件包下载到提前建立的x86-qtopia目录下。为防止版本的不同所造成的冲突,选择软件包时需要注意一些基本原则,因为qt-x11安装包的两个工具uic和designer产生的源文件会与qt-embedded库一起被编译链接,本着向前兼容的原则,qt-x11安装包的版本必须比qt-embedded安装包的版本旧。在Trolltech公司的网站上可以下载该公司所提供的Qt/Embedded的免费版本。 2.安装tmake 在Linux命令模式下运行以下命令: tar xfz tmake-1.11.tar.gz export TMAKEDIR=$PWD/tmake-1.11 export TMAKEPATH=$TMAKEDIR/lib/qws/linux-x86-g++ export PATH=$TMAKEDIR/bin:$PATH
3.安装Qt/Embedded2.3.7 在Linux命令模式下运行以下命令: tar xfz 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 cp $QPEDIR/src/qt/qconfig-qpe.h src/tools/ ./configure -qconfig qpe -qvfb -depths 4,8,16,32 make sub-src cd .. “./configure -qconfig qpe -qvfb -depths 4,8,16,32”指定Qt嵌入式开发包生成虚拟缓冲帧工具qvfb,并支持4、8、16、32位的显示颜色深度。也可以在configure的参数中添加-system-jpeg和gif,使Qtopia平台能支持jpeg、gif格式的图形。“make sub-src”指定按精简方式编译开发包。 4.安装Qt/X11 2.3.2 tar xfz qt-x11-2.3.2.tar.gz cd qt-2.3.2 ./configure -no-opengl make make -C tools/qvfb mv tools/qvfb/qvfb bin cp bin/uic $QtEDIR/bin 根据开发者本身的开发环境,也可以在configure的参数中添加其他参数,比如,-no-opengl或-no-xfs,可以键入./configure -help来获得一些帮助信息。
为了方便使用,我们把以上各步骤的操作编译成了build脚本,内容如下所示: 5.安装Qtopia 在Linux命令模式下运行以下命令: tar xfz qtopia-free-1.7.0.tar.gz cd qtopia-free-1.7.x export QtDIR=$QtEDIR export QPEDIR=$PWD export PATH=$QPEDIR/bin:$PATH cd src ./configure make cd ../.. 6.安装Qtopia桌面 cd qtopia-free-1.7.x/src ./configure -qtopiadesktop mv qtopiadesktop/bin/qtopiadesktop ../bin cd .. 为了方便使用,我们把以上各步骤的操作编译成了build脚本,内容如下所示: #!/bin/bash tar xfvz tmake-1.11.tar.gz tar xfvz qt-embedded-2.3.7.tar.gz tar xfvz qtopia-free-1.7.0.tar.gz tar xfvz qt-x11-2.3.2.tar.gz mv tmake-1.11 tmake mv qt-2.3.7/ qt mv qtopia-free-1.7.0 qtopia mv qt-2.3.2 qt-x11 cd qt-x11 export QtDIR=$PWD echo yes | ./configure -static -no-xft -no-opengl -no-sm make -C src/moc cp src/moc/moc bin make -C src make -C tools/designer make -C tools/qvfb cp tools/qvfb/qvfb bin strip bin/uic bin/moc bin/designer bin/qvfb
然后进入x86-qtopia目录执行生成命令: $./build $ldconfig cp qt-x11/bin/?* qt/bin rm -fr qt-x11 export QtDIR=$PWD/qt export QPEDIR=$PWD/qtopia export TMAKEDIR=$PWD/tmake export TMAKEPATH=$TMAKEDIR/lib/qws/linux-generic-g++ export PATH=$QtDIR/bin:$QPEDIR/bin:$TMAKEDIR/bin:$PATH cd qt make clean cp ../qtopia/src/qt/qconfig-qpe.h src/tools/ (echo yes; echo yes ) |./configure -platform linux-generic-g++ -qconfig qpe -depths 16,24,32 make -C src cd .. cd qtopia/src ./configure -platform linux-generic-g++ make 然后进入x86-qtopia目录执行生成命令: $./build $ldconfig 运行ldconfig是为了使生成的qt和qtopia库有效,运行一次即可。 根据上面的步骤安装完成Qt/Embedded和Qtopia之后,就可以运行这些程序了。 运行Qt的虚拟仿真窗口:在Linux的图形模式下运行命令qvfb&; 运行Qtopia,在图形模式下运行命令: export QtDIR=$QtEDIR, qpe & 这样,Qtopia的程序就运行在Qt的虚拟仿真窗口(QVFB)上。
Qt/Embedded的使用 1.信号与槽 信号与槽提供了对象间通信的机制。它们易懂易用,并且Qt设计器能够完整支持。 图形用户接口的应用程序能响应用户的动作。例如,当用户单击一个菜单项或工具栏按钮时,程序就会执行某些代码。大多数情况下,我们需要不同的对象之间能够通信。程序员必须将事件与相关的代码关联起来。以前的开发工具包使用的事件响应机制很容易崩溃、不够健全,同时也不是面向对象的。而Trolltech发明了一套叫做“信号与槽”的解决方案。信号与槽是一种强有力的对象间通信机制,这种机制既灵活,又面向对象,并且用C++来实现,完全可以取代传统工具中的回调和消息映射机制。 以前,使用回调函数机制关联某段响应代码和一个按钮的动作时,需要将相应代码函数指针传递给按钮。当按钮被单击时,函数被调用。对于这种方式不能保证回调函数被执行时传递的参数都有着正确的类型,很容易造成进程崩溃。并且回调方式将GUI元素与其功能紧紧地捆绑在一起,使开发独立的类变得很困难。 Qt的信号与槽机制则不同,Qt的窗口在事件发生后会激发信号。例如,当一个按钮被单击时会激发clicked信号。程序员通过创建一个函数(称做一个槽)并调用connect()函数来连接信号,这样就可以将信号与槽连接起来。信号与槽机制不需要类之间相互知道细节,这使得开发代码可高度重用的类变得更加容易。因为这种机制是类型安全的,类型错误被当成警告并且不会引起崩溃。 信号与槽连接的示意图如图9.8所示。如果一个退出按钮的clicked()信号被连接到一个应用程序的退出函数quit()槽,用户就可以单击退出键来终止这个应用程序。代码可以这样写: connect(button.SIGNAL(clicked()),qApp,SLOT(quit()));
在Qt程序执行期间,是可以随时增加或撤销信号与槽的连接的。它是类型安全的,可以重载或重新实现,并且可以在类的public、protected或private区出现。 例9.1:信号与槽连接的一个实例。如果要使用信号与槽机制,一个类必须继承QObject或它的一个子类,并且在定义这个类时包含Q_OBJECT宏。信号在signals区间里声明,而槽可以在public slots,protected slots或private slots区间声明。以下是子类化QObject的一个例子: class BankAccount : public QObject { Q_OBJECT public: BankAccount() { curBalance = 0; } int balance() const { return curBalance; } public slots: void setBalance( int newBalance ); signals: void balanceChanged( int newBalance ); private: int curBalance; }; BankAccount类有一个构造器,获取函数balance()和设置函数setBalance()。这个类还有一个balanceChanged()信号,用来声明它在BankAccount类的成员curBalance的值改变时产生。信号不需要被实现,当信号被激发时,连接到它的槽就会执行。设置函数作为一个槽在public slots区间声明,槽是能像其他函数一样被调用,也能与信号相连接的成员函数。以下是setBalance()的实现: void BankAccount::setBalance(int newBalance) if(newBalance!=curBalance){ curBalance=newBalance; emit balanceChanged(curBalance); } 其中,语句emit balanceChanged(curBalance)作用是当curBalance的值改变时,将新的curBalance值作为参数去激活信号balanceChanged()。关键字emit跟信号和槽一样是Qt提供的,会被C++预处理器转换成标准的C++代码。
例9.2:这是一个怎样连接两个BankAccount对象的实例: BankAccount x,y; connect(&x,SIGNAL(balanceChanged(int)),&y,SLOT(setBalance(int))); x.setBalance(2450); 当x的balance被设置为2450时,balanceChanged()信号被激发,这个信号被y的setBalance()槽接收后,y的balance也被设置为2450。 一个对象的信号可以连接到许多不同的槽,多个信号也可以连接到特定对象的一个槽。连接在具有相同参数的信号与槽之间建立,槽的参数可以比信号少,多余的参数会被忽略。 信号与槽机制是在标准C++中实现的,是使用Qt工具包中的C++预处理器和元对象编译器(Meta Object Compiler,moc)来实现的。moc读取程序头文件并产生必要的支持信号与槽机制的代码。qmake产生的Makefiles会将moc自动加入进去,开发者无需编辑甚至无需查看这些产生的代码。
2.窗口部件 Qt拥有一系列能满足不同需求的窗口部件,如按钮、滚动条等。Qt的窗口部件使用很灵活,能够适应子类化的特殊要求。 Qt中有3个主要的基类:QObject、QTimer和QWidget。窗口部件是QWidget或其子类的实例,自定义的部件则通过子类继承得来,继承关系如图9.9所示。
(1)基本部件。 一个窗口部件可包含任意数量的子部件。子部件在父部件的区域内显示。没有父部件的部件是顶级部件(比如一个窗口),Qt不在窗口部件上施加任何限制。任何部件都可以是顶级部件;任何部件都可以是其他部件的子部件。通过使用布局管理器可以自动设定子部件在父部件区域中的位置,如果喜欢也可以手动设定。如果父部件被停用、隐藏或删除后,同样的动作会递归地应用于它的所有子部件。 标签、消息框、工具提示等并不局限于使用同一种颜色、字体和语言。通过使用HTML的一个子集,Qt的文本渲染部件能够显示多语言宽文本,下面是一个实例代码: #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(); } 这是我们接触到的第一个Qt程序,为了使大家便于理解,对程序的代码一行一行地解说。 这一行引用了包含QApplication类定义的头文件。在每一个使用Qt的应用程序中都必须使用一个QApplication对象。QApplication管理了各种各样的应用程序的广泛资源,比如默认的字体和光标,等等。
#include < qlabel.h> 引用了包含QLabel类定义的头文件,因为本例使用了QLabel对象。QLabel可以像其他QWidget一样管理自己的外形。一个窗口部件就是一个可以处理用户输入和绘制图形的用户界面对象。程序员可以改变它的全部外形和其他属性,以及这个窗口部件的内容。 int main( int argc, char **argv ) main()函数是程序的入口。在使用Qt的所有情况下,main()只需要在把控制转交给Qt库之前执行一些初始化,然后Qt库通过事件来向程序告知用户的行为。 argc是命令行变量的数量,argv是命令行变量的数组,这是一个C/C++特征。 QApplication app( argc, argv ); app是这个程序的Qapplication,它在这里被创建并且处理命令行变量。所有被Qt识别的命令行参数都会从argv中被移除,并且argc也因此而减少。在任何Qt的窗口系统部件被使用之前必须创建QApplication对象。 QLabel *hello = new QLabel( "<font color=blue>Hello" " <i>Qt/Embedded!</i></font>", 0 ); 这里是在QApplication之后接着的是第一个窗口系统代码,创建了一个标签。这个标签被设置成显示“Hello Qt/Embedded!”并且字体颜色为蓝色,“Qt/Embedded!”为斜体。因为构造函数指定0为它的父窗口,所以它自己构成了一个窗口。 app.setMainWidget( &hello ); 这个按钮被选为这个应用程序的主窗口部件。如果用户关闭了主窗口部件,应用程序就退出了。设置主窗口部件并不是必须的步骤,但绝大多数程序都会这样做。 hello.show(); 当创建一个窗口部件的时候,它是不可见的。必须调用show()来使它变为可见的。 return app.exec(); 这里就是main()把控制转交给Qt,并且当应用程序退出的时候exec()就会返回。在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
(2)画布。 QCanvas类提供一个2D图形的高级接口。它能够处理大量的画布项目来描述直线、矩形、椭圆、文本、位图,以及动画等。画布项目很容易做成交互式界面,例如,支持用户移动等。 画布项目是QCanvasItem子类的实例。它们比窗口部件轻巧得多,能很快地移动、隐藏和显示。QCanvas可以有效地支持冲突检测,还能罗列出指定区域中的所有画布项目。QCanvasItem可以被子类化,用以提供自定义的项目类型或扩充已有类型的功能。 QCanvas对象由QCanvasView对象绘制,QCanvasView对象能以不同的译文、比例、角度和剪切方式显示同一个QCanvas。QCanvas是数据表现方式的典范,它可被用来绘制路线地图和展示网络拓扑,也适合开发快节奏有大量角色的2D游戏。 (3)自定义窗口部件。 通过对QWidget或它的一个子类进行子类化,我们可以创建自己的窗口部件或对话框。为了举例说明子类化,下面提供了数字钟部件的完整代码。如图9.16所示为数字钟图片显示。 数字钟部件是一个能显示当前时间并自动更新的LCD,冒号分隔符随秒数的流逝而闪烁。
clock.h中声明的函数在clock.cpp中实现: Clock在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; }; Clock从QLCDNumber部件继承了LCD功能。它有一个典型部件类所拥有的典型构造函数,带有可选parent和name参数。系统有规律地调用从QObject继承的timerEvent()函数。 clock.h中声明的函数在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 *) void Clock::showTime() QString timer=QTime::currentTime().toString().left(5); if(!showingColon) time[2]=' '; display(time); showingColon=!showingColon; 构造函数调用showTime()显示当前时间来初始化钟表,并且告诉系统每1000毫秒调用一次timerEvent()来刷新LCD的显示。在showTime()中,通过调用QLCDNumber::display()来显示当前时间。每次调用showTime()来让冒号闪烁时,冒号就被空白代替。
文件clock.h和clock.cpp完整地声明并实现了Clock部件,这个部件可立即投入使用。 #include <qapplication.h> #include "clock.h" int main(int argc,char **argv) { QApplication app(argc,argv); Clock *clock=new Clock; app.setMainWidget(clock); clock->show(); return app.exec(); } 这个样例程序包括了单个部件(数字钟)并且没有子部件。通过布局来联合部件可以产生复杂部件。如果需要也可以从头开始编写自己的部件。例如,要创建一个类似于钟表的部件,可能需要在代码中绘制钟面和指针而不是依靠已经实现的基本类。
3.主窗口 QMainWindow类为应用程序提供了一个典型的主窗口框架。一个主窗口包括一系列标准部件,顶部包含一个菜单栏,菜单栏下放置这一个工具栏,在主窗口的下方有一个状态栏。 工具栏可以任意放置在中心区域的四边,也可以拖拽到工具栏区域以外,作为独立的浮动工具托盘。 QToolButton类实现了具有一个图标,一个3D框架和一个可选标签的工具栏按钮。切换型工具栏按钮可以打开或关闭某些特征,其他的按钮则会执行一个命令,也能触发弹出式菜单。QToolButton可以为不同的模式(活动、关闭、开启等)和状态(打开、关闭等)提供不同的图标。如果只提供一个图标,Qt能根据可视化线索自动地辨别状态,例如,将禁用的按钮变灰。 QToolButton通常在QToolBar内并排出现。一个程序可含有任意数量的工具栏并且用户可以自由移动它们。工具栏可以包括几乎所有部件,例如,QComboBox和QSpinBox。 主窗口的中间区域可以包含多个其他窗体。
下面的代码实现了一个“Save”菜单项、一个“Save”工具栏按钮和一个“Save”快捷键,并且有旁述帮助和快捷键: 4.菜单 弹出式菜单QpopupMenu类在一个垂直列表里面向用户呈现菜单项,它可以是单独的(如背景菜单),可以出现在菜单栏里,也可以是另一个弹出式菜单的子菜单。菜单项之间可以用分隔符隔开。每个菜单项可以有一个图标、一个复选框和一个快捷键。菜单项通常会响应一个动作(比如,保存)。分隔符通常显示为一条线,用来可视化地分组相关的动作。下面是一个创建了New、Open、Exit菜单项的文件菜单的例子: QPopupMenu *fileMenu=new QPopupMenu(this); fileMenu->insertItem("&New",this,SLOT(newFile()),CTRL+Key_N); fileMenu->insertItem("&Open...",this,SLOT(open()),CTRL+Key_O); fileMenu->insertSeparator(); fileMenu->insertItem("E&xit",qApp,SLOT(quit()),CTRL+Key_X); QMenuBar实现了一个菜单栏。它自动布局在其父部件(如一个QMainWindow)的顶端,如果父窗口不够宽就会自动地分割成多行。Qt内置的布局管理器能够自动调整各种菜单栏。下面展示了创建一个含有File、Edit和Help菜单的菜单栏的方法: QMenuBar *bar=new QMenuBar(this); bar->insertItem("&File",fileMenu); bar->insertItem("&Edit",editMenu); bar->insertItem("&Help",helpMenu); Qt的菜单系统非常灵活。菜单项能够被动态地使能、失效、增加或删除。通过子类化QcustomMenuItem,可以创建自定义外观和行为的菜单项。 应用程序通常提供几种不同的方式来执行特定的动作。比如,许多应用程序通过存盘菜单(Flie|Save)、工具栏(一个软盘图标的按钮)和快捷键(Ctrl+S)来提供“Save”动作。QAction类封装了“动作”这个概念,它允许程序员定义一个动作。 下面的代码实现了一个“Save”菜单项、一个“Save”工具栏按钮和一个“Save”快捷键,并且有旁述帮助和快捷键: QAction *saveAct=new QAction("Save",saveIcon,"&Save",CTRL+Key_S,this); connect(saveAct,SIGNAL(activated()),this,SLOT(save())); saveAct->setWhatsThis("Saves the current file."); saveAct->addTo(fileMenu); saveAct->addTo(toolbar);
5.布局 Qt提供了布局管理器进行部件布局的优化。布局管理器把程序员从程序显示大小和位置的计算中解放出来,并且提供了自动调整的能力以适应用户的屏幕、语言和字体。Qt通过布局管理器来组织父部件区域中的子部件,它可以自动调整子部件的大小和位置,判断一个顶级窗口的最小和默认尺寸,并在内容或字体改变时重新定位,布局也有利于国际化。具有固定的位置和大小时,译文常常被截断;具有布局时,子部件能自动调整大小。 Qt提供了3种布局管理器的类:QHBoxLayout、VBoxLayout和QgridLayout。 QHBoxLayout将其管理的部件组织在一个从左到右的水平行上。 QVBoxLayout将其管理的部件组织在一个从上到下的垂直列上。 QGridLayout将其管理的部件组织在一些网格单元中。 多数情况下,Qt的布局管理器为其管理的部件挑选一个最适合的尺寸以便窗口能够平滑地缩放。如果其默认值不合适,开发者可以使用以下机制微调布局: 设置一个最小尺寸,一个最大尺寸,或者为一些子部件设置固定的大小。 设置一些延伸项目或间隔项目,延伸或间隔项目会填充空余的布局空间。 改变子部件的尺寸策略。通过调用QWidget::setSizePolicy(),程序员可以仔细调整子部件的缩放行为。子部件可以设置为扩展、收缩、保持原大小,等等。 改变子部件的建议大小。QWidget::sizeHint()和QWidget::minimumSizeHint()会根据内容返回部件的首选尺寸和最小首选尺寸。 设置拉伸比例系数。拉伸比例系数规定了子部件的相应增量,比如,三分之二的可用空间分配给部件A而三分之一分配给B。 布局也可以按从右到左或从下到上的方式组织,布局也是可以嵌套和随意进行的。在图9.18所示的例子中,一个对话框可以显示两种尺寸。对话框使用了3种布局: QVBoxLayout管理右边三个按钮,按垂直方向排列,组成按钮组; QHBoxLayout管理显示国家名称的列表和按钮组,按水平方向排列; QVBoxLayout组合了“Select a country”的标签和剩下的部件。
提示工具和“这是什么?”帮助以旁述的方式阐述了用户接口的使用方法。 工具提示是当鼠标指针在一个部件上徘徊时自动显示的小黄色矩形。工具提示通常用来解释一个工具栏按钮,因为工具栏按钮很少显示文本标签。下面是设置“Save”按钮工具提示的例子: QToolTip::add(saveButton,"Save"); 当工具提示显示的时候也可以让状态栏同时显示一个长文本。 “这是什么”帮助类似于工具提示,但它需要用户主动发出请求,比如,按下Shift+F1,或者单击一个部件或菜单项。“这是什么?”帮助通常会比工具提示长。下面是为“Save”工具栏按钮设置“这是什么?”帮助的方法: QWhatsThis::add(saveButton,"Saves the current file."); QToolTip和QWhatsThis类提供了可以重新实现的虚函数,来获取更多特殊化行为,比如,根据鼠标在部件的不同位置来显示相应的文本。 对于多文档界面(MDI)程序,中心区域可以包括任何部件;多文档界面(MDI)由QWorkspace类提供,可被用做一个QMainWindow的中心部件。例如,一个文本编辑器可以把QTextEdit作为中心部件: QTextEdit *editor=new QTextEdit(mainWindow); mainWindow->setCentralWidget(editor); QWorkspace的子部件可以是任意类型,它们可以用类似定级部件的边框来修饰。Show()、hide()、showMaximized()和setCaption()等函数以同样的方式作用于MDI子部件和顶层部件。 QWorkspace提供了诸如层叠与平铺等放置策略。如果一个子部件扩展到了MDI区域的外部,滚动条就会自动出现。如果一个子部件最大化,其边框按钮(如最小化按钮)就会出现在菜单栏中。
在“<Prev”和“Help”按钮之间放置了一个延伸项,使得两者之间保持一定比例的间隔,维护两者之间的距离。 下面的代码创建了对话框部件和布局: QVBoxLayout *buttonBox = new QVBoxLayout( 6 ); buttonBox->addWidget( new QPushButton("Next >", this) ); buttonBox->addWidget( new QPushButton("< Prev", this) ); buttonBox->addStretch( 1 ); buttonBox->addWidget( new QPushButton("Help", this) ); QListBox *countryList = new QListBox( this ); countryList->insertItem( "Canada" ); /* … */ countryList->insertItem( "United States of America" ); QHBoxLayout *middleBox = new QHBoxLayout( 11 ); middleBox->addWidget( countryList ); middleBox->addLayout( buttonBox ); QVBoxLayout *topLevelBox = new QVBoxLayout( this, 6, 11 ); topLevelBox->addWidget( new QLabel("Now please select a country", this) ); topLevelBox->addLayout( middleBox ); 通过子类化QLayout开发者可以定义自己的布局管理器。Qt还包括QSplitter,一个最终用户可以操纵的分离器。某些情况下,QSplitter可能比布局管理器更为实用。为了完全地控制,通过重新实现每个子部件的QWidget::resizeEvent()并调用QWidget::setGeometry(),就可以在一个部件中手动实现布局。
6.Qt设计器 用Qt设计器设计一个窗体是个简单的过程。开发者单击一个工具箱按钮即可加入一个想要的窗口部件,然后在窗体上单击一下即可定位这个部件。部件的属性可以通过属性编辑器修改,部件的精确位置和大小并不重要。开发者可以选择部件并且在上面应用布局,例如,可以通过选择“水平布局”来让一些窗口部件并排放置。这种方法可以非常快速地设计,完成后的窗体能够正确地调整窗口为任意大小来适应最终用户的喜好。 Qt设计器去掉了界面设计中费时的“编译,连接与运行”的循环,同时使得修改图形用户接口的设计变得更容易。Qt设计器的预览选项能让开发者看到其他风格下的窗体,例如,一个Macintosh开发者可以预览到Windows风格的窗体。 开发者既可以创建对话框式的程序,也可以创建带有菜单、工具栏、帮助和其他特征的主窗口式程序。Qt本身提供了一些窗体模板,开发者也可以根据需要创建自己的模板以确保窗体的一致性。Qt设计器利用向导方式使得工具栏、菜单和数据库程序的创建变得尽可能快。程序员还可以通过Qt设计器创建易于集成的自定义部件。 程序中用到的图标和其他图像自动地被同一工程中的所有窗体共享,这样可以减小可执行文件的体积并且能加快载入速度。 窗体设计被保存成XML格式的.ui文件并且被uic(用户界面编译器)转换成为C++头文件和源文件。由于qmake构建工具在它生成的makefiles中自动地包含了uic的规则,因此开发者不需要自己去加入uic。 窗体通常被编译到可执行文件中,但有些情况下客户需要在不涉及源代码的情况下修改程序的外观。Qt支持“动态对话框”,.ui文件可以在运行时载入并且动态地转换成完整功能的窗体。载入一个动态对话框很容易,例如: QDialog *creditForm=(QDialog *)QWidgetFactory::create("creditform.ui"); 即可实现.ui文件在运行时载入。
9.4 综合训练之Hello程序 Qt/Embedded开发流程 在进行Qt/Embedded开发前,必须要选择目标板和嵌入式开发环境,这些内容在前面章节已有介绍,在此不再赘述,我们只针对Qt/Embedded部分进行介绍。嵌入式应用程序的开发基本上是在宿主机上完成的,一般在PC机上调试运行嵌入式应用程序,并将输出结果显示在一个仿真嵌入式设备显示终端的模拟器上。Qt/Embedded的开发也不例外。在宿主机调试通过后,还需要发布到目标板上。 为了便于操作,我们首先在arm-qtopia目录下重新建立了一套针对目标板的Qt/Embedded环境。参考9.3.2节安装Qt/Embedded开发环境,由于我们选择的硬件环境是友善之臂的SBC-2410X开发板,所以需要针对ARM9的开发环境建立Build脚本。 开发环境建立后,开始应用程序的开发,如果仿真测试正确,就可以编译连接成适合于目标板的二进制代码,同时还要把Qt/Embedded库的源代码编译链接,并下载到目标板上,以给应用程序提供Qt/Embedded二进制代码库。当应用程序发布到目标板上,并能正确运行时,即可宣告开发过程结束。
tar xfvz tmake-1.11.tar.gz tar xfvz qt-embedded-2.3.7.tar.gz tar xfvz qtopia-free-1.7.0.tar.gz tar xfvz qt-x11-2.3.2.tar.gz mv tmake-1.11 tmake mv qt-2.3.7/ qt mv qtopia-free-1.7.0 qtopia mv qt-2.3.2 qt-x11 cd qt-x11 export QTDIR=$PWD echo yes | ./configure -static -no-xft -no-opengl -no-sm make -C src/moc cp src/moc/moc bin make -C src make -C tools/designer make -C tools/qvfb cp tools/qvfb/qvfb bin strip bin/uic bin/moc bin/designer bin/qvfb cd .. cp qt-x11/bin/?* qt/bin rm -fr qt-x11 export QTDIR=$PWD/qt export QPEDIR=$PWD/qtopia export TMAKEDIR=$PWD/tmake export TMAKEPATH=$TMAKEDIR/lib/qws/linux-arm-g++ export PATH=$QTDIR/bin:$QPEDIR/bin:$TMAKEDIR/bin:$PATH cd qt make clean cp ../qtopia/src/qt/qconfig-qpe.h src/tools/ (echo yes ; echo no) | ./configure -platform linux-arm-g++ -qconfig qpe -depths 16,24,32 cd qtopia/src ./configure -platform linux-arm-g++ make
基于PC的Hello程序 1.生成一个工程文件 一个应用通常对应一个工程文件。产生工程文件可以使用progen命令,该命令可以在tmake的安装路径下找到,使用方法如下: progen [可选项] [C/C++ 头文件和源文件] 可选项: -lower将文件名小写 -n name定义工程名(即目标名) -o file定义输出文件 -t file制定模板文件 如果没有指定头文件和源文件的话,progen将搜索所有在当前目录和它的子目录的所有文件(除了moc_*.cpp)。 要产生Hello工程文件可使用命令: progen –n hello –o hello.pro 但是,这里产生的工程文件hello.pro并不完整,还需手工添加工程所包含的头文件和源文件等信息。 2.新建窗体 在Qt/X11 2.3.2的安装路径的bin目录下运行“./designer”命令,就启动了Qt设计器。单击编辑器的“new”菜单,弹出了一个“new Form”对话框,在这个对话框里选择“Widget”,然后单击“OK”按钮,这样,我们就新建了一个窗体。接下来的工作就是按自己的喜好设置窗体属性。 图9.22所示为窗体属性设置界面。 当设置完成后存盘时,会将这个新的组件保存为一个扩展名为.ui的文件。
3.生成窗体类的源文件 假设窗体生成文件名hello.ui,用它生成相应的hello.h和hello.cpp。同样还是在这个目录下,可以看到一个uic的工具,这个是Qt专门用来将ui文件生成.h和.cpp文件的。 在终端模式下键入以下命令: ./uic -o hello.h hello.ui ./uic -o hello.h -i hello.cpp hello.ui 此时能看到生成了相应的hello.h和hello.cpp,这是一个类。当然,这只是一些前端显示的东西,还需要在这些代码中添加相应的信号和槽,完成所需要的操作。 在一般的开发过程中,首先通过这个ui生成的一个类(在Qt中通常叫做Base),如本例中的hello_Base类;然后再新建一个类,来继承这个Base。在这个实现类里定义所需要的成员函数、信号和槽。这样做的好处是:当ui需要改动时,只需要在设计器中修改ui,而不用去理会这些成员函数、信号和槽了。 基于上述原因,我们把窗体生成文件保存为hello_base.ui,并生成hello_base.h和hello_base.cpp。
hello_base.cpp代码如下: hello_base.h的代码如下: #include "hello_base.h" #include <qlabel.h> #include <qpushbutton.h> #include <qlayout.h> #include <qvariant.h> #include <qtooltip.h> #include <qwhatsthis.h> HelloBaseForm::HelloBaseForm( QWidget* parent, const char* name, WFlags fl ) : QWidget( parent, name, fl ) { if ( !name ) setName( "HelloBaseForm" ); resize( 354, 223 ); setCaption( tr( "Test my first Qtopia Application" ) ); MessageLabel = new QLabel( this, "MessageLabel" ); MessageLabel->setGeometry( QRect( 80, 170, 180, 20 ) ); MessageLabel->setText( tr( "" ) ); PushButton1 = new QPushButton( this, "PushButton1" ); PushButton1->setGeometry( QRect( 110, 90, 108, 32 ) ); PushButton1->setText( tr( "Hello,SBC-2410X" ) ); // signals and slots connections connect( PushButton1, SIGNAL( clicked() ), this, SLOT( SayHello() ) ); } HelloBaseForm::~HelloBaseForm() // no need to delete child widgets, Qt does it all for us void HelloBaseForm::SayHello() qWarning( "HelloBaseForm::SayHello(): Not implemented yet!" ); hello_base.h的代码如下: #ifndef HELLOBASEFORM_H #define HELLOBASEFORM_H #include <qvariant.h> #include <qwidget.h> class QVBoxLayout; class QHBoxLayout; class QGridLayout; class QLabel; class QPushButton; class HelloBaseForm : public QWidget { Q_OBJECT public: HelloBaseForm( QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); ~HelloBaseForm(); QLabel* MessageLabel; QPushButton* PushButton1; protected slots: virtual void SayHello(); }; #endif // HELLOBASEFORM_H
4.继承类源文件 创建hello类继承基类hello_Base,重构SayHello()函数,单击QPushButton时在QLabel上显示窗体上显示“Hello, Qtopia world!”。 hello.h文件代码如下: #ifndef HELLOFORM_H #define HELLOFORM_H #include "hello_base.h" class HelloForm : public HelloBaseForm { public: HelloForm( QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); virtual ~HelloForm(); protected: virtual void SayHello(); }; #endif // HELLOFORM_H hello.cpp文件代码如下: #include "hello.h" #include <qlabel.h> HelloForm::HelloForm( QWidget* parent, const char* name, WFlags fl): HelloBaseForm(parent, name, fl) } HelloForm::~HelloForm() void HelloForm::SayHello() MessageLabel->setText("Hello, Qtopia world!");
一个Qt/Embedded应用程序应该包含一个主函数,所在文件名是main.cpp。主函数是应用程序执行的入口点。 #include "hello.h" #include <qtopia/qpeapplication.h> int main( int argc, char *argv[] ) { QPEApplication a( argc, argv ); HelloForm f; a.showMainWidget( &f ); return a.exec(); } 6.编辑工程文件hello.pro 编译一个Qt程序必然需要makefile,在Qt中提供了一个专门生成makefile的工具,就是tmake。由于tmake是根据工程文件.pro产生makefile文件的,因此需要先做些pro相关介绍。.pro文件非常简单,有固定的格式,可以随意添加或删减各个选项。设置一个选项如下: HEADERS = gui.h xml.h url.h 如果选项不能在一行内写完的话,可以使用‘\’来分割它,例如: HEADERS = gui.h \ xml.h \ url.h 由于工程文件使用空白来分割各个元素,所以不能将带有空格的元素用在工程文件中。但是对INCLUDEPATH选项不同,它使用“;”来分割元素,也可以包含代空格的元素,例如: INCLUDEPATH = C:\Program Files\DBLib\Include;C:\qt\include
hello.pro工程文件中描述产生了makefile文件的配置模板是app.t,配置选项是“qt warn_on release”。 TEMPLATE = app DESTDIR = $(QPEDIR)/bin CONFIG += qtopia warn_on release HEADERS = hello.h SOURCES = hello.cpp\ main.cpp INTERFACES = hello_base.ui TARGET = hello hello.pro工程文件中描述产生了makefile文件的配置模板是app.t,配置选项是“qt warn_on release”。 (1)配置模板。 tmake发行版本中makefile有app.t、lib.t和subdirs.t 3个模板。应用程序模板(app.t)用来创建生成发布使用程序的makefile,此模板支持以下选项。 HEADERS源头文件; SOURCES原程序文件; TARGET目标文件; DESTDIR放置目标文件的目录; DEFINES告知C预处理编译器打开“-D”选项; INCLUDEPATH设置库文件路径(-I选项); DEPENDPATH设置相关搜索路径; DEF_FILE WINDOWS专用:链接一个.def文件; RC_FILE WINDOWS专用:使用.rc文件(编译为.res); RES_FILE WINDOWS专用:链接.res文件。 资源库模板(lib.t)用来创建生成libraries的makefile,使用该模板可以编译创建一个动态或静态库。lib.t支持和app.t相同的选项,同时还支持一个app.t不支持的选项:VERSION。子目录模板(subdirs.t)用来创建目标文档在目录中的makefile,当程序文件太多时,可以使用它。它将所列出的文件夹全部包括进去,并进行编译。
(2)Makefile配置选项。 配置选项可以用在app.t和lib.t中,它们用来指示使用什么编译选项和连接什么库文件。控制编译选项用来选择编译类型: release用来生成最优化编译(用于发布的软件),如果选了“debug”,此选项忽略; debug调试时使用该选项,打开debug功能; warn_on打开警告选项,产生比正常情况下多的警告。如果选了“warn_off”,此选项忽略; warn_off关闭警告选项。 控制程序/库文件类型: qt如果是生成qt程序,打开它(该选项是默认支持的); opengl编译OPENGL时使用; thread用来支持线程; X11用来支持X11; windows支持WINDOWS; console对象是WINDOWS下的控制台程序; dll生成动态连接库时使用; staticlib生成静态连接库时使用。 举个例子,假如我们的hello程序需要qt和opengl支持,并且还要打开debug,那么我们的配置就该是这样的: TEMPLATE = app CONFIG = qt opengl debug HEADERS = hello.h SOURCES = hello.cpp main.cpp TARGET = hello
7.生成makefile 生成hello.pro文件后,在终端中键入下面的命令: tmake -o makefile hello.pro 就自动生成了一个makefile,使用这个makefile编译所编写的程序就可以了。 tmake是一个很好用的生成和管理makefile的工具,它是由Trolltech公司自行开发的。tmake将我们从烦琐的生成makefile的过程中解脱出来,只要很简单的步骤就可以生成makefile了。tmake的使用方法如下: tmake [选项] project files or project settings 选项: -e expr执行perl表达式,忽略模板文件; -nodepend不产生关联信息; -o file指定输出文件(常用); -t file制定模板文件(覆盖工程文件中的模板变量); -unix强制使用UNIX模式; -v打开debug和warn_on选项; -win32强制使用win32模式。 默认的工程文件后缀名为“.pro”,默认的模板文件后缀为“.t”。如果用户不指定它们的话,tmake会自动为用户加上。 8.编译链接工程 最后在命令行下输入“make”命令,对整个工程进行编译链接,生成的二进制文件就是hello可执行文件。 $cd hello $make
9.程序运行 对生成的二进制文件单独运行: $qvfb -width 640 -height 480 & $hello –qws
重新登录后,需要设置Qt环境变量和tmake路径参数,并把tmake/bin加入到执行路径中去。 $export QTDIR=$PWD/qt $export QPEDIR=$PWD/qtopia $export TMAKEDIR=$PWD/tmake $export TMAKEPATH=$TMAKEDIR/lib/qws/linux-generic-g++ $export PATH=$QTDIR/bin:$QPEDIR/bin:$TMAKEDIR/bin:$PATH $hello 要想在Qtopia中运行应用程序,还需要建立启动器hello.desktop文件。建立一个文本文件,在文件中添加以下内容,这些内容指定了应用的名称、图标等信息,然后将文件更名为hello.desktop文件,内容如下: [Desktop Entry] Comment=An Example Program Exec=hello Icon=Hello Type=Application Name=Hello2410 将hello.desktop保存到应用程序配置目录$QPEDIR/apps/applications,然后运行虚拟Framebuffer: #qvfb -width 640 -height 480 & 然后执行“qpe”,在qpotia中出现hello程序,单击即可运行程序。
发布Qt/Embedded程序到目标板 设计Qt/Embedded的目的就是发布到目标板上去,源代码不做任何修改,只需要修改hello的makefile文件,修改部分如下: SYSCONF_CXX = arm-linux-g++ SYSCONF_CC = arm-linux-gcc DASHCROSS = -arm SYSCONF_LINK = arm-linux-gcc SYSCONF_LIBS = -lm SYSCONF_LINK_SHLIB = arm-linux-gcc SYSCONF_AR = arm-linux-ar cqs MOC_DIR = .moc/linux-arm-g++/ OBJECTS_DIR = .obj/linux-arm-g++/ CTL="$(CONTROL)"; for ctrl in $$CTL; do cd $(QPEDIR)/ipkg; ../bin/mkipks -platform linux-arm-g++ $(QPEDIR)/src/$$ctrl ; done cd $(QPEDIR)/src; ./configure -platform linux-arm-g++ -make /friendly-arm/gui/free-qtopia-arm/hello 所修改的只是编译器和目录相关部分。进入hello目录,重新编译完毕,hello将生成并保存在arm-qtopia/qtopia/bin目录。将可执行文件下载到目标板的/opt/qtopia/bin目录和/opt/qtopia/apps/Applications/目录。 重新启动后,就会看到hello程序已经存在,单击运行即可。