59
9 第第第 Linux 第第第第第第第第

第 9 章 嵌入式 Linux 用户图形界面编程

  • Upload
    huslu

  • View
    88

  • Download
    0

Embed Size (px)

DESCRIPTION

第 9 章 嵌入式 Linux 用户图形界面编程. 9.1 Linux 图形开发基础. 本节在介绍图形用户界面一般构架的基础上,详细讨论嵌入式图形用户界面开发常见的底层支持库和高级函数库。这些内容都是 Linux 图形界面开发的基础知识。. GUI 的一般架构. 图形用户界面 GUI ( Graphics User Interface )是迄今为止计算机系统中最为成熟的人机交互技术。一个好的图形用户界面的设计不仅要考虑到具体硬件环境的限制,而且还要考虑到用户的喜好等。 - PowerPoint PPT Presentation

Citation preview

Page 1: 第 9 章   嵌入式 Linux 用户图形界面编程

第 9章 嵌入式 Linux用户图形界面编

Page 2: 第 9 章   嵌入式 Linux 用户图形界面编程

9.1 Linux 图形开发基础

本节在介绍图形用户界面一般构架的基础上,详细讨论嵌入式图形用户界面开发常见的底层支持库和高级函数库。这些内容都是 Linux 图形界面开发的基础知识。

Page 3: 第 9 章   嵌入式 Linux 用户图形界面编程

GUI 的一般架构图形用户界面 GUI ( Graphics User Interface )是迄今为止计算机系统中最

为成熟的人机交互技术。一个好的图形用户界面的设计不仅要考虑到具体硬件环境的限制,而且还要考虑到用户的喜好等。

由于图形用户界面的引入主要是从用户角度出发的,因此用户自身的主观感受对图形用户界面的评价占了很大比重,比如,易用性、直观性、友好性,等等。另外,从纯技术的角度看,仍然也会有一些标准需要考虑,比如,跨平台性、对硬件的要求等。在嵌入式系统开发和应用中,我们所考虑的问题主要集中在图形用户界面对硬件的要求,以及对硬件类型的敏感性方面,在提供给用户的最终界面方面只是要求简单实用就够了。

虽然不同的 GUI 系统因为其使用场合或服务目的不同,具体实现互有差异,但是总结起来,一般在逻辑上可以分为以下几个模块:底层 I/O 设备驱动(显示设备驱动、鼠标驱动、键盘驱动等)、基本图形引擎(画点、画线、区域填充)、消息驱动机制、高层图形引擎(画窗口、画按钮),以及 GUI 应用程序接口( API )。

Page 4: 第 9 章   嵌入式 Linux 用户图形界面编程
Page 5: 第 9 章   嵌入式 Linux 用户图形界面编程

底层 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 应该易于使用并且非常可靠。此外,它不能占用太多的内存,以便在内存受限的微型嵌入式设备上无缝执行。由此可见,它应该是轻量级的,并且能够快速装入。

Page 6: 第 9 章   嵌入式 Linux 用户图形界面编程

嵌入式 GUI 要求简单、直观、可靠、占用资源小且反应快速,以适应系统硬件资源有限的条件。另外,由于嵌入式系统硬件本身的特殊性,嵌入式 GUI 应具备高度可移植性与可裁减性,以适应不同的硬件条件和使用需求。总体来讲,嵌入式GUI 具备以下特点:

体积小; 运行时耗用系统资源小; 上层接口与硬件无关,高度可移植; 高可靠性; 在某些应用场合应具备实时性。一个能够移植到多种硬件平台上的嵌入式 GUI 系统,应至少抽象出两类设备:

基于图形显示设备(如 VGA卡)的图形抽象层 GAL ( Graphic Abstract Layer )和基于输入设备(如键盘,触摸层等)的输入抽象层 IAL ( Input Abstract Layer )。 GAL 层完成系统对具体的显示硬件设备的操作,最大限度地隐藏各种不同硬件的技术实现细节,为程序开发人员提供统一的图形编程接口。 IAL 层则需要实现对于各类不同输入设备的控制操作,提供统一的调用接口,如图 9.2 所示。 GAL层与 IAL 层设计概念的引入,可以显著提高嵌入式 GUI 的可移植性。

Page 7: 第 9 章   嵌入式 Linux 用户图形界面编程

嵌入式 GUI 底层支持库 1. X Window

Page 8: 第 9 章   嵌入式 Linux 用户图形界面编程
Page 9: 第 9 章   嵌入式 Linux 用户图形界面编程

2. FrameBufferFrameBuffer 是出现在 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负担很重。

Page 10: 第 9 章   嵌入式 Linux 用户图形界面编程

3. SVGALibSVGALib 是 Linux 系统中最早出现的非 X 图形支持库,是 Linux 下的 VGA 驱

动函数库。虽然它的品质有点低,支援显卡种类也不多,但是有许多的游戏及程序都是用它来做开发,可以算是非官方的标准了。这个库从最初对标准 VGA兼容芯片的支持开始,已经发展到对老式 SVGA芯片,以及现今流行的高级视频芯片的支持。它为用户提供了在控制台上进行图形编程的接口,使用户可以在 PC兼容系统上方便地获得图形支持。但该系统也存在一些不足:

SVGALib 从最初的 vgalib 发展而来,保留了老系统的许多接口,而这些接口却不能良好地适应新显示芯片的图形能力。

未能较好地隐藏硬件细节。许多操作,不能自动使用显示芯片的加速能力支持。

可移植性差。 SVGALib 目前只能运行在 x86 平台上,除 Alpha 平台,对其他平台的支持能力较差。

SVGALib 作为一个老的图形支持库,目前的应用范围越来越小,尤其在 Linux 内核增加了 FrameBuffer 驱动支持之后,有逐渐被其他图形库替代的趋势。

4. LibGGILibGGI 是一个跨平台的绘图库,可以建立一个一般性的图形接口,这个抽象

接口连同相关的输入(鼠标、键盘、游戏杆等)接口一起,可以方便地运行在 X Window 、 SVGALib 、 FrameBuffer 等之上。建立在 LibGGI之上的应用程序,不经重新编译,就可以在上述这些底层图形接口上运行。

在 Linux 上, LibGGI 是通过调用 FrameBuffer 或 SVGALib 来完成图形操作的,可能速度比较慢。但在某些不支持 FrameBuffer 或 vga 的系统上,采用 LibGGI 仍然是一种不错的选择。

Page 11: 第 9 章   嵌入式 Linux 用户图形界面编程

嵌入式 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. AllegroAllegro 是一个专门为 x86 平台设计的游戏图形库。最初的 Allegro运行在 DOS 环境

下,目前也可运行在 Linux FrameBuffe控制台、 Linux SVGALib 、 X Window 等系统上。Allegro 提供了丰富的图形功能,包括矩形填充和样条曲线生成等,而且具有较好的三维图形显示能力。由于 Allegro 的许多关键代码是采用汇编编写的,所以该函数库具有运行速度快、占用资源少的特点。

4.Mesa3DMesa3D 是一个兼容 OpenGL规范的开放源码函数库,是目前 Linux 上提供专业三维

图形支持的惟一选择。 Mesa3D 也是一个跨平台的函数库,能够运行在 X Window 、 X Window with DGA 、 BeOS 、 Linux SVGALib 等平台上。

5. DirectFBDirectFB 是特别为 Linux FrameBuffer加速的一个图形库,正在尝试建立一个兼容 GT

K ( GIMP Toolkit )的嵌入式 GUI 系统。

Page 12: 第 9 章   嵌入式 Linux 用户图形界面编程

9.2 嵌入式 Linux 图形用户界面简介

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 。

Qt/Embedded

Page 13: 第 9 章   嵌入式 Linux 用户图形界面编程

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 仍在每个客户机上同步运行,这意味着一旦发送了客户机请求包,服务器在为另一个客户机提供服务之前一直等待,直到整个包都到达为止。这使服务器代码非常简单,而运行的速度仍非常快。

Page 14: 第 9 章   嵌入式 Linux 用户图形界面编程

MiniGUIMiniGUI 是自由软件项目(遵循 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校正,以及高级图形功能(椭圆、多边形、样条曲线),等等。

Page 15: 第 9 章   嵌入式 Linux 用户图形界面编程

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 平台的实时系统,可移植性稍差,目前的发展也基本停滞。

Page 16: 第 9 章   嵌入式 Linux 用户图形界面编程

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 平台。

Page 17: 第 9 章   嵌入式 Linux 用户图形界面编程
Page 18: 第 9 章   嵌入式 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 所示。

Page 19: 第 9 章   嵌入式 Linux 用户图形界面编程

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完全面向对象的做法。

Page 20: 第 9 章   嵌入式 Linux 用户图形界面编程

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 ,可以支持更多的客户键盘和其他非指示设备。

Page 21: 第 9 章   嵌入式 Linux 用户图形界面编程

在一个无键盘的设备上,输入法成了惟一的字符输入手段。 Qtopia 提供了 4种输入法:笔迹识别器、图形化的标准键盘、 Unicode 键盘和基于字典方式提取的键盘。

Page 22: 第 9 章   嵌入式 Linux 用户图形界面编程

创建 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.gzexport TMAKEDIR=$PWD/tmake-1.11export TMAKEPATH=$TMAKEDIR/lib/qws/linux-x86-g++export PATH=$TMAKEDIR/bin:$PATH

Page 23: 第 9 章   嵌入式 Linux 用户图形界面编程

3.安装 Qt/Embedded2.3.7在 Linux命令模式下运行以下命令:tar xfz qt-embedded-2.3.7.tar.gzcd qt-2.3.7export QtDIR=$PWDexport QtEDIR=$QtDIRexport PATH=$QtDIR/bin:$PATHexport LD_LIBRARY_PATH=$QtDIR/lib:$LD_LIBRARY_PATHcp $QPEDIR/src/qt/qconfig-qpe.h src/tools/./configure -qconfig qpe -qvfb -depths 4,8,16,32make sub-srccd ..

“./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在 Linux命令模式下运行以下命令:tar xfz qt-x11-2.3.2.tar.gzcd qt-2.3.2export QtDIR=$PWDexport PATH=$QtDIR/bin:$PATHexport LD_LIBRARY_PATH=$QtDIR/lib:$LD_LIBRARY_PATH./configure -no-openglmakemake -C tools/qvfbmv tools/qvfb/qvfb bincp bin/uic $QtEDIR/bincd ..

根据开发者本身的开发环境,也可以在 configure 的参数中添加其他参数,比如, -no-opengl 或 -no-xfs ,可以键入 ./configure -help 来获得一些帮助信息。

Page 24: 第 9 章   嵌入式 Linux 用户图形界面编程

5.安装 Qtopia在 Linux命令模式下运行以下命令:tar xfz qtopia-free-1.7.0.tar.gzcd qtopia-free-1.7.xexport QtDIR=$QtEDIRexport QPEDIR=$PWDexport PATH=$QPEDIR/bin:$PATHcd src./configuremakecd ../..

6.安装 Qtopia桌面cd qtopia-free-1.7.x/srcexport QtDIR=$QtEDIR./configure -qtopiadesktopmakemv qtopiadesktop/bin/qtopiadesktop ../bincd ..

为了方便使用,我们把以上各步骤的操作编译成了 build脚本,内容如下所示:#!/bin/bashtar xfvz tmake-1.11.tar.gztar xfvz qt-embedded-2.3.7.tar.gztar xfvz qtopia-free-1.7.0.tar.gztar xfvz qt-x11-2.3.2.tar.gzmv tmake-1.11 tmakemv qt-2.3.7/ qtmv qtopia-free-1.7.0 qtopiamv qt-2.3.2 qt-x11cd qt-x11export QtDIR=$PWDecho yes | ./configure -static -no-xft -no-opengl -no-smmake -C src/moccp src/moc/moc binmake -C srcmake -C tools/designermake -C tools/qvfbcp tools/qvfb/qvfb binstrip bin/uic bin/moc bin/designer bin/qvfbcd ..

Page 25: 第 9 章   嵌入式 Linux 用户图形界面编程

cp qt-x11/bin/?* qt/binrm -fr qt-x11export QtDIR=$PWD/qtexport QPEDIR=$PWD/qtopiaexport TMAKEDIR=$PWD/tmakeexport TMAKEPATH=$TMAKEDIR/lib/qws/linux-generic-g++export PATH=$QtDIR/bin:$QPEDIR/bin:$TMAKEDIR/bin:$PATHcd qtmake cleancp ../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 srccd ..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 )上。

Page 26: 第 9 章   嵌入式 Linux 用户图形界面编程

Qt/Embedded 的使用 1.信号与槽信号与槽提供了对象间通信的机制。它们易懂易用,并且 Qt 设计器能够完整支持。图形用户接口的应用程序能响应用户的动作。例如,当用户单击一个菜单项或工

具栏按钮时,程序就会执行某些代码。大多数情况下,我们需要不同的对象之间能够通信。程序员必须将事件与相关的代码关联起来。以前的开发工具包使用的事件响应机制很容易崩溃、不够健全,同时也不是面向对象的。而 Trolltech 发明了一套叫做“信号与槽”的解决方案。信号与槽是一种强有力的对象间通信机制,这种机制既灵活,又面向对象,并且用 C++ 来实现,完全可以取代传统工具中的回调和消息映射机制。

以前,使用回调函数机制关联某段响应代码和一个按钮的动作时,需要将相应代码函数指针传递给按钮。当按钮被单击时,函数被调用。对于这种方式不能保证回调函数被执行时传递的参数都有着正确的类型,很容易造成进程崩溃。并且回调方式将GUI元素与其功能紧紧地捆绑在一起,使开发独立的类变得很困难。

Qt 的信号与槽机制则不同, Qt 的窗口在事件发生后会激发信号。例如,当一个按钮被单击时会激发 clicked信号。程序员通过创建一个函数(称做一个槽)并调用connect() 函数来连接信号,这样就可以将信号与槽连接起来。信号与槽机制不需要类之间相互知道细节,这使得开发代码可高度重用的类变得更加容易。因为这种机制是类型安全的,类型错误被当成警告并且不会引起崩溃。信号与槽连接的示意图如图 9.8 所示。如果一个退出按钮的 clicked()信号被连接

到一个应用程序的退出函数 quit()槽,用户就可以单击退出键来终止这个应用程序。代码可以这样写:

connect(button.SIGNAL(clicked()),qApp,SLOT(quit()));

Page 27: 第 9 章   嵌入式 Linux 用户图形界面编程
Page 28: 第 9 章   嵌入式 Linux 用户图形界面编程

在 Qt 程序执行期间,是可以随时增加或撤销信号与槽的连接的。它是类型安全的,可以重载或重新实现,并且可以在类的 public 、 protected 或 private 区出现。

例 9.1 :信号与槽连接的一个实例。如果要使用信号与槽机制,一个类必须继承 QObject 或它的一个子类,并且在定义这个类时包含 Q_OBJECT宏。信号在 signals 区间里声明,而槽可以在 public slots , protected slots 或 private slots 区间声明。以下是子类化 QObject 的一个例子:

class BankAccount : public QObject{ Q_OBJECTpublic: 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++代码。

Page 29: 第 9 章   嵌入式 Linux 用户图形界面编程

例 9.2 :这是一个怎样连接两个 BankAccount 对象的实例:BankAccount x,y;connect(&x,SIGNAL(balanceChanged(int)),&y,SLOT(setBalance(i

nt)));x.setBalance(2450);当 x 的 balance被设置为 2450 时, balanceChanged()信号被激

发,这个信号被 y 的 setBalance()槽接收后, y 的 balance 也被设置为 2450 。

一个对象的信号可以连接到许多不同的槽,多个信号也可以连接到特定对象的一个槽。连接在具有相同参数的信号与槽之间建立,槽的参数可以比信号少,多余的参数会被忽略。信号与槽机制是在标准 C++ 中实现的,是使用 Qt工具包中的 C++

预处理器和元对象编译器( Meta Object Compiler , moc )来实现的。moc读取程序头文件并产生必要的支持信号与槽机制的代码。 qmake产生的 Makefiles 会将moc 自动加入进去,开发者无需编辑甚至无需查看这些产生的代码。

Page 30: 第 9 章   嵌入式 Linux 用户图形界面编程

2.窗口部件Qt拥有一系列能满足不同需求的窗口部件,如按钮、滚动条等。 Qt 的窗口部

件使用很灵活,能够适应子类化的特殊要求。Qt 中有 3 个主要的基类: QObject 、 QTimer 和 QWidget 。窗口部件是 QWid

get 或其子类的实例,自定义的部件则通过子类继承得来,继承关系如图 9.9 所示。

Page 31: 第 9 章   嵌入式 Linux 用户图形界面编程

( 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 程序,为了使大家便于理解,对程序的代码一行一行

地解说。#include <qapplication.h>这一行引用了包含 QApplication 类定义的头文件。在每一个使用 Qt 的应用程序中

都必须使用一个 QApplication 对象。 QApplication 管理了各种各样的应用程序的广泛资源,比如默认的字体和光标,等等。

Page 32: 第 9 章   嵌入式 Linux 用户图形界面编程

#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 接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。

Page 33: 第 9 章   嵌入式 Linux 用户图形界面编程
Page 34: 第 9 章   嵌入式 Linux 用户图形界面编程

( 2 )画布。QCanvas 类提供一个 2D 图形的高级接口。它能够处理大量的画布项目来描述直线、

矩形、椭圆、文本、位图,以及动画等。画布项目很容易做成交互式界面,例如,支持用户移动等。

画布项目是 QCanvasItem子类的实例。它们比窗口部件轻巧得多,能很快地移动、隐藏和显示。 QCanvas 可以有效地支持冲突检测,还能罗列出指定区域中的所有画布项目。 QCanvasItem 可以被子类化,用以提供自定义的项目类型或扩充已有类型的功能。

QCanvas 对象由 QCanvasView 对象绘制, QCanvasView 对象能以不同的译文、比例、角度和剪切方式显示同一个 QCanvas 。 QCanvas 是数据表现方式的典范,它可被用来绘制路线地图和展示网络拓扑,也适合开发快节奏有大量角色的 2D游戏。

( 3 )自定义窗口部件。通过对 QWidget 或它的一个子类进行子类化,我们可以创建自己的窗口部件或对话

框。为了举例说明子类化,下面提供了数字钟部件的完整代码。如图 9.16 所示为数字钟图片显示。

数字钟部件是一个能显示当前时间并自动更新的 LCD ,冒号分隔符随秒数的流逝而闪烁。

Page 35: 第 9 章   嵌入式 Linux 用户图形界面编程

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 *){showTime();}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() 来让冒号闪烁时,冒号就被空白代替。

Page 36: 第 9 章   嵌入式 Linux 用户图形界面编程

文件 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();}这个样例程序包括了单个部件(数字钟)并且没有子部件。通过布局来联

合部件可以产生复杂部件。如果需要也可以从头开始编写自己的部件。例如,要创建一个类似于钟表的部件,可能需要在代码中绘制钟面和指针而不是依靠已经实现的基本类。

Page 37: 第 9 章   嵌入式 Linux 用户图形界面编程

3.主窗口QMainWindow 类为应用程序提供了一个典型的主窗口框架。一个主窗口包括一系列

标准部件,顶部包含一个菜单栏,菜单栏下放置这一个工具栏,在主窗口的下方有一个状态栏。工具栏可以任意放置在中心区域的四边,也可以拖拽到工具栏区域以外,作为独立的

浮动工具托盘。QToolButton 类实现了具有一个图标,一个 3D框架和一个可选标签的工具栏按钮。切

换型工具栏按钮可以打开或关闭某些特征,其他的按钮则会执行一个命令,也能触发弹出式菜单。 QToolButton 可以为不同的模式(活动、关闭、开启等)和状态(打开、关闭等)提供不同的图标。如果只提供一个图标, Qt 能根据可视化线索自动地辨别状态,例如,将禁用的按钮变灰。

QToolButton通常在 QToolBar 内并排出现。一个程序可含有任意数量的工具栏并且用户可以自由移动它们。工具栏可以包括几乎所有部件,例如, QComboBox 和 QSpinBox 。

主窗口的中间区域可以包含多个其他窗体。

Page 38: 第 9 章   嵌入式 Linux 用户图形界面编程

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);

Page 39: 第 9 章   嵌入式 Linux 用户图形界面编程

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” 的标签和剩下的部件。

Page 40: 第 9 章   嵌入式 Linux 用户图形界面编程

提示工具和“这是什么?”帮助以旁述的方式阐述了用户接口的使用方法。工具提示是当鼠标指针在一个部件上徘徊时自动显示的小黄色矩形。工具提示通

常用来解释一个工具栏按钮,因为工具栏按钮很少显示文本标签。下面是设置“ Save” 按钮工具提示的例子:

QToolTip::add(saveButton,"Save");当工具提示显示的时候也可以让状态栏同时显示一个长文本。“这是什么”帮助类似于工具提示,但它需要用户主动发出请求,比如,按下 S

hift+F1 ,或者单击一个部件或菜单项。“这是什么?”帮助通常会比工具提示长。下面是为“ Save”工具栏按钮设置“这是什么?”帮助的方法:

QWhatsThis::add(saveButton,"Saves the current file.");QToolTip 和 QWhatsThis 类提供了可以重新实现的虚函数,来获取更多特殊化

行为,比如,根据鼠标在部件的不同位置来显示相应的文本。对于多文档界面( MDI )程序,中心区域可以包括任何部件;多文档界面( MD

I )由 QWorkspace 类提供,可被用做一个 QMainWindow 的中心部件。例如,一个文本编辑器可以把 QTextEdit 作为中心部件:

QTextEdit *editor=new QTextEdit(mainWindow);mainWindow->setCentralWidget(editor);QWorkspace 的子部件可以是任意类型,它们可以用类似定级部件的边框来修饰。

Show() 、 hide() 、 showMaximized() 和 setCaption() 等函数以同样的方式作用于 MDI子部件和顶层部件。

QWorkspace 提供了诸如层叠与平铺等放置策略。如果一个子部件扩展到了 MDI区域的外部,滚动条就会自动出现。如果一个子部件最大化,其边框按钮(如最小化按钮)就会出现在菜单栏中。

Page 41: 第 9 章   嵌入式 Linux 用户图形界面编程
Page 42: 第 9 章   嵌入式 Linux 用户图形界面编程

在“ <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() ,就可以在一个部件中手动实现布局。

Page 43: 第 9 章   嵌入式 Linux 用户图形界面编程

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文件在运行时载入。

Page 44: 第 9 章   嵌入式 Linux 用户图形界面编程
Page 45: 第 9 章   嵌入式 Linux 用户图形界面编程

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二进制代码库。当应用程序发布到目标板上,并能正确运行时,即可宣告开发过程结束。

Page 46: 第 9 章   嵌入式 Linux 用户图形界面编程
Page 47: 第 9 章   嵌入式 Linux 用户图形界面编程

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 tmakemv qt-2.3.7/ qtmv qtopia-free-1.7.0 qtopiamv qt-2.3.2 qt-x11cd qt-x11export QTDIR=$PWDecho yes | ./configure -static -no-xft -no-opengl -no-smmake -C src/moccp src/moc/moc binmake -C srcmake -C tools/designermake -C tools/qvfbcp tools/qvfb/qvfb binstrip bin/uic bin/moc bin/designer bin/qvfbcd ..cp qt-x11/bin/?* qt/binrm -fr qt-x11export QTDIR=$PWD/qtexport QPEDIR=$PWD/qtopiaexport TMAKEDIR=$PWD/tmakeexport TMAKEPATH=$TMAKEDIR/lib/qws/linux-arm-g++export PATH=$QTDIR/bin:$QPEDIR/bin:$TMAKEDIR/bin:$PATHcd qtmake cleancp ../qtopia/src/qt/qconfig-qpe.h src/tools/(echo yes ; echo no) | ./configure -platform linux-arm-g++ -qconfig qpe -depths 16,24,32 make -C srccd ..cd qtopia/src./configure -platform linux-arm-g++make

Page 48: 第 9 章   嵌入式 Linux 用户图形界面编程

基于 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 的文件。

Page 49: 第 9 章   嵌入式 Linux 用户图形界面编程

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 。

Page 50: 第 9 章   嵌入式 Linux 用户图形界面编程
Page 51: 第 9 章   嵌入式 Linux 用户图形界面编程

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_OBJECTpublic: HelloBaseForm( QWidget* parent = 0, const char* name = 0, WFlags fl

= 0 ); ~HelloBaseForm(); QLabel* MessageLabel; QPushButton* PushButton1;protected slots: virtual void SayHello();};#endif // HELLOBASEFORM_H

hello_base.cpp代码如下:

#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!" );}

Page 52: 第 9 章   嵌入式 Linux 用户图形界面编程

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_Hhello.cpp文件代码如下:#include "hello.h"#include <qlabel.h>HelloForm::HelloForm( QWidget* parent, const char* name, WFlags f

l):HelloBaseForm(parent, name, fl){}HelloForm::~HelloForm(){}void HelloForm::SayHello(){

MessageLabel->setText("Hello, Qtopia world!");}

4.继承类源文件创建 hello 类继承基类 hello_Base ,重构 SayHello() 函数,单击 QPushButt

on 时在 QLabel 上显示窗体上显示“ Hello, Qtopia world!” 。

Page 53: 第 9 章   嵌入式 Linux 用户图形界面编程

5.编写主函数 main()一个 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

Page 54: 第 9 章   嵌入式 Linux 用户图形界面编程

针对本工程的 hello.pro文件为:TEMPLATE = appDESTDIR = $(QPEDIR)/binCONFIG += qtopia warn_on releaseHEADERS = hello.hSOURCES = hello.cpp\ main.cppINTERFACES = hello_base.ui

TARGET = hellohello.pro工程文件中描述产生了 makefile文件的配置模板是 app.t ,配置选项是“ qt

warn_on release” 。( 1 )配置模板。tmake 发行版本中 makefile 有 app.t 、 lib.t 和 subdirs.t 3 个模板。应用程序模板( ap

p.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 ,当程序文件太多时,可以使用它。它将所列出的文件夹全部包括进去,并进行编译。

Page 55: 第 9 章   嵌入式 Linux 用户图形界面编程

( 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 = appCONFIG   = qt opengl debugHEADERS  = hello.hSOURCES  = hello.cpp main.cppTARGET   = hello

Page 56: 第 9 章   嵌入式 Linux 用户图形界面编程

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

Page 57: 第 9 章   嵌入式 Linux 用户图形界面编程

9.程序运行对生成的二进制文件单独运行:$qvfb -width 640 -height 480 &$hello –qws

Page 58: 第 9 章   嵌入式 Linux 用户图形界面编程

重新登录后,需要设置 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 ProgramExec=helloIcon=HelloType=ApplicationName=Hello2410将 hello.desktop保存到应用程序配置目录 $QPEDIR/apps/applications ,然后

运行虚拟 Framebuffer :#qvfb -width 640 -height 480 &然后执行“ qpe” ,在 qpotia 中出现 hello 程序,单击即可运行程序。

Page 59: 第 9 章   嵌入式 Linux 用户图形界面编程

发布 Qt/Embedded 程序到目标板 设计 Qt/Embedded 的目的就是发布到目标板上去,源代码不做任何修改,

只需要修改 hello 的 makefile文件,修改部分如下:SYSCONF_CXX = arm-linux-g++SYSCONF_CC = arm-linux-gccDASHCROSS = -armSYSCONF_LINK = arm-linux-gccSYSCONF_LIBS = -lmSYSCONF_LINK_SHLIB = arm-linux-gccSYSCONF_AR = arm-linux-ar cqsMOC_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 ; donecd $(QPEDIR)/src; ./configure -platform linux-arm-g++ -make /friendly-

arm/gui/free-qtopia-arm/hello所修改的只是编译器和目录相关部分。进入 hello 目录,重新编译完毕, hell

o将生成并保存在 arm-qtopia/qtopia/bin 目录。将可执行文件下载到目标板的 /opt/qtopia/bin 目录和 /opt/qtopia/apps/Applications/ 目录。

重新启动后,就会看到 hello 程序已经存在,单击运行即可。