Programming Microsoft Windows CE.NET, Third Edition by Douglas Boling Microsoft Press 2003 (1224 pages) ISBN:0735618844 作者对如何把 Windows 嵌入式程序设计经验应用到 Windows CE.NET 环境里做了娴熟的示范 这这个环境里, 可以为支持 Windows 的 Pocket PC Smartphone 以及其它下一代设备编写更快, 更小的应用 目录前言第一部分 - Windows 程序设计基础第 1 章 - "Hello Windows CE" 第 2 章 - 屏幕绘画第 3 章 - 输入 : 键盘 鼠标和触摸屏第 4 章 - 窗口 控件和菜单第 5 章 - 公共控件和 Windows CE 第 6 章 - 对话狂和属性表第二部分 - Windows CE 程序设计第 7 章 - 内存管理第 8 章 - 文件和注册第 9 章 - Windows CE 数据库第 10 章 - 模块 进程和线程第 11 章 - 通知机制第三部分 - 通信第 12 章 - 串口通信第 13 章 - Windows CE 网络通信第 14 章 - 设备间通信第 15 章 - 桌面连接第四部分设备编程第 16 章 - 浏览器 Shell 编程第 17 章 - Pocket PC 编程第 18 章 - Pocket PC 扩展第 19 章 - Smartphone 编程第 20 章 - 游戏 API-GAPI 第五部分 - Windows CE 高级编程第 21 章 - 系统编程第 22 章 - 设备驱动程序和服务编程第 23 章 -.Net CF 下的程序设计索引 封底 用这本广受业界推崇的 Windows CE.NET 参考书, 为最新的智能移动设备设计整洁 高性能的 应用程序 在第 3 版里, 基本内容已经根据 Windows CE.NET 4.2 和微软.NET CF 做了更新 作者 Douglas Boling 对如何把 Windows 嵌入式程序设计经验应用到 Windows CE.NET 环 境里做了娴熟的示范 在这个环境里, 可以为支持 Windows 的 Pocket PC Smartphone 以
及其它下一代设备编写更快, 更小的应用 在 Douglas Boling 专家级的指导下, 可以促进提 升你的技术, 更快的适应市场变化 内容要点 : 用特定的技术处理内存 存储和电源限制 能够和网络 PC 其他设备( 包括使用红外线和蓝牙技术的设备 ) 通信 新内容 -- 为 Smartphone 的特殊要求编程, 包括安全特性 新内容 -- 在 Pocket PC Phone 和 Smartphone 上使用短消息服务 (SMS) 写面向连接的应用 新内容 -- 编写自己的设备驱动程序 服务程序和超级服务程序 配置用户和事件通知 使用游戏 API 编程 关于作者 作为一个作家 培训师和顾问,Douglas Boling 从 Windows CE 初始就开始涉足这个领域了, 被公认为这个平台的权威 他频繁的出现在各种主要的专业开发人员大会上, 微软嵌入式开发大会 微软专业开发者大会以及 TechEd 都有他的身影 通过他的 Boling Consulting 公司,Doug 为许多在 Windows CE 上工作的一流公司做教学和咨询 主要的专业开发人员大会上, 微软嵌入式开发大会 微软专业开发者大会以及 TechEd 都有他的身影 通过他的 Boling Consulting 公司,Doug 为许多在 Windows CE 上工作的一流公司做教学和咨询 前言 几乎是从 Windows CE 诞生起, 我就开始在它上面工作了 作为一个多年的 Windows 程序员, 能够将我的 Windows 程序设计经验应用到如此多的不同而又很小的系统上, 使我感到吃惊 这些 Windows CE 系统运行在各个领域, 从类似 PC 的迷你膝上机到蜂窝手机, 再到深藏于一些大型工业设备里的嵌入式设备 Windows CE 里的 Win32 API 使成千上万的 Windows 程序员能够在一个全新的系统领域里写应用程序 然而, 细微的差别, 使得在 Windows CE 中编程不同于在桌面版本的 Windows 中编程 我将在本书里进行讲解的正是这些差异 Windows CE 到底是什么? Windows CE 是最小, 也可能是最有趣的微软 Windows 操作系统 Windows CE 彻头彻尾的被设计成一个基于 ROM 的, 使用 Win32 API 子集的小型操作系统 Windows CE 把 Windows API 扩展到那些不支持大容量 XP 内核的领域和机器上 目前逐步消亡的 Windows 95/98/Me 对那些需要向后兼容 MS-DOS Windows 2.x 和 3.x 程序的用户来说, 是个大操作系统 虽然它有缺点, 但 Windows Me 还是继承了这个困难的任务 另一方面,Windows NT/2000/XP 是用于企业级的 它牺牲了兼容性和大小, 来达到高级别的稳定和健壮 Windows XP 家庭版是 Windows XP 的一个版本, 用于家庭用户, 它尽力保持了兼容性, 但这相对于它的基本目标 -- 稳定性来说, 兼容性还是排在第 2 位的
Windows CE 并不向后兼容 MS-DOS 或者 Windows. 它不是一个为企业级计算设计的全功能的操作系统 相反,Windows CE 是一个轻量级 多线程的操作系统, 它具有可选的图形用户接口 它的长处在于它的小尺寸 Win32 API 子集和多平台支持 Windows CE 也是最初.NET CF 版本的基础,.NET CF 是.NET 在移动和嵌入式设备上的版本 这个精简框架使用一个更小的类库提供了一个同样强大的.NET 运行时环境, 这使得它适合用于电池供电的小型设备上 Win CE 历史简介 为了理解 Windows CE 的历史, 你需要理解操作系统和使用操作系统的产品之间的区别 操作系统是微软内部一组核心团队开发的 他们的产品就是操作系统自身 其他团队, 比如开发 Pocket PC 的团队, 在操作系统即将发布的时候, 获得并使用最新版本的操作系统 这种划分成两部分的方式, 可能对理解 Windows CE 是怎么发展的会产生一些混淆 下面让我们分别看一下这两部分的历史 -- 设备和操作系统自身 设备首批为 Windows CE 设计的是手持式 管理器 设备, 配有 480*240 或 640*240 的屏幕和 Chiclet 键盘 ( 计算机常用的一种键盘, 有一个很小的矩形键盘 ) 这些设备, 称为手持式 PC, 在 1996 年首次面世 在 Fall Comdex 97 大会上,Windows CE 2.0, 一个进行了显著更新的操作系统版本发布了, 和它配套的是类似的更新一些的硬件, 具有 640*240 的横向屏幕, 一些是彩色的, 还具有略大一些的键盘 1998 年 1 月, 消费电子展览会上, 微软宣布了两个平台, 掌上电脑 ( 译者注 :Palm-size PC 可以看作 Pocket PC 的前身, 其中使用 Windows CE 2.x 的称为 Palm-size PC, 使用 Windows CE 3.x 的称为 Pocket PC) 和车载 PC( 译者注 :Auto PC 是 Windows CE 的一种应用模式, 多用在一些车载电脑 工业自动控制等场合, 可按客户需要修改输入输出方式而不限定使用原有的程序 ) 掌上电脑目标直接瞄准了由 Palm 0s 统治的基于笔式的管理器市场 掌上电脑具有纵向显示的特点, 并配有 240*320 的屏幕, 使用输入笔来作为输入方式 但是很不幸, 对 Windows CE 爱好者来说, 对这种原始掌上电脑, 公众显然缺乏热情 此后, 出现了一种新的迷你膝上式 Windows CE 设备, 配备了触摸式输入键盘和 VGA/Super VGA 屏幕 这种设备被称为 H/PC( 专业版 ), 电池可使用 10 小时, 并配备了微软 Pocket Office 软件改进版 这种设备中有许多具有内置 Modem, 一些甚至配备了 thenstandard 屏幕, 移动轨迹垫或者 IBM 轨迹球设备 2000 年 4 月, 微软宣布了 Pocket PC, 它对老式掌上电脑 (Palm-size PC) 做了极大的增强 最初的 Pocket PC 使用预发布的具有更多功能的 Windows CE 3.0 Pocket PC 的用户界面也有所不同, 具有更加整洁的 2 纬外观和修改过的主页 -Today 桌面 然而,Pocket PC 最重要的特性是极大的改进了 Windows CE 的性能 微软做了很多工作来调优 Windows CE 的性能 这些改进加上更快的 CPU, 可以让系统像 Pocket 管理器期望的那样运行的更快 在这种 Pocket PC 里, 摩尔定律使得 Windows CE 设备可以跨越这条线 : 现在硬件已经有能力提供 Windows CE 需要的运算能力了 手持式 PC 在 2000 年时升级到了 Windows CE 3.0 虽然这些系统 ( 现在称为手持式 PC2000) 在消费领域没有成功, 但在工业领域找到了市场 它凭借相对低的费用, 大屏幕和长时间电池能力满足了这个机会市场
2001 年晚些时候,Pocket PC 更新为 Pocket PC 2002 这次发布使用了 Windows CE3.0 的最终发布版本, 并做了一些用户接口方面的改进 同时也增加了 Pocket PC Phone 版本, 在 Pocket PC 设备中集成了蜂窝电话支持功能 这些设备具有了 Pocket PC 的功能, 也具有蜂窝电话的联通功能, 形成了新一代的几乎可以始终连接的移动软件 微软的另一组人发布了 Smart Display( 译者注 : 一种具有触摸屏的无线监视器, 可以通过 802.11b 无线网络连接到个人计算机, 并使用 Windows XP Professional 操作系统的 Remote Desktop ( 远程桌面 ) 来访问主机 ) 它使用 Windows CE.NET 4.1 系统, 具有平板式设备形式, 有无线网络访问能力, 有一个底座可以连接到 PC 上 当使用底座的时候, 它可以做第 2 个显示器 当不用底座的时候, 它可作为 PC 的移动显示器 2003 年春季,Pocket PC 团队发布了升级版的 Pocket PC, 称为 Pocket PC 2003 系统在用户接口方面没有很多变化, 但由于是基于 Windows CE.NET 4.2, 所以在稳定性和性能方面都有了巨大改进 Pocket PC 2003 还集成了蓝牙支持功能,OEM 厂商可以选择是否包含该功能 微软还和 OEM 厂商合作生产基于 Windows CE 的蜂窝电话 这些电话中的少部分被称为 Smartphone( 智能电话 ), 在 2002 年末发布了, 最初是基于 Windows CE 3.0 2003 年升级到了 Windows CE4.2, 并增加了一系列特征, 包括.NET runtime 功能 新设备一直不断在发布, 例如 Media to Go 设备, 就是使用硬盘来存储的移动视频播放器 Windows CE 操作系统的强大功能, 使应用程序凭借运行在这些设备上的简单操作系统超越了系统能力 操作系统 虽然面向消费者的产品可以不断产生新闻热点, 但最重要的开发工作仍然在操作系统自身 Windows CE 从 1.0 之日就开始不断演化, 当它还是一个简单的管理器操作系统时就被寄予厚望 从 Windows CE 2.0 开始一直持续到今日, 微软不断发布 Windows CE 的嵌入式版本, 使开发者可以用在他们自定义的硬件上 虽然像 Pocket PC 等消费平台占据了宣传的主体, 但对基本操作系统的改进是使诸如 Pocket PC 和 Smartphone 之类的设备能够运做的基础 在 Fall Comdex 1997 大会上,Windows CE 2.0 随着手持式 PC 2.0 一起发布了 Windows CE 2.0 增加了网络支持, 包括 Windows 标准网络功能, 支持 NDIS miniport 驱动模式, 以及通用 NE2000 网络卡驱动支持 虽然增加的 COM 被限制在进程内服务, 但可以用来支持脚本编写 引入了新的显示驱动模式, 可以支持像素深度, 而不再是 Windows CE 1.0 中原始的每像素 2 位了 Windows CE 2.0 是 Windows CE 操作系统中第一个与诸如 H/PC 等产品分离, 独立发布的版本 开发者可以购买 Windows CE 嵌入式工具包 (ETK), 它可以让开发者为特殊的硬件平台定制 Windows CE 然而, 用过 ETK 后, 开发者会发现, 这个产品的功能还没有达到它所宣称的目标 随着掌上电脑 (Palm-size PC) 在 1998 年初发布后,Windows CE 又在不断改进 虽然 Windows CE 2.01 没有按 ETK 形式发布, 但它在减少操作系统和应用程序大小方面做的努力还是值得称道的 在 Windows CE 2.01 中,C 运行时库从每个 EXE 和
DLL 都要绑定的静态链接库中去掉了, 放到了操作系统里 这极大的减小了操作系统和应用程序的大小 1998 年 8 月, 微软发布了 H/PC, 配套的发布了操作系统的新版本 -2.11 版 Windows CE 2.11 是从未正式发布的 Windows CE 2.1 的服务升级包 这一年年末,Windows CE 2.11 作为 Windows CE 平台的 2.11 版本, 发布给了嵌入式开发者社区 该版本支持改进的对象存储, 允许要存储的文件大于 4M 还增加了对控制台程序的支持, 同时增加了 MS-DOS 风格的命令行解释器 CMD.exe 的 Windows CE 支持 Windows CE2.11 还增加了 Fast IR, 用于支持 IrDA 的 4M 红外线标准, 同时增加了一些特殊功能来支持 IP 多点传送 最初的安全概念被引入 : 设备可以检查和拒绝装载没有被认证的模块 Windows CE 2.12 也是作为 2.1- 白桦 (Windows CE 2.1 的代号 ) 的服务包发布的 这次发布中最大的亮点是一个增强的平台 Builder 工具集, 它具有一个图形化的前端界面 用一个新的通知接口调整了系统操作, 新的接口将不同的通知功能进行了组合 通知的用户接口暴露在平台 Builder 中, 允许嵌入式应用开发者定制通知对话框 微软基于 PC 的 Internet 浏览器 IE 4.0 也被引入到 Windows CE 中, 称为 Genie- 通用 IE 控件 这个 HTML 浏览器控件完善了简单小巧的 Pocket Internet 浏览器 微软消息队列 (MMQ) 也被加了进去 Windows CE 2.11 中的安全功能 运行 / 不运行 (go/no go) 也增加了 运行, 但不信任 (go,but don't trust) 的选项 这样, 不被信任的模块可以运行, 但不能调用关键功能集, 也不能修改注册表的某些部分 期待已久的 Windows CE 3.0 终于在 2000 年中发布了 这次发布是跟随同年 4 月发布的 Pocket PC 的, 它使用了 Windows CE 3.0 略早一些的内部版本 Windows CE 3.0 最大的亮点在它的内核, 内核为更好的支持实时功能而做了优化 增强后的内核, 支持 256 个线程优先级 ( 之前的版本是 8 个 ), 可调整线程周期, 可嵌套的中断服务程序, 并减少了内核等待时间 Windows CE 3.0 改进的地方不仅仅在内核上 一个新的 COM 组件被加入进来, 用来完善 2.0 就有的进程内 COM 功能 新的组件支持完整的进程外 COM 和 DCOM 功能 对象存储区域也做了改进, 可以支持 256M RAM 了 对象存储区域里的文件大小限制也提高到了 32M/ 文件 Platform Builder 3.0 的附加的软件包加入了更多的功能, 增加了 media player 控件, 提高了多媒体支持 用 PPTP,ICS 和远程桌面显示功能改进了网络支持 还正式引入了 DirectX API Windows CE 接下来的一个版本, 改变的不仅仅是新特性, 产品的名字也改了 2001 年初,Windows CE.NET 4.0 发布了 这个版本里, 改变了虚拟内存的管理方式, 将每个应用程序的虚拟内存空间扩大了 1 倍 Windows CE.NET 4.0 还增加了新的驱动装载模式, 服务 (Services) 支持, 新的基于文件的注册选项, 蓝牙功能,802.11 以及 1394 支持 具有讽刺意味的是,Windows CE.NET 4.0 虽然叫.NET, 但却不支持.NET 精简框架 2001 年末,Windows CE 4.1 跟随 Windows CE 4.0 之后发布了, 增加了 IP v6 支持,Winsock2 支持, 一组支持 applets 的新功能, 以及一个叫 Power Manager 的例子 Windows CE 4.1 支持.NET 精简框架.NET 运行时库作为一个快速修复包 (QFE), 在操作系统发布后提供的
2003 第 2 季度,Windows CE.NET 4.2 发布了 这次升级, 提供了很棒的 OEM 厂商期待的新特性 - 在嵌入式系统上支持 Pocket PC 应用程序 Pocket PC 特有的 API, 比如菜单条 软输入法以及其它解释器特性, 被移植到基本操作系统里了 为支持名字空间扩展, 浏览器 Shell 被重写 通过在某些 CPU 上直接支持硬件分页表功能, 内核性能得到了改进 因为 Windows CE 不断在发展, 下个版本的 Windows CE 正在开发中, 一旦有新版本发布的信息, 我将更新到我的网站 www.bolingconsulting.com 为什么你应该读这本书 微软 Windows CE 程序设计 一书, 是为任何想给 Windows CE 或.NET 精简框架编写应用程序的人写的 嵌入式系统程序员为特定的应用使用 Windows CE,Windows 程序员对写或者移植一个已有的 Windows 程序很感兴趣, 甚至可管理代码的开发者都可以用本书里的信息来使他们的工作更容易 嵌入式系统上的程序员, 他们或许没有 Windows 程序员熟悉 Win32 API, 他们可以读本书第 1 部分的内容来熟悉 Windows 编程 虽然这一部分不如 Windows 程序设计 (Charles Petzold 著 ) 这类书能提供全面的指南, 但它的确提供了一些基本信息, 使读者可以读懂后面的内容 它也为嵌入式系统程序员开发复杂但很有用的 Windows CE 程序提供了帮助 有经验的 Windows 程序员可以通过本书学习 Windows CE 和 Windows XP 中 Win32 API 的差异, 两者之间的差异是很显著的 Windows CE 的小巧意味着 Win32 模式里的很多 API 不能被支持 Win32 API 的某些部分根本不被支持 另一方面, 因为 Windows CE 的特殊性,Windows CE 在许多领域扩展了 Win32 API, 本书将讲述这些领域 本书对使用.NET CF 的开发者也很有用 当前的 CF 在功能上有些缺陷 : 需要用可管理的应用程序来调用操作系统来执行某个任务 本书对从操作系统可以获得什么做了很好的指导 书中有一章节会讨论在基于 Windows CE 的设备中开发可管理代码所涉及的特性 本书通过例子来进行教学 我专门为本书写了许多 Windows CE 示例程序 例子的原代码列在了书中 源代码及最终编译后的程序可以在附书光盘里找到, 这些适合许多 Windows CE 支持的处理器 书中的示例都是直接用 API 写的, 即所谓的 Petzold 编程方式 由于本书的目标是教你如何为 Windows CE 编写程序, 所以示例都避免使用诸如 MFC 等类库, 应为这会使为 Windows CE 写应用程序所涉及的特性产生混乱 一些人可能会认为 Windows CE 中有 MFC, 了解 Windows CE API 的必要性就不那么大了 我并不这么认为, Windows CE API 的知识可以使你更有效的使用 MFC 我认为真正了解操作系统能够有效的简化应用程序的调试 第 3 版新内容第 3 版本做了比较大的修订, 增加了很多重要的内容, 包括 Smartphone 蓝牙等多
个新主题 本书更新了 Windows CE.NET 4.2 的新特性 增加了新的章节, 覆盖 Smartphone 和.NET CF 许多章节做了明显扩充, 覆盖了 OBEX 蓝牙和服务等主题 另有一些章节做了重新整理以更好的描述主题 为 Smartphone 以及 Pocket PC Phone 的通信特性增加了一章内容, 涉及如何为 Smartphone 2003 设备写应用程序 还讲述了在 Smartphone 和 Pocket PC Phone 上, 如何通过 SMS 系统, 使用联接管理器 发送和接收消息功能来写应用程序 新增了一章关于.NET CF 内容的, 涉及如何写 Windows CE 上可管理的应用程序 在此之后, 集中在.NET CF 的特殊类 Windows Form 的应用程序上 本章涉及的一个重要部分是, 当可管理类库不能提供应用所需的功能时, 如何在可管理的代码中调用不可管理或者本地代码 设备之间通信这一章节涉及蓝牙和 OBEX 蓝牙是一个无线通信标准, 但坦白地说, 在许多文章中并没有很好的解释说明 这一章介绍了蓝牙技术并提供了一个简单易懂的使用示例 本章还包括 OBEX 内容,OBEX 是蓝牙和红外线数字联盟使用的对象交换标准 本章另外一个例子演示了如何用 OBEX 通过蓝牙或者红外线数据方式去传送文件到其他设备 Pocket PC 这一章增加了 Pocket PC 2003 设备的新特性 本书第 2 版 Pocket PC 一章中的菜单条例子已经移到了通用控件这一章, 这表明出在最新的 Windows CE 版本中,Pocket PC API 将一些功能移到操作系统中 驱动程序和服务一章也做了更新, 涉及 Windows CE 服务 Windows CE 服务在 Windows CE.NET 4.0 中引入 Windows CE 服务程序为编写后台运行程序提供了方法, 避免为服务使用单独的进程所带来的开销 操作系统还提供超级服务来监控 IP 端口, 并在客户端连接该端口的时候通知服务 本章提供了一个简单的 Windows CE 服务例子, 演示了如何写服务程序和使用超级服务的特性 对本书第 1 版的读者来说, 本书包含了第 2 版的所有新特性 更新内容涉及 Pocket PC 和 Windows CE 设备驱动程序, 以及第 1 版发布后, 实现的新的内容管理和线程特性.NET 精简框架 一个开发者没有听说过微软的.NET 计划, 那他一定是在荒岛上 该计划包括一个运行时环境, 把代码从硬件上隔离开, 同时提供一个类型安全的运行时环境以增加安全性 为嵌入式和电池供电的设备编写了更小一些的运行库.NET 精简框架的最初版本运行在有 Windows CE 的 Pocket PC 和基于 Windows CE.NET 4.1 及以后的嵌入式系统上 嵌入式设备的特殊要求使得在它上面只用可管理的代码写应用程序成为一项挑战 嵌入式应用和一些移动应用要求应用程序和设备紧密的集成在一起 因为运行时库的特性之一就是将应用和硬件隔离, 所以一个嵌入式可管理的应用程序有时需要打破运行时库的限制, 直接访问某些操作系统的功能
正如前面所提到的, 在精简框架这一章节里, 花费了大量时间来讨论如何使可管理的应用程序访问操作系统 讨论涉及到一些技术, 比如通过跨越可管理 / 本地代码边界来聚集参数等, 当然, 在精简构架里完成这个任务比在桌面应用中更困难 关于 MFC 对于问是否该用 MFC 开发 Windows CE 下应用程序的人, 过去, 我通常的回答是 : 不要用 老版的 windows CE 系统加上比较慢的 CPU, 运行复杂 全功能的 MFC 应用会带来很大的压力 但现在, 我不再这么绝对了 新版的 Windows CE 平台足够快, 可以用合理的性能运行基于 MFC 的应用 MFC 运行时库包含在这些设备的 ROM 中, 所以应用程序仅仅是代码, 而不再是代码加 MFC 运行时库 但正像速度和运行库都已经加到平台里去一样,MFC 正逐渐没落 微软不再推荐 MFC 应用开发, 取而代之的是.NET 开发 所以你应该用 MFC 开发吗? 我的回答是不要再用 MFC 开发新项目 对于旧项目, 仍然有机会用 MFC, 但仅仅只是因为这些项目没有移植到其他开发平台上 Windows CE 开发工具本书假设读者了解 C 和熟悉微软 Windows, 在 XP 下用微软嵌入式 Visual C++ 开发过本地程序 为了编译书中的例子程序, 需要微软嵌入式 Visual C++ 4.0, 这可以在附书光盘中找到 需要相关的适合 Windows CE 设备的 SDK, 用于你的目标设备 每个例子已经有一个预先定义好的工程设置, 但你也可以选择从头创建一个工程 对大多数例子来说, 简单创建一个普通 WCE 应用程序工程就可以了 对于那些要求访问 Pocket PC 上特殊功能的例子, 即使整个工程设置不是特别为 Pocket PC 应用定义的, 也可以用特殊的代码链接这些功能 对那些想为 Pocket PC 2000 和 2002 写应用程序的开发者来说, 需要使用嵌入式 Visual C++ 3.0 不幸的是, 附书光盘没有足够的空间同时放 evc3 和 evc4, 但你可以从微软网站下载 evc3 还需要用于这些老版本 Pocket PC 系统的 SDK 一些例子, 例如蓝牙 OBEX 和服务等例子, 使用了老系统里没有的例子 用 Visual Studio.NET 2003 可以开发.NET 精简框架应用程序 因为太大和非免费, 所以没有在光盘里提供 当然, 这个工具是非常高效的开发工具 那些对开发可管理代码感兴趣的人来说, 开发效率提高减轻了升级花费所带来的痛苦 在精简框架这一章, 你需要 Visual Studio.NET 2003 来编译里面的例子 该工具为所有的 Pocket PC 设备以及基于 Windows CE 4.1 以上的嵌入式版本提供了必要的运行时库 目标系统你并不需要 Windows CE 目标设备来体验书中提供的例子 各种 SDK 平台都带有 Windows CE 模拟器, 让你可以在 Windows XP 对 Windows CE 程序下执行基本测试 在你手边没有实际的设备的时候, 可以很方便地使用模拟器 模拟器执行 PC 模拟器中的 Windows CE 版本, 而 PC 模拟器会在 PC 上执行一个实际的 Windows CE 操作系统
在决定使用哪种 Windows CE 硬件来测试的时候, 你应该考虑很多因素 首先, 如果应用程序式一个商业产品, 你至少应该为每种目标 CPU 购买一个系统 你需要在所有目标 CPU 上进行测试, 因为虽然源代码可能相同, 但执行结果可能不同, 并且每种目标 CPU 分配的内存大小也不同 CD 里的内容附书光盘包含了书中所有例子的源代码 我提供了 MS evc 的工程文件, 你可以打开这些预配置的文件 所有的例子都是为 Windows CE 4.2 Pocket PC 2003 和 Smartphone 2003 设计的 除了例子, 光盘中还包含免费的 evc 这和你从微软站点下载或者通过光盘购买的版本一样, 可以把这些工作看作是赠品 还包括了 Pocket PC 2003 的平台 SDK 光盘还包括一个 StartCD 程序, 提供图形化界面, 让你可以访问 CD 里的内容 如果你开了 Windows 自动运行功能, 当你把光盘插到光驱中, 这个程序就会自动运行 如果不能自动运行, 进入 CD 根目录, 运行 StartCD.exe 即可 Readme.txt 文件为 CD 中的内容提供了更多的信息, 介绍了包含的工具和 SDK 的系统要求, 以及对包含产品的支持信息 下面是为了安装和运行 MS evc 所需要的系统要求 注意 evc 是运行在 Windows 2000, Windows XP, 或者 Windows Server 2003 上的 * 带 Pentium 处理器的 PC: 推荐 Pentium 150 MHz 或者更高 *Microsoft Windows XP, Windows 2000 Service Pack 2 (or later) or Windows Server 2003 *32 MB 内存, 推荐 48MB * 硬盘空间 : 最小安装 360MB, 完全安装 720MB * 带光驱 *VGA 或更高分辨率的显示器, 推荐 S VGA * 微软鼠标或兼容鼠标设备 其它资源虽然我试图将本书做成 一站式 的 Windows CE 编程书籍, 但没有一本书可以覆盖各个方面的 通常, 要了解更多 Windows 编程知识, 我推荐经典书籍 --Charles Petzold 写的 Windows 编程 ( 微软出版社,1998) 一书 这是迄今为止最好的 Windows 编程书籍 Charles 提供了例子, 演示如何解决困难而又常见的 Windows 问题 要更多的了解 Win32 核心 API, 我推荐 Jeff Richter 的 Windows 核心编程 (Programming Applications for Microsoft Windows (Microsoft Press, 1999)) Jeff 揭示了进程 线程和内存管理方面的很多细节技术 要了解更多的 MFC 编程知识, 没有比 Jeff Prosise 的 MFC 程序设计 ( Programming Windows with MFC (Microsoft Press, 1999). ) 更好的书籍了 这本书是 MFC 版的 Petzold 编程书籍, 可以说是 MFC 程序员必读 要更多的了解.NET 编程, 我推荐 Charles Petzold 的 C# Windows 程序设计 (Programming Windows with C# (Microsoft Press, 2002)) Charles 将他令
人惊叹的技巧应用到了.NET 框架的 Windows Form 中 这是赶上.NET 客户端编程发展速度的一本非常的好书 WinCE 程序设计 (3rd 版 )-- 第一章 Hello Windows CE- 概述 概述 从经典的 The C Programming Language 一书开始, 传统上编程方面的书都是从 "Hello,world" 程序开始的 这是一个合理恰当的开始 每个程序都有一个基本底层结构, 分析它可以揭示所有运行在这个操作系统上的应用程序都具有的基础, 避一些设计复杂的任务将结构搞的难以理解 在本书 "Hello,world" 一章里, 包含了关于设置和使用编程环境的细节 用于开发微软 Windows CE 应用程序的环境与开发标准微软 Windows 应用程序的环境有些不同, 因为 Windows CE 程序是在运行 XP 的 PC 机器上编写, 却主要在独立的基于 Windows CE 的目标机器上进行调试的 经验丰富的 Windows 程序员可能会跳过这一章进入有更多内容的主题, 但我建议你至少浏览一下本章, 了解标准 Windows 编程和 Windows CE 编程之间的区别 两个开发过程中许多细微而又重要的不同之处和 Windows CE 应用程序的基本构架包含在第一章里 1.1 Windows CE 有什么不同之处 Windows CE 有什么不同之处 Windows CE 有许多特性使得它不同于其它 Windows 平台 首先, 运行 Windows CE 的系统, 大多数可能不使用 Intel x86 兼容微处理器 实际上, Windows CE 运行在 4 种不同的 CPU 系列里 :SHx, MIPS, ARM, 和 x86 幸运的是, 开发环境几乎将各种不同 CPU 的所有差异与程序员隔离开了 Windows CE 程序事先不能确定屏幕或键盘 Pocket PC 设备有一个 240*320 的纵向屏幕, 而其它系统具有传统的 480*240,640*240,640*480 像素分辨率的横向屏幕 一些嵌入式设备则根本没有显示器 一些目标设备可能不支持彩色, 并且大部分 Windows CE 设备用触摸屏替代了鼠标 一些在触摸屏设备上, 轻触屏幕, 表示鼠标左键点击, 没有明显的方法代表鼠标右键 为了能处理右键, Windows 约定, 当轻触屏幕的时候, 按下 Alt 键, 由 Windows CE 程序把这个组合序列解释为鼠标右键点击 Windows CE 设备具有更少的资源运行 Windows CE 系统的目标设备上, 各种资源变化极大 当写一个标准 Windows 程序的时候, 程序员可以对目标设备做许多假定, 并且设备几乎都是 IBM 兼容机 目标设备通常有硬盘用于存储, 同时虚拟内存系统用硬盘作为交换设备来模拟一个几乎没有数量限制的虚拟内存 程序员知道用户有键盘 双键鼠标以及可以当前支持 256 色 至少有 800*600 分辨率的显示器
Windows CE 程序所运行的设备几乎都没有硬盘作为大容量存储 没有硬盘不仅仅意味着没有地方存储大量文件, 也意味着不能交换数据到磁盘上来创建虚拟内存 所以 Windows CE 程序几乎总是在少量内存环境里运行的 因为资源缺乏, 内存分配经常失败 当空闲内存达到一个严重低的级别,Windows CE 可能会自动终止一个程序 RAM 的限制对 Windows CE 程序有很大的影响, 并且是将现有的 Windows 应用程序移植到 Windows CE 过程涉及的主要挑战之一 Unicode 在写 Windows CE 程序时, 程序员可以使用的一个特性是 Unicode. Unicode 是一个字符编码标准, 使用 16 位表示一个字符, 相对的,ASCII 标准是用单个 8 位编码一个字符 Unicode 允许相当简单将程序移植到不同的国际市场, 因为世界上所有已知的字符都可以用 65,536 个 Unicode 值里的一个来表示 处理 Unicode 相对容易, 只要避免假设字符串是用 ASCII 代表和字符是按单个字节存储的 使用 Unicode 的一个结果是每个字符占 2 个字节而不是一个, 字符串长了一倍 程序员必须小心计算缓冲区和字符串的长度 你不能再假设 260 字节可以存储 259 个字符和一个 0 结尾符 作为标准 char 数据类型的替代品, 你应该使用 TCHAR 数据类型 TCHAR 在 MS Windows 95 和 98 中定义为 char 型, 在 Windows 2000,XP,Windows CE 中, 使用 Unicode 功能的程序里,TCHAR 定义为 unsigned short 类型 这些类型定义, 允许在基于 ASCII 和 Unicode 的操作系统上源代码级的兼容 新控件 Windows CE 上有许多为特殊环境设计的新控件 包括命令条 菜单条控件, 提供类似菜单和工具条的功能, 在具有更小屏幕的 Windows CE 设备上, 这些为了节省空间都合并一行里 其它控件也为 Windows CE 做了改进 Windows CE 里的编辑控件 (edit control) 可以设置为自动将单词首字母大写, 这对在无键盘的 PDA 上进行设计是很重要的 对于 Windows 桌面版本里的控件, Windows CE 则提供了大部分 例如, 日期和时间调整控件 日历控件使日历和管理器应用程序更加适合诸如 H/PC 和 Pocket PC 等手持设备 其它
Windows 标准控件做了功能裁减, 这反应了 Windows CE 特殊的硬件系统配 置所具有的紧凑简洁特性 组件化 Windows CE 编程中另一个需要注意的方面是 Windows CE 可以被微软或 OEM 厂商分解和重新配置, 以更好地适应目标市场或设备 Windows 程序员通常只是检查 Windows 版本, 看是否是 Windows 95/98,Me 系列或者 2000,XP 系列 通过获得版本, 程序员可以判断哪些 API 函数可以使用 然而,Windows CE 可以按无数种方式配置 迄今为止,Windows CE 最流行的配置是 Pocket PC 微软定义了具体 Windows CE 组件集合, 这些都体现在所有称为 Pocket PC 的设备上 然而, 一些用 Windows CE 生产的 OEM 产品 --PDA 设备, 并不叫 Pocket PC 这些设备同 Pocket PC 设备在 API 上略微不同 如果你没有意识到这一点, 你很容易写一个程序能运行在一个平台, 却不能运行在另一个平台 在嵌入式平台上, OEM 厂商决定包括什么组件, 并可以为它特定的平台创建一个 SDK 开发包 如果 OEM 厂商对第三方开发感兴趣, 它会为它的设备提供一个可定制的 SDK 包 新的平台不断出现, 它们有许多共同的地方, 也有许多不同之处 程序员需要了解目标平台, 在尝试用一个可能不被设备支持的功能集时, 需要让程序检查在特殊平台上什么函数可用 Win32 子集最后, 因为 Windows CE 比 XP 小很多, 它不能像它大块头的兄弟 XP 那样支持所有的函数调用 当你面对一个不支持打印功能的操作系统, 比如原始模式下的 Windows CE 时, 不要调用任何打印函数,Windows CE 还去除了一些 XP 中支持的冗余功能 虽然 Windows CE 可能不支持你喜欢的功能, 但其它不同的函数集可能会工作的很好 有时 Windows CE 编程似乎主要是用 Windows CE 中稀少的 API 来找出实现一个特性的方式, 虽然成千上万的函数很少被调用 1.2 依然是 Windows 编程
依然是 Windows 编程虽然 Windows CE 和 Windows 的其它版本之间确实存在差异, 但不应该夸大这种差异 编写 Windows CE 应用程序依然是编写 Windows 应用程序 有同样的消息循环, 同样的窗口, 大部分情况下, 具有同样的资源和控件 差异并不会掩盖相同的地方 匈牙利命名方式是重要的相同点之一 匈牙利命名方法自从 Charles Petzold 写 Windows 程序设计 一书开始, 大部分 Windows 程序都采用了匈牙利命名方法, 这是一种传统和良好的命名方法 这种编程风格是 Charles Simonyi 多年前在微软发明的, 它给程序里每个变量用 1 到 2 个字母的前缀来表示变量的类型 例如, 命名为 Name 的字符串数组应该命名为 szname, 前缀 sz 表示变量类型是以 0 做终止符的字符串 匈牙利命名法的价值在于极大的提高了源程序的可读性 其它的程序员, 或者你看一段代码, 不应该重复的看变量声明来判定变量的类型 表 1-1 列出了变量典型的匈牙利前缀 表 1-1 变量的匈牙利前缀 变量类型 Integer Word (16-bit) Double word (32-bit unsigned) Long (32-bit signed) Char String Pointer Long pointer Handle Window handle Struct size 匈牙利前缀 i or n w or s Dw L C Sz P lp h hwnd cb 你可以看到 Windows 早期的一些痕迹 lp 或者长指针, 在 Intel 16 位编程模 式下, 指针分位短指针 (16 位偏移 ) 和长指针 ( 附加偏移段 ) 其它前缀由类
型的缩写构成 例如, 刷子的句柄通常是 hbr 前缀可以是组合的, 就像 lpsz, 指出一个以 0 结尾的长指针 Windows API 中大部分结构是采用匈牙利表示法来给它们的域命名的 本书通篇使用这种命名法 我鼓励你在你的程序里用这种命名方法 1.3 第一个 Windows CE 程序 第一个 Windows CE 程序说了这么多, 就让我们一起看一下你的第一个 Windows CE 程序吧 列表 1-1 显示了 Hello1-- 为 Windows CE 写的一个简单的 Hello World 应用程序 Listing 1-1: Hello1, 一个简单的 Windows 应用程序 Hello1.cpp ================================================= ===================== Hello1 - A simple application for Windows CE Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include "windows.h" Program entry point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { printf ("Hello World\n"); return 0;
如您所见, 除了程序入口点外, 代码看上去十分类似经典的 Kernighan 和 Ritchie 版程序 在注释之后, 有一行代码 #include "windows.h" 这个文件包含了一组文件, 它们中定义了 Windows API 及其用到的结构和常量 程序的入口点是本程序和标准 C 程序之间最大的差别 作为 C 标准入口 int main (char **argv, int argc) 的替代品,Windows CE 通过标准 Windows 入口点 [ 注 1] int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow); 构建程序环境 Windows CE 在一些方面不同于桌面版的 Windows 第 1 个参数,hInstance 为其它应用程序指明具程序实例, 也为需要验证这个 EXE 的 Windows API 函数提供了程序实例 参数 hprevinstance 是从旧的 Win16 API(Windows 3.1 及更早的系统 ) 遗留下来的 包括 Windows CE 在内的所有 Win32 操作系统中,hPrevInstance 始终是 0, 可以忽略掉 参数 lpcmdline 指向一个 Unicode 字符串, 包含了命令行文本 从微软 Windows CE 浏览器启动的应用程序没有命令行参数, 但在某些情况下, 例如当系统自动启动一个程序时, 系统包含一个命令行参数来说明程序为什么被启动 参数 lpcmdline 是 Windows CE 与 XP 出现差异的情况之一, 在 Windows CE 里, 命令行字符串是 Unicode 字符串, 在其它版本的 Windows 里, 命令行字符串总是 ASCII 的 最后一个参数,nCmdShow, 指明了程序主窗口的初始状态 由父进程, 通常是浏览器传递这个参数到程序里 它对应用程序如何配置主窗口给出了说明 该参数可能会规定窗口最初显示成图标 (SW_SHOWMINIMIZE), 或者显示最大化 (SW_SHOWMAXIMIZED) 来覆盖整个桌面, 或者显示为普通 (SW_RESTORE) 大小来表明窗口按标准可变大小方式显示在屏幕上 其它值
规定窗口初始状态对用户不可见, 或者规定窗口可见但不能成为活动窗口 在 Windows CE 里, 这个参数的值被限制成 3 个状态 : 普通 (SW_SHOW) 隐藏 (SW_HIDE) 和非激活显示 (SW_SHOWNOACTIVATE) 除非应用程序需要强制它的窗口预先确定状态, 在程序主窗口被创建后, 这个值是不加修改, 只是简单的传递给 ShowWindow 函数的 下一行是这个应用程序唯一的功能行 : printf ("Hello World\n"); Windows CE 支持大部分标准 C 库, 包括 printf,getchar 等等 这一行有趣的地方在于它不像 Windows CE 其它任何地方, 这个串不是 Unicode 而是 ANSI 下面是对这个现象的合理解释 : 对于用 ANSI 标准编译的 C 标准库,printf 和诸如 strcpy 等其它字符串库函数用的是 ANSI 字符串 当然,Windows CE 支持这些标准函数的 Unicode 版, 例如 wprintf, getwchar, 和 wcscpy 最后, 用 return 0; 结束程序 其它进程可以用 Win32 API 函数 GetExitCodeProcess 获取返回值 构建第一个应用程序要在你的系统上从头创建 Hello1, 可以启动 MS evc++, 在 [File] 菜单上选择 [ 新建 ] 创建一个新工程 当看到新工程的对话框, 可以明显的看到 WinCE 编程与标准 Win32 编程的变化 你有机会选择在图 1-1 中所显示的各种平台 对于非 MFC 或 ATL 工程, 首选是 WCE Pocket PC 应用 ( 用于 Pocket PC) 和 WCE 应用 ( 用于其它 Windows CE 系统 ) 您还可以选择适当的目标 CPU 例如, 选择 Win32(WCE MIPI) 来为使用 MIPS CPU 的 Windows CE 平台编译程序 无论是何种目标设备, 都要确保选择 WCE 模拟器 这样你就可以在用 XP 下的模拟器中运行例子程序了 图 1-1 平台列表使 evc++ 能够面向不同的 Windows CE 平台 接下来,eVC++ 会询问您是否想创建一个空的工程 一个简单程序或者一个 Hello World 应用 对本书里所有的例子, 都选择空工程 这样做是为了避免代码向导向例子添加任何额外的代码 在 [File] 菜单选择 [New],Hello1.cpp 创建新文件
当为 Hello 1 创建或从 CD 复制了合适的源文件, 选择 Win32(WCE x86em) Debug 作为目标平台, 之后 build 这个程序 这一步会编译源代码, 如果没有编译错误的话, 系统自动启动模拟器, 并把 EXE 程序放到模拟器文件系统里, 接下来你可以启动 Hello1 如果你在 Windows 98 或 Me 系统下, 模拟器系统能够会显示一个错误信息, 因为模拟器只能在 Win2000 或 XP 下运行 如果你有 Windows CE 系统, 比如 Pocket PC(PPC), 把 PPC 连接到 PC 上, 方法和同步 PC 的内容到 PPC 一样 打开微软 ActiveSync, 建立 PPC 和 PC 的链接 为了 Windows CE 设备能够运行, 虽然 ActiveSync 链接并不是必须的, 但我发现让它运行可以为开发环境和 Windows CE 系统之间建立一个更稳定的链接 一但 PC 和 Windows CE 设备之间的链接成功和运行, 切换回 evc++, 选择合适的目标设备 ( 如适合 ipaq Pocket PC 的 Win32 [WCE ARM] Debug) 进行编译和 rebuild 和为模拟器创建程序一样, 如果没有错误,eVC++ 自动下载编译程序到远程设备上 程序要么被放到对象存储的根目录上, 要么放在 \windows\start 菜单目中 运行该程序要在嵌入式 Winodows CE 设备 H/PC 上运行 Hello1, 单击手持式 PC 的 My Computer 图标, 找到根目录下的文件, 之后双击应用程序图标启动程序 要在 Pocket PC 上运行程序, 从设备的 Start 菜单选择程序就可以了 因为 evc++ 已经把程序下载到 \windows\start 菜单所在的目录了 这种方式下, 下载的应用程序自动显示在 Start 菜单里 有问题? 如果在 Pocket PC 上点击图标或开始菜单选择 Hello1 程序, 似乎没什么反应 在手持式 PC 上, 程序似乎只是刷新了一下屏幕 这是因为程序启动 写到控制台并结束了 除非你从一个已经创建的控制台启动程序, 否则 Windows CE 会在 Hello1 执行 printf 语句时, 创建一个控制台窗口, 并且在 Hello1 结束的时候自动关闭控制台 在 Pocket PC 上, 应用程序可以运行, 但 Pocket PC 并不为诸如 printf 之类的输出提供控制台显示的功能 通过把驱动程序 console.dll 放到 Pocket PC 的 Windows 目录里, 可以为 Pocket PC 增加控制台支持 驱动程序从驱动程序接口获取输入, 创建屏幕窗口, 打印输出字符串 控制台启动程序可以从手持式 PC 和嵌入式版本的 Windows CE 获得 注 1: 虽然从技术上可以改变入口点原型来匹配 C 的标准入口点, 但通常不值得这么麻烦的 1.4 Hello2
Hello2 既然已经有了基础, 那么是时候把 Hello1 升级一下, 至少应该让人可以看见它 因为很多 Windows CE 系统没有控制台驱动程序,Hello2 创建一个消息框而不是用 printf 来显示 Hello CE 文字 Hello2 的程序如下表 1-2 所示 : 列表 1-2: Hello2, 使用 MessageBox 函数的简单应用程序 Hello2.cpp ================================================= ===================== Hello2 - A simple application for Windows CE Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include "windows.h" Program entry point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { MessageBox (NULL, TEXT ("Hello World"), TEXT ("Hello2"), MB_OK); return 0; 编译并运行 Hello2, 可以看到如图 1-2 所示的小窗口 图 1-2( 略 ) 运行在 Windows CE 桌面的 Hello2 替代 printf 的 MessageBox 函数为 Hello2 提供了 2 个特性 第一个也是最明显的一个就是它 创建一个窗口, 并在上面显示 "Hello World" 文本 第二个特性是 MessageBox 函数直到用户 关闭消息窗口才会返回 这允许 Hello2 一直保持运行, 直到用户关闭窗口 MessageBox 函数原型如下 : int MessageBox (HWND hwnd, LPCTSTR lptext, LPCTSTR lpcaption, UINT utype); 第 1 个参数是顶层窗口的句柄, 它是消息窗口的父窗口 目前我们把这个参数置为 NULL, 因为 Hello2 还没有任何其它窗口 第 2 个参数是准备显示在窗口里的文字 注意传入的字符串是用 TEXT 宏包裹的, 确保它能够编译成 Unicode 版本 第 3 个参数 lpcaption 是显示在窗口标题栏的文字 最后一个参数 utype 是一系列标志位, 规定消息框如何显示在屏幕上 标志位规定了消息框中按扭的数量和类型, 规定了图标的类型以及消息框窗口的风格设置
表 1-2 列出了 Windows CE 下有效的标志位 表 1-2: 默认标志位 标志位 按钮或者图标 用于按钮 MB_OK OK MB_OKCANCEL OK and Cancel MB_RETRYCANCEL Retry and Cancel MB_YESNO Yes and No MB_YESNOCANCEL Yes, No, and Cancel MB_ABORTRETRYIGNORE Abort, Retry, and Ignore 用于图标 MB_ICONEXCLAMATION, MB_ICONWARNING Exclamation point MB_ICONINFORMATION, MB_ICONASTERISK Lower case i within a circle MB_ICONQUESTION Question mark MB_YESNO Yes and No MB_ICONSTOP, MB_ICONERROR, MB_ICONHAND Stop sign MB_DEFBUTTON1 First button MB_DEFBUTTON2 Second button MB_DEFBUTTON3 Third button For Window Styles:
标志位 按钮或者图标 MB_SETFOREGROUND Bring the message box to the foreground. MB_TOPMOST Make the message box the topmost window. MessageBox 的返回值指出用户按了哪个按扭 返回值如下 : IDOK OK button pressed IDYES Yes button pressed IDNO No button pressed IDCANCEL Cancel button pressed or Esc key pressed IDABORT Abort button pressed IDRETRY Retry button pressed IDIGNORE Ignore button pressed 此时值得注意的是, 如果你调试和重新编译这个程序, 它不会被再次下载到目标设备上的, 因为程序早先的版本仍然正在目标系统上运行 换句话说, 当您在 evc++ 中启动一个新的 build 时, 您要确保 Hello2 没有运行在远程系统上, 否则编译过程里的自动下载过程就会失败 如果发生这种情况, 关闭应用程序, 选择 evc++ 里 [Update Remote File] 菜单命令去下载新的编译后的文件 Hello2 展示了一个简单的窗口, 但窗口只能按 MessageBox 函数允许的形式进行配置 如何显示一个完全由程序配置的窗口呢? 在我们这样做之前, 对 Windows 应用程序如何工作的做一个快速浏览是必要的 WinCE 程序设计 (3rd 版 )--1.5 Windows 应用程序剖析 Windows 应用程序剖析基于 Windows 的编程远不同于基于 MS-DOS 或 Unix 的编程 只要是程序需要, 任何时候基 于 MS-DOS 或 Unix 的程序都可以使用 getc- 或 putc 风格的函数从键盘读取字符并写到屏幕 上 这是 MS-DOS 或 Unix 程序所使用的典型的 "Pull"( 拉 ) 风格, 这种风格是面向过程的, 而一个 Windows 程序, 则使用 "Push"( 推 ) 模式 在这种模式下, 必须编写程序来响应来自 操作系统的通知, 比如一个键被压下去了或者收到一个重绘屏幕的命令
Windows 应用程序并不从操作系统请求输入, 而是由操作系统通知应用程序输入产生了 操作 系统通过发送消息 (messages) 给应用程序窗口来完成这些通知 所有窗口都是窗口类的具体 实例 在进一步深入之前, 让我们先确保理解这些术语 窗口类窗口是屏幕上的一个区域, 除了特殊情况, 基本上都是矩形 窗口有一些基本参数, 比如位置参 数 --x,y 和 z( 窗口在屏幕其它窗口之上或者之下 )-- 可视性以及层次关系 -- 窗口与系统桌面形 成父子窗口关系, 系统桌面也是一个窗口 每个被创建的窗口都是窗口类的一个具体实例 窗口类是一个模板, 为该类的所有窗口定义了许 多共同属性 换句话说, 属于同一个类的窗口有同样的属性 这些共享的属性中最重要的是窗口 过程 窗口过程窗口类里窗口过程中的代码定义了同一个类里所有窗口的行为 窗口过程处理发到窗口的所有通 知和请求 这些通知, 要么是操作系统发给窗口, 告诉窗口有事件发生, 窗口必须回应, 要么是 其他窗口发来的, 向该窗口查询信息 这些通知是以消息的形式发送的 消息实际就是对窗口过程的一次调用, 带有参数指出通知或者 查询的种类 当有事件发生, 例如窗口被移动 被改变大小或有键被按下去等, 就会发送消息 标识消息的值由 Windows 定义 应用程序使用预定义好的常量, 例如 WM_CREATE 和 WM_MOVE, 来表示消息 因为有很多消息可被发送, 所以当窗口类对某个消息没有特殊处理 的必要时,Windows 提供了一个默认处理函数来处理传递这些消息 消息生命周期 让我们回头一会, 看一下 Windows 是如何协调发到系统里各个窗口的各种消息的 Windows 监视系统的所有输入, 例如键盘 鼠标 触摸屏以及其它可以产生影响窗口的事件的硬件 当事 件发生后, 消息就被构成并定向给特定的窗口 Windows 没有直接调用窗口过程, 而是加了一 个中间步骤 消息被放到拥有该窗口的应用程序的消息队列里了 当应用程序准备接收消息的时 候, 它把消息从队列里取出来, 并告诉 Windows 发送该消息到应用程序适当的窗口上 你可能会认为这个过程中涉及许多中间步骤, 那么你是对的 就让我们分解一下这个过程吧 1 当事件发生,Windows 就构成一个消息并放到拥有目的窗口的应用程序的消息队列里 和 在 XP 里一样, 在 Windows CE 中, 每个应用程序有自己单独的消息队列 [1] ( 这与 Windows3.1 及更早的 Windows 版本不同, 那时只有唯一一个系统范围内的消息队列 ) 事 件发生及构成一个消息都要比应用程序处理它们的速度快 虽然程序最好能快速响应或者用户希 望看到应用程序快速响应, 但是队列允许应用程序按自己的速率处理消息 消息队列允许 Windows 在运做中设置一个通知并继续完成其它任务, 而不是仅仅限制在只响应收到消息的这 个应用程序 2 应用程序把消息从消息队列中移出来, 并回调 Windows 来分派消息 似乎很奇怪应用程序 从队列里获得消息却只是简单的回调 Windows 来处理这个消息, 对这种方式, 解释如下 : 应用 程序从队列里获取消息, 这使得应用程序在请求 Windows 把消息分派到相应窗口之前, 可以预 处理这些消息 许多情况下, 应用程序会调用 Windows 里不同的函数来处理具体的各种消息 3 Windows 分发消息, 更确切的说, 是 Windows 调用相应的窗口过程 没有让应用程序直 接调用窗口过程, 而是间接调用, 这允许 Windows 协调这个窗口过程的调用与系统里的其它事 件 虽然此刻消息并不在另外一个队列里, 但 Windows 在调用窗口过程之前, 可能需要做一些
预处理 但无论如何, 这种调度方式减少了应用程序的责任, 不用程序去决定适当的目的窗口, 而是由 Windows 负责了 4 窗口过程处理消息 所有的窗口过程都有相同的调用参数: 被调用的窗口实例的句柄 消息参数 两个普通参数, 包含与消息相关的数据 窗口过程用窗口句柄区分窗口的每个实例 消息参数, 指明窗口必须响应的事件 两个普通包含与消息相关的数据 例如,WM_MOVE 消息指出窗口将被移动, 其中一个普通参数指向一个包含窗口新坐标的结构 注 : 技术上,Windows CE 应用程序的每个线程都有一个消息队列 稍后我将在本书里讨论线程 WinCE 程序设计 (3rd 版 )--1.6 Hello3 Hello3 回顾的够多了, 是时候做一个完整的 Windows 应用程序 --Hello3 了 虽然 Hello3 的整个程 序文件以及书中全部例子都可以在附书光盘里找到, 但我还是建议, 对于初期的例子, 您应当避 免简单的从 CD 上装载工程文件, 而是应该手工输入整个例子 通过这种略微有些枯燥的工作, 你会体会到标准 Win32 程序与 Windows CE 程序之间在开发过程的不同以及在程序上的细微 差别 清单 1-3 给出了 Hello3 的全部源代码 清单 1-3: 程序 Hello3 Hello3.cpp ================================================= ===================== Hello3 - A simple application for Windows CE Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM); ================================================= ===================== Program entry point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { WNDCLASS wc; HWND hwnd; MSG msg; Register application main window class. wc.style = 0; Window style
wc.lpfnwndproc = MainWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = TEXT("MyClass"); Window class name if (RegisterClass (&wc) == 0) return -1; Create main window. hwnd = CreateWindowEx(WS_EX_NODRAG, Ex style flags TEXT("MyClass"), Window class TEXT("Hello"), Window title Style flags WS_VISIBLE WS_CAPTION WS_SYSMENU, CW_USEDEFAULT, x position CW_USEDEFAULT, y position CW_USEDEFAULT, Initial width CW_USEDEFAULT, Initial height NULL, Parent NULL, Menu, must be null hinstance, Application instance NULL); Pointer to create parameters if (!IsWindow (hwnd)) return -2; Fail code if not created. Standard show and update calls ShowWindow (hwnd, ncmdshow); UpdateWindow (hwnd); Application message loop while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); Instance cleanup return msg.wparam; ================================================= ===================== MainWndProc - Callback function for application window
LRESULT CALLBACK MainWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PAINTSTRUCT ps; RECT rect; HDC hdc; switch (wmsg) { case WM_PAINT: Get the size of the client rectangle GetClientRect (hwnd, &rect); hdc = BeginPaint (hwnd, &ps); DrawText (hdc, TEXT ("Hello Windows CE!"), -1, &rect, DT_CENTER DT_VCENTER DT_SINGLELINE); EndPaint (hwnd, &ps); return 0; case WM_DESTROY: PostQuitMessage (0); break; return DefWindowProc (hwnd, wmsg, wparam, lparam); Hello3 展示了 Windows 程序的各个方面, 从注册窗口类到创建窗口及窗口过程 和头两个例子一样,Hello3 有着相同的入口点 --WinMain 但是因为 Hello3 创建了自己的窗口, 所以它必须为主窗口注册一个窗口类, 创建窗口并且提供一个消息循环来为窗口处理消息 注册窗口类在 WinMain 中,Hello3 为主窗口注册了窗口类 注册一个窗口类只是简单的填充一个描述窗口类的有些大的结构并调用 RegisterClass 函数 RegisterClass 和 WNDCLASS 结构定义如下 : ATOM RegisterClass (const WNDCLASS *lpwndclass); typedef struct _WNDCLASS { UINT style; WNDPROC lpfnwndproc; int cbclsextra; int cbwndextra; HANDLE hinstance; HICON hicon; HCURSOR hcursor;
HBRUSH hbrbackground; LPCTSTR lpszmenuname; LPCTSTR lpszclassname; WNDCLASS; 给 WNDCLASS 结构各个域赋的值为 Hello3 主窗口的所有实例定义了行为表现 style 域为窗口设置了类的风格 在 Windows CE 中, 类风格被限制为 : CS_GLOBALCLASS 表示类是全局的 这个标志只是出于兼容性才提供的, 因为 Windows CE 中所有窗口类都是进程级全局类 CS_HREDRAW 告诉系统如果窗口改变了水平大小, 就强制重画窗口 CS_VREDRAW 告诉系统如果窗口改变了垂直大小, 就强制重画窗口 CS_NOCLOSE 如果 [ 关闭 ] 按钮出现在标题栏上, 则使其失效 CS_PARENTDC 让窗口使用父窗口的设备环境变量 CS_DBLCLKS 允许 [ 双击 ] 通知 (Windows CE 下敲击两次为双击 ) 传递给父窗口 lpfnwndproc 分配的是窗口的窗口过程的地址 因为该域定义为指向窗口过程的指针, 所以在源代码中, 必须在域被设置之前, 定义该过程的声明 否则, 编译器类型检查时会警告该行 cbclsexra 允许程序员为类结构增加额外的空间来存储只有应用程序才知道的类特定数据 cbwndextra 更加便于使用, 这个域为 Windows 内部结构增加空间, 该结构负责维护窗口每个实例的状态 不在窗口结构本身里存储大量的数据, 应用程序应该存储一个指向应用程序特定结构的指针, 该结构包含窗口每个实例的数据 在 Windows CE 里,cbClsExtra 和 cbwndextra 域必须时 4 字节的倍数 hinstance 域设置为程序的实例句柄, 该句柄指明拥有窗口的进程 hicon 域设置为窗口默认图标的句柄, 但在 Windows CE 中并不支持该域, 所以该域应该设置为 NULL ( 在 Windows CE 中, 会在类的第一个窗口被创建后设置类的图标 对于 Hello3, 没有图标提供, 并且与其它 Windows 版本不同,Windows CE 中没有任何预定义图标用于装载 ) 除非应用程序是为带鼠标的 Windows CE 系统设计的, 否则 hcursor 域应该设置为 NULL 幸运的是, 如果系统不支持光标, 调用 LoadCursor (IDC_ARROW) 函数会返回 NULL hbrbackground 域规定 Windows CE 如何画窗口背景 Windows 用这个域中指定的刷子 brush( 一个小的预定义的像素数组 ) 来画窗口背景 Windows CE 提供许多预定义的刷子, 你可以用 GetStockObject 函数来装载 如果 hbrbackground 域是 NULL, 窗口必须处理 WM_ERASEBKGND 消息, 重画窗口背景 lpszmenuname 域必须设置为 NULL, 因为 Windows CE 不直接支持有菜单的窗口 在 Windows CE 中, 菜单由主窗口创建的命令工具条 命令带或菜单条控件提供 lpszclassname 设置为程序员定义的字符串, 用于为 Windows 指明类的名字 Hello3 用的是 MyClass 做为类名 整个 WNDCLASS 类被填充后,RegisterClass 函数被调用, 并用指向 WNDCLASS 结构的指针作为唯一的参数 如果函数成功, 一个标记窗口类的值被返, 如果失败, 函数返回 0 创建窗口一旦窗口类注册成功, 就可以创建主窗口了 所有 Windows 程序员在他们的我 Windows 编程
生涯里都会学习到 CreateWindow 和 CreateWindowEx 函数调用 CreateWindowEx 的原型如下 : HWND CreateWindowEx (DWORD dwexstyle, LPCTSTR lpclassname, LPCTSTR lpwindowname, DWORD dwstyle, int x, int y, int nwidth, int nheight, HWND hwndparent, HMENU hmenu, HINSTANCE hinstance, LPVOID lpparam); 虽然参数数量让人畏惧, 但是一旦你了解了这些参数, 会发现它们很有逻辑性 第 1 个参数是扩展风格标志位 Windows CE 能够支持的扩展风格标志如下 : WS_EX_TOPMOST 窗口置顶 WS_EX_WINDOWEDGE 窗口有凸起的边框 WS_EX_CLIENTEDGE 窗口有凹陷的边框 WS_EX_STATICEDGE 静态窗口具有 3D 外观 WS_EX_OVERLAPPEDWINDOW 是 WS_EX_WINDOWEDGE 和 WS_EX_CLIENTEDGE 两个风格的组合 WS_EX_CAPTIONOKBUTTON 在标题栏上有 OK 按钮 WS_EX_CONTEXTHELP 在标题栏上有帮助按钮 WS_EX_NOACTIVATE 点击窗口时, 窗口不成为活动窗口 WS_EX_NOANIMATION 当窗口创建的时候, 顶层窗口没有弹出矩形, 在任务条上也没有按钮 WS_EX_NODRAG 防止窗口被移动参数 dwexstyle 是 CreateWindowEx 和 CreateWindow 之间唯一有差别的地方 实际上, 如果你在 Windows CE 头文件里看 CreateWindow 的声明, 会发现 CreateWindow 只是简单的将 dwexstyle 设置为 0 并调用 CreateWindowEx 而已 第 2 个参数是实例化窗口所使用的窗口类的名字 在 Hello3 里, 类名是 MyClass, 也就是用 RegisterClass 注册的类的名字 第 3 个参数用做窗口文本 在 Windows 其它版本里, 它是用做标准窗口标题栏里的文字 在 H/PC 里, 主窗口很少有标题栏, 这个文字只用在任务条按钮上 在 Pocket PC 里, 这个文字出现在显示屏顶部导航条里 文字使用 TEXT 宏, 确保字符串在 Windows CE 下可以转换成 Unicode 风格标志规定了窗口的初始风格 风格标志即用于系统里所有相关窗口的普通风格, 也用于特定类的风格, 比如按钮类或者列表框类的风格 在这种情况下, 我们需要指定的就是用 WS_VISIBLE 标志来指明窗口在创建时可视 经验丰富的 Win32 程序员应该查阅关于 CreateWindow 的文档, 因为许多窗口风格标志在 Windows CE 下并不支持 接下来的四个参数指定了窗口初始位置和大小 因为 Wimdows CE 下大部分应用程序都是全屏窗口, 所以每一个大小和位置域都使用 CW_USEDEFAULT 标志作为默认值 在 Windows CE 当前版本里, 默认值创建的窗口, 其大小覆盖整个屏幕 注意不要为 Windows CE 设备假设任何特殊的尺寸, 因为不同的实现有不同的屏幕尺寸 下一个域填写为父窗口的句柄 因为是顶层窗口, 所以父窗口域设置为 NULL 菜单域也设置为 NULL, 因为 Windows CE 不支持顶层窗口菜单
hinstance 参数就是传递给程序的实例句柄 窗口创建中实例句柄总会用的上, 实例句柄是在程序开始时就被存储的 最后的参数是一个指针, 在 WM_CREATE 消息期间, 用于在 CreateWindow 调用时传递数据给窗口过程 本例中, 没有额外数据需要传递, 所以该参数设置为 NULL 如果创建窗口成功,CreateWindow 返回刚创建的窗口的句柄, 如果函数调用期间有错误发生, 则返回 0 在通过错误检查语句--if 语句之后, 该窗口句柄用在接下来的两个语句里 (ShowWindow 和 UpdateWindow 里 ) ShowWindow 函数修改窗口状态, 使其同传给 WinMain 的参数 ncmdshow 中给的状态一致 UpdateWindow 函数则强制 Windows 发送 WM_PAINT 消息给刚创建的窗口 消息循环主窗口创建后,WinMain 进入消息循环, 这是每个 Windows 应用程序的心脏 Hello3 的消息循环定义如下 : while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); 该循环很简单 : 调用 GetMessage 函数, 从应用程序消息队列中获取下一个消息 如果没有消息可用, 则调用进入等待期, 阻塞应用程序线程直到消息可用 当消息可用, 该函数返回包含在 MSG 结构的消息数据 MSG 结构自身包含几个域, 有的用于识别消息, 有的提供特定消息参数, 有的识别在消息被发送之前, 被笔触摸过的最后屏幕位置点 该位置信息不同于标准 Win32 消息位置数据, 在 XP 下, 返回的位置是当前鼠标位置而不是最后点击 ( 或者 tapped, 在 Windows CE 里 ) 的位置 TranslateMessage 把适当的键盘信息转换成字符信息 ( 后面会讨论其它信息过滤器, 比如 IsDialogMsg )DispatchMessage 接下来告诉 Windows 把消息发给应用程序适当的窗口 获取消息 转换消息 分发消息这个过程会一直循环到 GetMessage 收到 WM_QUIT 消息, 这会使 GetMessage 返回 0, 这一点不同于其它消息 从 while 子句可以看出,GetMessage 返回 0 将导致循环终止 消息循环终止后, 程序除了清理和退出外几乎不做什么 在 Hello3 里, 程序简单的从 WinMain 中返回 WinMain 的返回值成为程序的返回码 传统上, 最后一个消息 WM_QUIT 的 wparam 参数值包含有返回值 为了响应应用程序对 PostQuitMessage 的调用,WM_QUIT 消息被发送出去, 此时 WM_QUIT 的 wparam 参数值被填充 窗口过程发送或提交 (send 或 post 方式 ) 到 Hello3 主窗口的消息被送到 MainWndProc 过程中 和所有窗口过程一样,MainWndProc 原型如下 : LRESULT CALLBACK MainWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam); 返回值类型 LRESULT 实际上就是 long 型 ( 在 Windows 里 long 是一个 32 位值 ), 写成这种形式是为源代码和机器之间提供一个中间级 虽然你可以轻易的从包含文件中确定 Windows
编程时使用的变量的真实类型, 但当你试图把代码做跨平台的转换时会产生问题 虽然了解变量类型的大小对计算内存使用是有用的, 但没有什么好的理由去使用 ( 实际上有很多不使用的理由 ) windows.h 文件中提供的类型定义 CALLBACK 类型指明该函数是 EXE 的外部入口点, 这是 Windows 直接调用该过程所必须的 在桌面系统里,CALLBACK 指出参数是按类 Pascal 风格从右到左方式压进程序栈的, 这和标准 C 语言方式相反 为外部入口点使用 Pascal 语言栈框架的原因可以追朔到 Windows 开发非常早的时期 使用固定大小 Pascal 栈方式, 意味着由被调用的过程来清理栈, 而不是留给调用者来清理 这种方式可以有效的减少 Windows 及其附属程序的大小, 所以早期的微软开发者认为这是一个好的方式 在 Windows CE 里, 应用程序对所有函数都使用 C 栈框架, 不管是否是外部调用 传给窗口过程的第一个参数是窗口句柄, 当您需要定义具体的窗口实例的时候, 这个句柄是很有用的 wmsg 参数表示发给窗口的消息 这不是 WinMain 消息循环里使用的 MSG 结构, 而是一个包含消息值的 unsigned 整型 剩余两个参数,wParam 和 lparam, 传递和具体消息有关的数据给窗口过程 它们的名字来自 Win16 时代, 那时 wparam 是个 16 位值而 lparam 是 32 位值 同其它 Win32 操作系统一样, 在 Windows CE 里, 两个都是 32 位的 和传统的窗口过程一样,Hello3 的窗口过程通过一个 switch 语句解析 wmsg 消息 ID 该 switch 语句包含 2 个 case 语句, 一个用来解析 WM_PAINT 消息, 另一个用来解析 WM_DESTROY 消息 这个窗口过程大概是窗口过程所能简化到及至的一个窗口过程了 WM_PAINT 绘制窗口, 处理 WM_PAINT 消息, 这在任何 Windows 程序中都是很重要的功能之一 窗口的外观是在程序处理 WM_PAINT 消息的过程中完成的 除了用您在注册窗口类时指定的刷子绘制默认背景外,Windows 对处理该消息不提供任何帮助 Hello3 中处理 WM_PAINT 消息如下 : case WM_PAINT: Get the size of the client rectangle GetClientRect (hwnd, &rect); hdc = BeginPaint (hwnd, &ps); DrawText (hdc, TEXT ("Hello Windows CE!"), -1, &rect, DT_CENTER DT_VCENTER DT_SINGLELINE); EndPaint (hwnd, &ps); return 0; 在窗口绘制之前, 程序必须确定窗口大小 在 Windows 程序里, 一个标准窗口被划分为两个区域 -- 非客户区和客户区 窗口标题栏和可变大小的边框通常占据了窗口的非客户区, 这个区域由 Windows 负责绘制 客户区属于窗口的内部区域, 由应用程序负责绘制 应用程序通过调用 GetClientRect 函数来确定客户区的大小和位置 该函数返回一个 RECT 结构, 包含左上角 右下角坐标等描述客户区矩形边界的信息 分成客户区和非客户区的好处是, 应用程序不必绘制那些窗口标准元素, 例如标题栏 其它版本的 Windows 提供一系列 WM_NCxxx 消息, 允许您的应用程序绘制非客户区 在 Windows CE 里, 窗口很少有标题栏 因为很少有非客户区, 所以 Windows CE 不发送非客户端消息给窗口过程
WM_PAINT 消息里执行的所有绘制工作都必须由两个函数 BeginPaint 和 EndPaint 包围 BeginPaint 函数返回设备环境句柄 HDC 设备环境是物理显示设备( 例如视频显示器或打印机 ) 的逻辑代表 Windows 程序从不直接修改显示硬件 相反,Windows 用设备环境将程序与具体硬件隔离开 BeginPaint 填充一个 PAINTSTRUCT 结构, 其结构如下 : typedef struct tagpaintstruct { HDC hdc; BOOL ferase; RECT rcpaint; BOOL frestore; BOOL fincupdate; BYTE rgbreserved[32]; PAINTSTRUCT; hdc 就是 BeginPaint 函数返回的句柄 ferase 指出窗口过程是否需要重画窗口背景 rcpaint 是 RECT 结构, 定义了需要重画的客户区 Hello3 忽略该域, 并假设在每个 WM_PAINT 消息中, 整个客户区窗口都需要重画 当性能是需要考虑的问题时, 该域是很有用的, 因为有时仅仅需要重画部分窗口即可 即使当程序尝试重画 rcpaint 矩形以外的区域时,Windows 也会阻止这么做的 该结构的其它域,fRestore, fincupdate, 和 rgbreserved, 属于 Windows 内部使用, 应用程序可以忽略掉它们 Hello3 中唯一的绘制工作是在窗口绘制一行文本 Hello3 调用 DrawText 函数来完成该绘制 我将在第 2 章描述 DrawText 的细节, 如果您看一下该函数, 很容易会明白这个调用在窗口上绘制了一行字符串 Hello Windows CE 在 DrawText 返回后, 调用 EndPaint 来通知 Windows 程序已经完成了窗口更新 EndPaint 调用同时也使没有被绘制的窗口其它区域有效 Windows 保持一份无效窗口区域 ( 也就是需要重画的区域 ) 列表和有效区域 ( 也就是已经更新的区域 ) 列表 不论您是否在窗口画了什么, 通过成对的调用 BeginPaint 和 EndPaint, 会通知 Windows 由您来处理窗口的无效区域 实际上, 您必须调用 BeginPaint 和 EndPaint, 或者通过其它方式使窗口无效区域变有效, 否则 Windows 会不断发送 WM_PAINT 消息给窗口, 直到无效区域变有效 WM_DESTROY Hello3 中处理的另一个消息是 WM_DESTROY 当窗口即将被销毁时, 该消息被送出 因为该窗口是应用程序主窗口, 当窗口被销毁时应用程序将终止 处理 WM_DESTROY 消息的代码调用 PostQuitMessage 消息来触发该动作 PostQuitMessage 函数将 WM_QUIT 消息放到到消息队列里 该函数的参数是返回码的值, 该值放在 WM_QUIT 消息的 wparam 参数里传回应用程序 如前所述, 消息循环看到 WM_QUIT 消息就会退出循环 WinMain 接着调用 TermInstance, 在 Hello3 里, 该函数什么也不做, 只是返回 WinMain 接着返回, 并终止程序 Hello3 是典型的 Windows 程序 这种编程风格有时也成为 Petzold 式 Windows 编程, 这是为了向 Charles Petzold, 这位 Windows 编程大师表示敬意 Charles 所著的 Windows 程序设计 (Programming Microsoft Windows ) 当前是第 5 版, 并且依然是学习 Windows 编程的最好的书
我更喜欢为我的 Windows 程序选择略微不同的设计 从某种意义上讲, 这是一个将 Windows 程序功能组件化的方法, 这使的更加容易将程序的一部分复制到另一个程序 在本章最终的例子 里, 我介绍这种编程风格, 并一起介绍一些 Windows CE 应用程序所必须的额外特性 WinCE 程序设计 (3rd 版 )--1.7 HelloCE HelloCE Windows 编程中典型的 SDK 风格饱受责难的地方就是在窗口过程中总是使用巨大的 switch 语句 switch 语句分析传给窗口过程的消息, 这样每个消息可以被独立的处理 这种标准结果的优势之一是强制把一个类似的结构加到几乎所有 Windows 应用程序中, 这使一个程序员可以更容易理解另一个人的代码 劣势是整个窗口过程的所有的变量通常会比较杂乱的出现在过程的开头 这么多年来, 我为我的 Windows 程序探索出一个不同的风格 主要想法是将 WinMain 和 WinProc 过程分解成更易理解和更易转换到其它 Windows 程序中的可管理单元 WinMain 被分解成几个过程, 包括执行应用程序初始化 实例初始化和实例终止 作为所有 Windows 程序核心的消息循环也在 WinMain 里 窗口过程被分解为几个独立过程, 每个处理一个具体的消息 窗口过程自身是一个代码框架, 只是查找传入的消息, 看是否有过程来处理这个消息 如果有, 则调用该过程 ; 如果没有, 则把消息传递给默认的窗口过程 把对消息的处理划分成独立的块, 使该结构更容易被理解 并且, 由于将一个消息处理代码段同另一个极大的隔离开来, 可以使您更容易把处理特定消息的代码从一个程序转换到另一个程序 还是在很多年前, 我从 Ray Duncan 在 PC 杂志 的 编程力量 专栏里, 第一次看到对这种结构的描述 在 MS-DOS 和 OS/2 编程领域里,Ray 是传奇人物之一 虽然为了适合我的需要, 我对这种设计做了一些修改, 但这还是应该归功于 Ray 代码 HelloCE 的源代码如清单 1-4 所示 : 清单 1-4 HelloCE 程序 HelloCE.h ================================================= ===================== Header file Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= =============== Returns number of elements #define dim(x) (sizeof(x) / sizeof(x[0])) ---------------------------------------------------------------------- Generic defines and data types
struct decodeuint { Structure associates UINT Code; messages with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); ; struct decodecmd { Structure associates UINT Code; menu IDs with a LRESULT (*Fxn)(HWND, WORD, HWND, WORD); function ; ---------------------------------------------------------------------- Function prototypes HWND InitInstance (HINSTANCE, LPWSTR, int); int TermInstance (HINSTANCE, int); Window procedures LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM); Message handlers LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM); ================================================= ===================== HelloCE - A simple application for Windows CE Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include "helloce.h" Program-specific stuff ---------------------------------------------------------------------- Global data const TCHAR szappname[] = TEXT("HelloCE"); HINSTANCE hinst; Program instance handle Message dispatch table for MainWindowProc const struct decodeuint MainMessages[] = { WM_PAINT, DoPaintMain, WM_DESTROY, DoDestroyMain,
; ================================================= ===================== Program entry point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { MSG msg; int rc = 0; HWND hwndmain; Initialize this instance. hwndmain = InitInstance (hinstance, lpcmdline, ncmdshow); if (hwndmain == 0) return 0x10; Application message loop while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); Instance cleanup return TermInstance (hinstance, msg.wparam); ---------------------------------------------------------------------- InitInstance - Instance initialization HWND InitInstance (HINSTANCE hinstance, LPWSTR lpcmdline, int ncmdshow) { WNDCLASS wc; HWND hwnd; Save program instance handle in global variable. hinst = hinstance; #if defined(win32_platform_pspc) If Pocket PC, only allow one instance of the application hwnd = FindWindow (szappname, NULL); if (hwnd) { SetForegroundWindow ((HWND)(((DWORD)hWnd) 0x01)); return 0; #endif Register application main window class.
wc.style = 0; Window style wc.lpfnwndproc = MainWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = szappname; Window class name if (RegisterClass (&wc) == 0) return 0; Create main window. hwnd = CreateWindow (szappname, Window class TEXT("HelloCE"), Window title Style flags WS_VISIBLE WS_CAPTION WS_SYSMENU, CW_USEDEFAULT, x position CW_USEDEFAULT, y position CW_USEDEFAULT, Initial width CW_USEDEFAULT, Initial height NULL, Parent NULL, Menu, must be null hinstance, Application instance NULL); Pointer to create parameters if (!IsWindow (hwnd)) return 0; Fail code if not created. Standard show and update calls ShowWindow (hwnd, ncmdshow); UpdateWindow (hwnd); return hwnd; ---------------------------------------------------------------------- TermInstance - Program cleanup int TermInstance (HINSTANCE hinstance, int ndefrc) { return ndefrc; ================================================= ===================== Message handling procedures for main window
---------------------------------------------------------------------- MainWndProc - Callback function for application window LRESULT CALLBACK MainWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { INT i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(mainmessages); i++) { if (wmsg == MainMessages[i].Code) return (*MainMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoPaintMain - Process WM_PAINT message for window. LRESULT DoPaintMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PAINTSTRUCT ps; RECT rect; HDC hdc; Get the size of the client rectangle GetClientRect (hwnd, &rect); hdc = BeginPaint (hwnd, &ps); DrawText (hdc, TEXT ("Hello Windows CE!"), -1, &rect, DT_CENTER DT_VCENTER DT_SINGLELINE); EndPaint (hwnd, &ps); return 0; ---------------------------------------------------------------------- DoDestroyMain - Process WM_DESTROY message for window. LRESULT DoDestroyMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PostQuitMessage (0); return 0;
如果您浏览 HelloCE 的源代码, 会从中看到本书中所有程序都使用的标准模板 在包含的头文件和宏定义之后是一些全局变量 我知道有很多好的建议劝说全局变量不应该出现在程序里, 但我使用他们是为了便于简化本书中的例子, 并使它们更条理清晰 每个程序都定义了一个 Unicode 字符串 szappname, 它用在程序的很多地方 hinst 也同样用在很多地方, 并且在详述 InitInstance 过程时, 我会提到它 最后那个全局结构是一个消息列表, 包含处理相关消息的过程 窗口过程使用这个结构将消息及处理消息的过程关联到一起 HelloCE 中,WinMain 有两各基本功能 : 调用 InitInstance( 放有应用程序初始化的代码 ), 在消息循环中处理消息, 当消息循环退出时调用 TerminateInstance 在这个程序模版里, WinMain 成为一个基本不用修改的例程 总的来说,WinMain 唯一的改变之处是对消息循环处理过程的修改, 涉及对键盘加速键的处理以及对无模式对话框消息及其他任务的监控 实例初始化 InitInstance 的主要任务是注册主窗口的窗口类, 创建应用程序主窗口, 按传给 WinMain 的 ncmdshow 参数指定的格式显示主窗口 如果是为 Pocket PC 编译, 则还有一些条件编译代码, 用来防止同一时刻运行一个程序的多个实例 InitInstance 做的第一个工作就是把程序实例句柄 hinstance 保存在全局变量 hinst 中 程序的实例句柄在 Windows 应用程序的很多地方是很有用的 我之所以在这里保存这个值是因为此时已经知道实例句柄并且这是程序中保存实例句柄的一个很方便的地方 当在 Pocket PC 上运行时,HelloCE 用 FindWindow 来查看是否有自身的副本正在运行 该函数在顶层窗口中搜索, 看是否有类名或窗口标题匹配或者两者都匹配的窗口 如果找到匹配的, 用 SetFroegroundWindow 函数将窗口放到最前面, 之后该例程退出, 返回码为 0, 这将使 WinMain 退出, 终止应用程序 我将花更多的时间在 17 章里讨 Pocket PC 相关的代码 Pocket PC 相关的代码包含在 #if and #endif 行里 这些行告诉编译器只有当 #if 语句的条件为真的时候才包含它们, 在本例中, 如果定义了常量 WIN32_PLATFORM_PSPC, 则条件为真 该常量定义在 [ 项目设置 ] 中 快速浏览一下 [ 项目设置 ] 中的 C/C++ 页, 可以看到完整的关于预处理器定义的区域 在这个区域里, 定义之一是 $(CePlatform-Ce 平台 ),$ 注册值占位符 在 Key [HKEY_LOCAL_MACHINE]\Software\Microsoft\Windows CE Tools\Platform Manager 里, 你可以找到一系列注册 key, 每个代表一个在 evc++ 中安装的目标平台 CePlatform(Ce 平台 ) 值取决于目标项目的不同 对 Pocket PC 和老式 Palm-size PC 项目, CePlatform 定义为 WIN32_PLATFORM_PSPC 窗口类的注册和主窗口的创建过程与 Hello3 中的过程很相似 唯一的不同是使用了用做主窗口类类名的全局字符串 szappname 每次我用这个模板, 我将 szappname 改成程序的名字即可 这可以使不同的应用程序窗口类名保持唯一, 方便 FindWindow 工作 到此 InitInstance 算是完成了, 应用程序主窗口已经创建和更新了, 因为在进入消息循环之前, 已经有消息发送给窗口的窗口过程了 现在是时候看看这部分程序了 窗口过程当你写 Windows 程序的时候, 编程的大部分时间是花在窗口过程上的 窗口过程是程序的核心, 也是窗口行为创建程序个性的地方 同大部分不使用 MFC 等类库的 Windows 程序相比, 我的编程风格明显不同的地方正是在窗口过程里 对于我的几乎所有程序, 窗口过程都和前面 HelloCE 里的一样 在进一步深入之前,
我重申 : 这种程序结构并不特别用在 Windows CE 中 我的所有 Windows 应用程序都使用这种风格, 不管是 Windows 3.1 Me XP, 还是 CE 这种风格将窗口构成简化成一个简单的表查找函数 主要思想是在早先定义在 C 文件里的 MainMessages 表里查找消息值 如果消息被找到, 关联的过程就被调用, 并把原始参数传递给处理该消息的过程 如果没有找到, 调用默认过程 DefWindowProc DefWindowProc 是 Windows 函数, 为系统里所有消息提供默认行为, 这使得 Windows 程序不用处理传递给窗口的每个消息 消息表将消息值和处理消息的过程关联在一起 该表如下所示 : Message dispatch table for MainWindowProc const struct decodeuint MainMessages[] = { WM_PAINT, DoPaintMain, WM_DESTROY, DoDestroyMain, ; 该表定义为常量, 这不仅仅是好的编程习惯, 而且因为这样可以帮助节约内存 因为 Windows CE 程序可以在 ROM 里执行, 不变的数据应该标记为常量 这样允许 Windows CE 程序装载器将常量数据放在 ROM 里, 而不是装载一个副本到 RAM 里, 因此节约了宝贵的 RAM 表自身是一个简单的结构数组, 该结构包含 2 个原素 第一项是消息值, 第二项是指向处理消息的函数的指针 因为函数可以起任何名字, 所以在整本书里我使用了一致的结构, 好帮助您理解它们 名字由 Do 前缀 ( 符合面向对象的经验 ), 后跟一个消息名及一个用来指明与表相关的窗口类的后缀 例如 DoPaintMain 是为程序的主窗口处理 Wm_PAINT 消息的函数名 DoPaintMain 和 DoDestroyMain HelloCE 里两个消息处理例程是 PaintMain 和 DoDestroyMain 它们和 Hello3 里 case 子句的功能很类似 独立例程的好处是代码和本地局部变量是例程隔离的 而在 Hello3 的窗口过程里, 针对绘画代码的局部变量则都集中在窗口过程的顶部 代码的封装使您很容易复制代码到您的下一个应用程序里 运行 HelloCE 当在 evc++ 中输入和编译本程序后, 可以在 VC++ 里选择 [Build]->[Execute] HelloCE.exe 或者按 Ctrl+F5 远程执行本程序 程序在空白窗口的中间显示一行 "Hello Windows CE" 字样, 图 1-3 和图 1-4 显示了 HelloCE 运行在 PocketPC 上的样子 点一下标题栏上的 [Close] 按钮会让 Windwos CE 给窗口发送 WM_CLOSE 消息 虽然 HelloCE 没有明确的处理 WM_CLOSE 消息, 但 DefWindowProc 过程使用销毁主窗口作为默认的处理方式 因为窗口正在销毁, 会发出 WM_DESTORY 消息, 这又会导致对 PostQuitMessage 的调用 图 1-3( 略 ) 运行在嵌入式 Windows CE 系统上的 HelloCE 窗口 正象我说过的,HelloCE 是一个非常基本的 Windows CE 程序, 但它给您展示了一个应用程序基本框架, 您可以在上面添加更多内容 如果您用浏览器看 HelloCE.exe 文件, 会发现该程序使用的是通用图标 当 HelloCE 运行的时候, 任务条上 HelloCE 对应的按钮里, 文字旁边也没有图标 关于给程序增加定制图标以及 DrawText 函数是如何工作的, 我将在后面几章作为两个主题来讲解 图 1-4( 略 ) 运行在 Pocket PC 上的 HelloCE 窗口
图 1-4 显示了 HelloCE 在 Pocket PC 上运行时的问题 HelloCE 窗口伸展到了屏幕底部 和程序间具体切换方式有关,SIP( 输入法软键盘 ) 按钮可能显示在 HelloCE 窗口的上面 为 Pocket PC 设计的应用程序会在屏幕底部创建一个菜单条, 用来显示软键盘等事物的按钮会包含在上面 必须人工调整窗口尺寸, 避免覆盖菜单条或者被菜单条覆盖 稍后我们会讨论如何为 Pocket PC 的用户界面设计应用程序 可以确信的是 : 书中头几章关于 Windows CE 的内容一样适用于 Pocket PC 设备及其它 Windows CE 系统 WinCE 程序设计 (3rd 版 )-- 第 2 章屏幕绘图 -- 概述 第 2 章屏幕绘图概述 在第 1 章, 示例程序 HelloCE 完成一项工作 : 在屏幕上显示一行文字 显示这行文字只需要调 用一次 DrawText 即可, 因为 Windows CE 代为处理了很多细节, 例如字体 字体颜色 文本 行在屏幕上的位置等等 借助图形用户接口的力量, 应用程序不只能在屏幕上输出本文行, 还能 做更多的事情 应用程序可以绘制出非常精细的显示外观 纵观微软 Windows 操作系统, 用于绘制屏幕的函数数量发生了巨大的扩展 Windows 每个后 续的版本里, 都增加了许多函数以扩展程序员可以使用的工具集 虽然新函数增加了, 但旧函数 依然被保留, 这样即使有旧函数被新函数取代, 旧程序依然可以继续运行在新版本的 Windows 上 这种函数不断堆积, 旧函数被保留以向后兼容的策略, 在最初的 Windows CE 版本里却被 废弃了 因为需要制作更小版本的 Windows,CE 团队苦览 Win32 API, 并只复制适合 Windwos CE 目标市场的应用程序绝对需要的 API 这种精简对 Win32 API 影响最大的领域之一就是图形函数 到不是您会缺乏用于工作的函数, 只是在 Win32 API 的冗余度方面, 对图形函数做了教大的精简 程序员面临的新挑战之一就是 不同的 Windows CE 平台支持略微不同的 API 集合 Windows CE 图形功能与桌面系统不同 之处, 其中之一就是 Windows CE 不支持不同的映射模式, 而这在其他 Windows 系统里是支 持的 Windows CE 设备环境始终设置为 MM_TEXT 映射模式 坐标转化在 Windows CE 下 也不支持 虽然这些特性在一些类型的应用中很有用, 但在小型便携式设备的 Windows CE 环 境里, 这些需求并不突出 所以当你阅读本章里使用的函数和技术时, 请记住其中一些可能不能 在所有平台上被支持 通过 GetDeviceCaps 函数, 程序可以判断系统支持什么函数 GetDeviceCaps 返回当前图形设备的实际能力 贯穿本章始末, 当判定在目标设备上什么函 数被支持时, 我会谈到 GetDeviceCaps 函数的 像书中第一部分里其它章节一样, 本章回顾 Windows CE 所支持的绘画功能 需要记住的最重 要的事情之一是虽然 Windows CE 不支持全部 Win32 图形 API, 但它的快速发展使它可以支 持一些 Win32 里最新的函数 -- 其中一些非常新, 可能您对它们都不熟悉 本章将为您展示您可 以使用的函数以及如何在这个有一些函数不被 Windows CE 支持的领域里工作 WinCE 程序设计 (3rd 版 )--2.1 绘图基础 绘图基础综观历史,Windows 被细分成三个主要部分 : 核心层, 处理进程和管理内存 ; 用户层, 处理窗 口接口和控件 ; 图形设备接口 (GDI) 负责底层绘制 在 Windows CE 里, 用户层和 GDI 层 合成为图形窗口及事件处理器, 即 GWE 你可能有时会听 Windows CE 程序员谈起 GWE
GWE 并不是什么新事务, 只是标准 Windows 部件的不同包装而已 在本书里, 我通常将 GWE 的图形部分依然称为 GDI, 以保持和标准 Windows 编程术语的一致性 不论你是为 Windows CE 2000 还是 XP 编写程序, 需要做的不仅仅是处理 WM_PAINT 消息这么简单 理解什么时候和为什么 WM_PAINT 消息要被送到窗口是很有益处的 有效和无效区域某些情况下, 窗口的一部分暴露给用户, 这些部分在 Windows 里称为区域, 这些区域被标记为无效 当应用程序消息队列里没有其它消息在等待处理并且应用程序窗口包含有无效区域的时候,Windows 会给窗口发送 WM_PAINT 消息 正如第一章里提到的, 处理 WM_PAINT 消息的绘制操作是包含在 BeginPaint 和 EndPaint 调用之间的 BeginPaint 实际上执行很多动作 首先将无效区域标记为有效, 接下来计算需要裁减的区域 裁减区是限制绘图动作的区域 BeginPaint 接下来发送 WM_ERASEBACKGROUND 消息, 如果需要, 还会重绘背景, 如果用于指示文本输入的光标可见, 它还会隐藏光标 最后 BeginPaint 获得显示设备环境变量的句柄, 该句柄可以用于应用程序中 EndPaint 函数则释放设备环境, 如果必要, 还会重新显示光标 如果 WM_PAINT 处理过程没有更多的操作要执行, 您也必须至少调用 BeginPaint 和 EndPaint, 以便将无效区域标记为有效 作为替代, 您可以调用 ValidateRect 来强制使矩形有效 但这种情况下没有绘制动作发生, 因为应用程序在窗口上绘制任何东西之前, 必须有设备环境句柄 应用程序经常需要强制重画它的窗口 应用程序决不应该邮递或发送 (post or send) WM_PAINT 消息给自身或其它窗口 您应该使用以下函数 : BOOL InvalidateRect (HWND hwnd, const RECT *lprect, BOOL berase); 请注意 InvalidateRect 并不要求窗口设备环境句柄, 只要窗口句柄自身 lprect 表示窗口中需要无效的区域 该参数为 NULL 则表示整个窗口无效 berase 用来指出在调用 BeginPaint 的时候是否重画窗口背景 请注意不像 Windows 的其它版本,Windows CE 要求 hwnd 必须是一个有效的窗口句柄 设备环境设备环境经常被简称为 DC, 它被 Windows 用于管理对显示器和打印机的访问, 当然在本章我将只讨论显示器 除非特别说明, 下面的讨论适合所有 Windows, 而并不具体针对 Windows CE Windows 应用程序从不直接写屏幕 相反, 它们为适当的窗口请求一个显示设备环境句柄, 之后用该句柄在设备环境里绘制 接下来 Windows 作为中间人将像素从 DC 操纵到屏幕上 只应该在 WM_PAINT 消息里调用 BeginPaint, 它为窗口返回一个显示 DC 句柄 通常应用程序在处理 WM_PAINT 消息时在屏幕上绘制东西 Windows 将绘制作为一个低优先级的任务, 这样做是适当的, 因为将绘制作为高优先级会导致为每个小的显示改变都产生一个绘图消息, 这将使绘图消息泛滥 而通过处理所有正在等待的消息, 允许应用程序先完成所有未完成的事务, 这样使所有无效区域能够被有效的一次性绘制完成 用户并不会注意到由 WM_PAINT 消息低优先级所带来的微小延迟 当然, 有些时候是需要立即绘制的 字处理器就是一个例子, 当键盘被按下后, 字处理器需要立即显示对应的字符 为了在 WM_PAINT 消息以外的时刻进行绘制, 可以用 GetDC 函数来获得 DC 句柄 GetDC 原型如下 :HDC GetDC (HWND hwnd);
GetDC 返回窗口客户区 DC 句柄 接下来可以在窗口客户区的任何地方进行绘制, 因为这个过程不像 WM_PAINT 消息的处理过程, 没有裁减区域来限制您只能在一个无效区域里绘制 Windows CE 支持另一个获得 DC 的函数, 该函数如下 : HDC GetDCEx (HWND hwnd, HRGN hrgnclip, DWORD flags); GetDCEx 允许您对返回的设备环境有更多的控制 新参数 hrgnclip 使您可以定义用来限制绘制区域的裁减区域 flags 指出当您在 DC 上绘制的时候,DC 如何反应 注意 Windows CE 不支持下面的标志 :DCX_PARENTCLIP, DCX_NORESETATTRS, DCX_LOCKWINDOWUPDATE, 和 DCX_VALIDATE 当绘制完成后, 必须调用 ReleaseDC 来释放设备环境 ReleaseDC 原型如下 : int ReleaseDC (HWND hwnd, HDC hdc); GetDC 用于在客户区内绘制, 有时应用程序需要访问窗口非客户区, 比如标题栏 为了获得整个窗口的 DC, 使用以下函数 :HDC GetWindowDC (HWND hwnd); 如前所述, 当绘制完成后, 要调用 ReleaseDC Windows CE 下的 DC 函数同 XP 下的设备环境函数是一样的 这是可以预料到的, 因为 DC 是 Windows 绘图体系里的核心 对这个领域的 API 进行改变将导致 Windows CE 程序与它的桌面程序严重的不兼容 WinCE 程序设计 (3rd 版 )--2.2 输出文本输出文本在第一章里, 例子程序 HelloCE 调用 DrawText 函数显示了一行文本 代码如下 : DrawText (hdc, TEXT ("Hello Windows CE!"), -1, &rect, DT_CENTER DT_VCENTER DT_SINGLELINE); DrawText 是一个相当高级的函数, 允许由程序显示文本, 而由 Windows 处理大部分细节 DrawText 的头几个参数几乎是不言而喻, 很直观 当前正在使用的设备环境句柄被传入, 同时传入的还有被 TEXT 宏包围的用来显示的文本, 声明成 Unicode 字符串是为了符合 Windows CE 的需要 第三个参数是要输出的字符个数, 当为 -1, 则表示传入的是以 NULL 为终止符的字符串并由 Windows 计算其长度 第四个参数是一个指向 rect 结构的指针, 为文本规定了格式化矩形 DrawText 用该矩形作为文本格式化输出的基础 文本如何格式化取决于函数的最后一个参数 -- 格式化标志位 这些标志位指定文本如何被放在格式化的矩形里 在指定了 DT_CALCRECT 标志位的情况下, 由 DrawText 来计算需要输出的文本的尺寸 DrawText 甚至用自动计算出的行中断 (line break) 来将文本格式化多行 在 HelloCE 的情况里, 标志位规定文本应该水平居中 (DT_CENTER) 和垂直居中 (DT_VCENTER) DT_VCENTER 标志只在单行文本的情况下有效, 所以最后一个标志位 DT_SINGLELINE 规定如果矩形宽度不足以显示整个字符串时, 文本不应该折成多行 画文本的另一个方法就是使用下面的函数 : BOOL ExtTextOut (HDC hdc, int X, int Y, UINT fuoptions, const RECT *lprc, LPCTSTR lpstring, UINT cbcount, const int *lpdx);
ExtTextOut 函数同 DrawText 相比有一些优势 首先,ExtTextOut 画单行文本往往更快一些 其次, 文本并不在矩形里格式化, 而是以传入的 x y 坐标作为文本绘制的起始坐标 通常, 该点是矩形的左上角坐标, 但它可以随着 DC 中文本对齐方式来改变 传入的 rect 参数用做剪切矩形, 如果背景模式是 opaque, 则背景颜色的区域被绘制 该矩形参数可以是 NULL, 表示不需要剪切或者 opaquing( 不透明化 ) 接下来的两个参数是文本及字符个数 最后一个参数允许应用程序指定相邻字符之间的水平距离 Windows CE 与其它版本的 Windows 不同的地方在于只有这两个文本绘制函数用于显示文本 通过使用 DrawText 或 ExTextOut, 您可以模拟 TextOut 和 TabbedTextOut 等其它版本 Windows 里文本函数所能做的大部分操作 这是 Windows CE 同 Windows 早期版本不同的地方之一, 通过牺牲向后兼容性来获得一个更小的操作系统 设备环境属性关于 HelloCe 中用的 DrawText, 我还没有提到的是当显示文本时程序对 DC 配置做的大量假设 在 Windows 设备环境上绘制需要很多参数, 例如前景色和背景色 文字如何绘制在背景上以及文字的字体等 每次绘制时并不是指定所有这些参数, 设备环境保持当前设置, 也就是属性, 每次在绘制设备变量上绘制时都使用这些属性 前景色和背景色文本属性最显而易见的是前景色和背景色 SetTextColor 和 GetTextColor 允许程序设置和获取当前颜色 这两个函数在 Windwos CE 设备支持的灰度级屏幕和彩色屏幕上都可以运行的很好 使用前面提到的 GetDeviceCaps 函数, 可以确定设备支持多少颜色 该函数原型如下 : int GetDeviceCaps (HDC hdc, int nindex); 您需要被查询的 DC 的句柄, 因为不同的 DC 有不同的内容 例如, 打印机 DC 就不同于显示器 DC 第二个参数指出要查询的内容 在返回设备上可用颜色时, 当设备支持 256 色或更少颜色时,NUMCOLORS 返回支持的颜色数量 当超过 256 时,NUMCOLORS 对应的返回值为 -1, 用 BITSPIXEL 可以返回颜色, 此时返回的是每个像素所用的位 (bit) 数 通过将 BITSPIXEL 的返回值左移动一位, 可以将这个值转换为颜色数, 代码示例如下 : nnumcolors = GetDeviceCaps (hdc, NUMCOLORS); if (nnumcolors == -1) nnumcolors = 1 << GetDeviceCaps (hdc, BITSPIXEL); 文字对齐方式当用 ExtTextOut 显示文本时, 系统用 DC 的文字对齐方式来决定在哪里绘制文本 通过 SetTextAlign 函数, 文字可以水平和垂直对齐 SetTextAlign 如下 : UINT WINAPI SetTextAlign (HDC hdc, INT fmode); 传给 fmode 的对齐标方式标志位如下所示 : TA_LEFT 文本左边缘与控制点对齐 ( 控制点解释见后 ) TA_RIGHT 文本右边缘与控制点对齐 TA_TOP 文本顶部与控制点对齐 TA_CENTER 文本以控制点为中心水平居中 TA_BOTTOM 文本底部边缘与控制点对齐 TA_BASELINE 文本基线与控制点对齐
TA_NOUPDATECP 在调用 ExtTextOut 后,DC 的当前点并不更新 TA_UPDATECP 在调用 ExtTextOut 后,DC 的当前点更新上面描述中提到的控制点指的是传给 ExtTextOut 的 x y 坐标 对 SetTextAlign 的每次调用, 垂直对齐标志和水平对齐标志可以组合在一起 因为很难形象化的描述这些标志位的效果, 图 2-1 展示了每个标志的效果 在图中,X 表示控制点 图 2-1( 略 ): 当前绘制点和文本对齐标志之间的关系绘制模式影响文本输出的另一个属性是背景模式 当字母在设备环境上绘制时, 系统使用前景色绘制字母自身 字母之间的空间则是另一回事 如果背景模式设置为不透明, 则使用当前背景色来绘制该空间 但如果背景模式设置为透明, 字母之间的空间保持文字绘制之前的状态 虽然这可能不像是一个大的区别, 但设想一个使用图画或者图片来填充的窗口背景 如果文字在图片顶部输出, 背景模式设置为不透明, 文字周围的区域会被填充, 背景色会填充在图片上 如果背景模式是透明, 文字在图片上看起来就像文字原本就在图片上一样, 图片会在文本的字母之间显示出来 TextDemo 示例程序 TextDemo 程序, 演示了文本颜色 背景色和背景模式之间的关系 程序如列表 2-1 所示 : 列表 2-1:TextDemo 程序 TextDemo.h ================================================= =============== Header file Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== Returns number of elements #define dim(x) (sizeof(x) / sizeof(x[0])) ---------------------------------------------------------------------- Generic defines and data types struct decodeuint { Structure associates UINT Code; messages with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); ; struct decodecmd { Structure associates UINT Code; menu IDs with a LRESULT (*Fxn)(HWND, WORD, HWND, WORD); function. ;
---------------------------------------------------------------------- Function prototypes HWND InitInstance (HINSTANCE, LPWSTR, int); int TermInstance (HINSTANCE, int); Window procedures LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM); Message handlers LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM); TextDemo.cpp ================================================= =============== TextDemo - Text output demo Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include "TextDemo.h" Program-specific stuff ---------------------------------------------------------------------- Global data const TCHAR szappname[] = TEXT ("TextDemo"); HINSTANCE hinst; Program instance handle Message dispatch table for MainWindowProc const struct decodeuint MainMessages[] = { WM_PAINT, DoPaintMain, WM_DESTROY, DoDestroyMain, ; ================================================= ===================== Program Entry Point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { MSG msg; int rc = 0;
HWND hwndmain; Initialize this instance. hwndmain = InitInstance (hinstance, lpcmdline, ncmdshow); if (hwndmain == 0) return 0x10; Application message loop while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); Instance cleanup return TermInstance (hinstance, msg.wparam); ---------------------------------------------------------------------- InitInstance - Instance initialization HWND InitInstance (HINSTANCE hinstance, LPWSTR lpcmdline, int ncmdshow){ WNDCLASS wc; HWND hwnd; hinst = hinstance; Save handle in global variable. #if defined(win32_platform_pspc) If Pocket PC, allow only one instance of the application. hwnd = FindWindow (szappname, NULL); if (hwnd) { SetForegroundWindow ((HWND)(((DWORD)hWnd) 0x01)); return 0; #endif Register application main window class. wc.style = 0; Window style wc.lpfnwndproc = MainWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = szappname; Window class name
if (RegisterClass (&wc) == 0) return 0; Create main window. hwnd = CreateWindowEx (WS_EX_NODRAG, Ex Style flags szappname, Window class TEXT("TextDemo"), Window title Style flags WS_VISIBLE WS_CAPTION WS_SYSMENU, CW_USEDEFAULT, x position CW_USEDEFAULT, y position CW_USEDEFAULT, Initial width CW_USEDEFAULT, Initial height NULL, Parent NULL, Menu, must be null hinstance, Application instance NULL); Pointer to create Parameters Return fail code if window not created. if ((!hwnd) (!IsWindow (hwnd))) return 0; Standard show and update calls ShowWindow (hwnd, ncmdshow); UpdateWindow (hwnd); return hwnd; ---------------------------------------------------------------------- TermInstance - Program cleanup int TermInstance (HINSTANCE hinstance, int ndefrc) { return ndefrc; ================================================= ===================== Message handling procedures for MainWindow ---------------------------------------------------------------------- MainWndProc - Callback function for application window LRESULT CALLBACK MainWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { INT i; Search message list to see if we need to handle this message. If in list, call procedure.
for (i = 0; i < dim(mainmessages); i++) { if (wmsg == MainMessages[i].Code) return (*MainMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoPaintMain - Process WM_PAINT message for window. LRESULT DoPaintMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PAINTSTRUCT ps; RECT rect, rectcli; HBRUSH hbrold; HDC hdc; INT i, cy; DWORD dwcolortable[] = {0x00000000, 0x00808080, 0x00cccccc, 0x00ffffff; GetClientRect (hwnd, &rectcli); hdc = BeginPaint (hwnd, &ps); Get the height and length of the string. DrawText (hdc, TEXT ("Hello Windows CE"), -1, &rect, DT_CALCRECT DT_CENTER DT_SINGLELINE); cy = rect.bottom - rect.top + 5; Draw black rectangle on right half of window. hbrold = (HBRUSH)SelectObject (hdc, GetStockObject (BLACK_BRUSH)); Rectangle (hdc, rectcli.left + (rectcli.right - rectcli.left) / 2, rectcli.top, rectcli.right, rectcli.bottom); SelectObject (hdc, hbrold); rectcli.bottom = rectcli.top + cy; SetBkMode (hdc, TRANSPARENT); for (i = 0; i < 4; i++) { SetTextColor (hdc, dwcolortable[i]); SetBkColor (hdc, dwcolortable[3-i]); DrawText (hdc, TEXT ("Hello Windows CE"), -1, &rectcli, DT_CENTER DT_SINGLELINE);
rectcli.top += cy; rectcli.bottom += cy; SetBkMode (hdc, OPAQUE); for (i = 0; i < 4; i++) { SetTextColor (hdc, dwcolortable[i]); SetBkColor (hdc, dwcolortable[3-i]); DrawText (hdc, TEXT ("Hello Windows CE"), -1, &rectcli, DT_CENTER DT_SINGLELINE); rectcli.top += cy; rectcli.bottom += cy; EndPaint (hwnd, &ps); return 0; ---------------------------------------------------------------------- DoDestroyMain - Process WM_DESTROY message for window. LRESULT DoDestroyMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PostQuitMessage (0); return 0; TextDemo 的实质内容在 OnPaintMain 函数里 对 DrawText 的第一次调用并没有在设备环境上画任何东西 相反, 使用 DT_CALCRECT 标志指示 Windows 将文本串的矩形尺寸存在 rect 里 该信息用于计算字符串的高度, 并存储在 cy 里 接下来, 在窗口的右侧绘制了一个黑色矩形 我将在本章稍后一些讨论如何绘制一个矩形 该矩形用来在文本被书写之前, 产生两个不同的背景 函数接下来用不同的前景色 背景色在透明和不透明模式下输出了同样的字符串 程序结果如图 2-2 所示 : 头四行使用透明模式来绘制 后四行使用不透明模式来绘制 文本颜色从黑到白, 每行使用不同的颜色, 同时背景色设置为从白到黑 在透明模式下, 背景色是无关紧要的, 因为并不使用该背景色 但是在不通明模式下, 背景色欣然出现在每行里 图 2-2( 略 ):TextDemo 展示了文本色 背景色以及背景模式之间的关系 字体如果 Windows 提供的全部灵活性就是设置前景色和背景色, 那我们还是回到 MS-DOS 和字符属性时代的好 有证据表明,Windows 同 MS-DOS 最显著的变化就是 Windows 可改变显示文本字体的能力 所有 Windows 操作系统都是建立在 WYS/WYG- 所见即所得 - 的概念上的, 而采用可变字体就是达到这一目的的一个主要手段 在所有 Windows 操作系统里都有两种字体类型 - 光栅型 (raster) 和 TrueType 型 光栅型字体存储为位图, 即小的像素图像, 每个字符都有一个 光栅字体容易存储和使用, 但有一个重要
问题 : 不能很好的缩放 就像小图放大时出现锯齿纹一样, 光栅字体放大到更大的字体时会出现锯齿纹 TrueType 字体解决了缩放问题 它不是存储成图象, 每个 TrueType 字符存都存储成如何绘制字符的描述信息 作为在屏幕上绘制字符的 Windows 功能的一部分, 字体引擎获得描述信息, 并按需要的尺寸在屏幕上绘制字符 一个 Windows CE 系统支持 TrueType 或光栅字体, 但不会同时支持 幸运的是, 对光栅字体和 TrueType 字体来说, 编程接口都是一样的, 这减少了 Windows 开发者对字体技术的担忧, 毕竟字体技术用在所有应用里, 也是最让人吃力的技术之一 Windows CE 里的字体函数同 Windows 其它版本中的字体函数很相近 从创建字体 选择进 DC 到最后删除字体, 让我们看一下字体生存周期中用到的函数吧 如何查询当前字体以及枚举可使用的字体等, 都会在下面几节里涉及到 创建字体在应用可以使用非默认字体之前, 该字体必须被创建并被选进设备环境里 在新字体被选进 DC 后, 在 DC 里绘制的任何文本都使用这个新字体 在 Windows CE 中创建字体的方法如下 : HFONT CreateFontIndirect (const LOGFONT *lplf); 该函数接受一个指向 LOGFONT 结构的指针, 该结构必须使用您需要的字体描述来填充 typedef struct taglogfont { LONG lfheight; LONG lfwidth; LONG lfescapement; LONG lforientation; LONG lfweight; BYTE lfitalic; BYTE lfunderline; BYTE lfstrikeout; BYTE lfcharset; BYTE lfoutprecision; BYTE lfclipprecision; BYTE lfquality; BYTE lfpitchandfamily; TCHAR lffacename[lf_facesize]; LOGFONT; lfheight 规定字体在设备单位下的高度 如果该域为 0, 字体管理器返回使用的字体系中默认字体尺寸 对于大多数应用程序来说, 您需要创建一个特殊尺寸的字体 下面的公式用于给 lfheight 转换磅值 : lfheight = 1 * (PointSize * GetDeviceCaps (hdc, LOGPIXELSY) / 72); 这里传给 GetDeviceCaps 的 LOGPIXELSY 告诉函数返回垂直方向上每英寸的逻辑像素数 72 表示每英寸的磅值 ( 磅是用于排版的计算单位 ) lfwidth 规定了平均字符宽度 因为字体的高度比宽度更重要, 所以大多数程序都把这个值设为 0 表示让字体管理器根据字体高度计算出合适的宽度 lfescapement 和 lforientation 规定了字符基线同 X 坐标轴之间以十分之一度为单位的旋转角度 lfweight 规定了从 0 到 1000 范
围内的字体粗细程度, 其中 400 是标准字体,700 是加重字体 接下来的三个域规定了字体是否是斜体 下划线或者删除线 lpcharset 规定了您选择的字符集 该域对于软件的国际版本是很重要的, 因为国际版本一般都要求特定的语言字符集合 lfoutprecision 规定 Windows 匹配您请求的字体的精确程度 在众多可以使用的标志当中,OUT_TT_ONLY_PRECIS 标志规定创建的字体必须是 TrueType 字体 lfclipprecision 规定 Windows 如何裁剪超出显示区域的字符 lfquality 可以设置为以下几种 : DEFAULT_QUALITY 默认系统质量 DRAFT_QUALITY 牺牲质量换取速度 CLEARTYPE_QUALITY 用 ClearType 技术绘制文本 CLEARTYPE_COMPAT_QUALITY 同样的空间 用 ClearType 技术绘制文本 对非 ClearType 字体使用 ClearType 是一种为字体提供更清晰的外观的文本显示技术, 该技术独立寻址红 绿 蓝 LCD, 它们在彩色 LCD 显示器上构成一个像素 根据系统的不同, 有的可能不支持 ClearType, 有的可能对系统里的所有字体都支持 对于支持 ClearType 但不完全支持的系统来说, 使用 CLEARTYPE_QUALITY 或 CLEARTYPE_COMPAT_QUALITY 可以创建用 ClearType 绘制的字体 因为 ClearType 并不改进所有字体的外观, 所以您应该测试一下, 看 ClearType 是否对您选择的字体有所改进 lfpitchandfamily 规定了您选择的字体的字系 当您请求诸如 Swiss 字系 -- 一种没有衬线的专业字体 -- 时, 使用该域很方便 再例如选择 Roman 字系 -- 一种带衬线的专业字体 -- 时, 也很方便, 但您要注意不要针对特定的字体 您也可以用该域来规定均衡或等宽字体, 并让 Windows 来决定使用哪种字体来匹配传给 LOGFONT 结构的特定特性 最后,lfFaceName 域用来指明具体字体的字体名称 当用一个填充后的 LOGFONT 结构调用 CreateFontIndirect 时, Windows 根据提供的特性, 创建一个最匹配的逻辑字体 为了使用该字体, 需要做的最后一步是选择该字体到一个设备环境里 选择字体到设备环境用 SelectObject 函数将字体选进 DC 里, 函数如下所示 : HGDIOBJ SelectObject (HDC hdc, HGDIOBJ hgdiobj); 该函数不仅仅用于设置默认字体, 还用在很多地方 ; 正如您很快就会看到的, 可以用该函数来选择其它 GDI 对象 该函数返回先前被选择的对象 ( 在本例中返回的是先前选择的字体 ), 应该保存该返回值以便当您用完新的字体后可以把先前的选择回 DC 里 代码行如下所示 : holdfont = (HFONT)SelectObject (hdc, hfont); 当逻辑字体被选择时, 系统从可使用的字体中选择最匹配的逻辑字体 对没有 TrueType 字体的设备, 匹配的字体同指定的参数相比有一定数量的差异 因此, 不要想当然的认为您请求了一个特殊字体, 返回的就是准确匹配的字体 例如, 您要求的字体高度可能和选进设备环境的字体高度是不一样的 查询字体特性为了确定选进设备环境的字体的特性, 调用 GetTextMetrics 函数来返回字体特性, 函数原型
如下 : BOOL GetTextMetrics (HDC hdc, LPTEXTMETRIC lptm); TEXTMETRIC 结构带有返回信息, 该结构定义如下 : typedef struct tagtextmetric { LONG tmheight; LONG tmascent; LONG tmdescent; LONG tminternalleading; LONG tmexternalleading; LONG tmavecharwidth; LONG tmmaxcharwidth; LONG tmweight; LONG tmoverhang; LONG tmdigitizedaspectx; LONG tmdigitizedaspecty; char tmfirstchar; char tmlastchar; char tmdefaultchar; char tmbreakchar; BYTE tmitalic; BYTE tmunderlined; BYTE tmstruckout; BYTE tmpitchandfamily; BYTE tmcharset; TEXTMETRIC; TEXTMETRIC 结构包含了 LOGFONT 结构中的很多域, 但此时 TEXTMETRIC 的值是选进设备变量的字体的特性 图 2-3 显示了一些域同实际字符之间的关系 图 2-3( 图略 ):TEXTMETRIC 结构及其与字体的关系 除了可以判断您是否真的获得了您需要的字体外,GetTextmetrics 函数调用还有另外一个有价值的用途 -- 确定字体高度 回忆一下 TextDemo 程序, 行的高度是通过调用 DrawText 函数计算出来的 虽然那种方法很方便, 但它有点慢 您可以使用 TEXTMETRIC 数据, 以更加直接的方法来计算高度 将表示字符高度的 tmheight 域与 tmexternalleading 域 -- 表示一行底部像素到下一行顶部像素之间的间距 -- 相加, 您就可以获得两行文本基线的间距了 虽然 GetTextMetrics 对确定字符高度来说是很好用的, 但它只提供了字体的平均宽度和最大宽度 如果需要 TrueType 的更多细节, 可以使用函数 GetCharABCWidths, 函数原型如下 : BOOL GetCharABCWidths (HDC hdc, UINT ufirstchar, UINT ulastchar, LPABC lpabc); GetCharABCWidths 返回由 ufirstchar 和 ulastchar 参数所确定的一系列字符的 ABC 宽度 该函数检查 hdc 参数指定的 DC 里的字体 ABC 结构如下 : typedef struct _ABC { int abca; UINT abcb; int abcc;
ABC; abca 为在放置字符轮廓前的空白间距,abcB 为字符轮廓本身的间距,abcC 为字符轮廓右方的空白间距 abca 和 abcc 都可以是负值, 用来指示缩进或者凸起 要获得点阵字体 (bitmap fonts) 的宽度, 可以使用 GetCharWidth32 函数 对指定字符范围内的每个字符, 该函数返回一个字符宽度数组 销毁字体同其它 GDI 资源一样, 在程序用完后, 字体必须被销毁 在终止程序前如果删除字体失败, 将导致资源泄露 -- 孤立的图形资源, 占用了珍贵的内存但却不再被应用程序拥有 为了销毁字体, 首先将字体从所在的设备环境里取消选择, 这可以通过调用 SelectObject 来完成 传入的字体是最初调用 SelectObject 来选择字体时返回的字体 字体取消选择后, 调用 DeleteObject 从系统里删除字体 DeleteObject 原型如下 : BOOL DeleteObject (HGDIOBJ hobject); hobject 表示要删除的字体句柄 从这个过程可以看出, 字体管理不是一个小事情 LOGFONT 结构的许多参数可能令人畏惧, 但它们赋予了应用程序准确指定字体的巨大能力 处理字体时的一个问题是判断在具体设备上能够支持什么类型的字体 Windows CE 设备提供标准字体集合, 但制造商或者用户可能在具体系统上装载了附加字体 幸运地是,Windows 提供了枚举系统上所有字体的方法 枚举字体为了判断系统上有什么字体可以使用,Windows 提供了以下函数 : int EnumFontFamilies (HDC hdc, LPCTSTR lpszfamily, FONTENUMPROC lpenumfontfamproc, LPARAM lparam); 该函数列举所以字系以及字系里的每个字体 头一个参数是设备环境 第二个参数是需要枚举的字系的名字 如果该参数为 NULL, 表示枚举每个可以使用的字系 第三个参数稍微有些不同, 是一个指向由应用程序提供的函数的指针 该函数是一个回调函数, 由 Windows 为每个枚举的字体调用 最后一个参数,lParam, 是由应用程序使用的普通参数 该值不经修改, 直接传给了应用程序回调过程 虽然回调函数的名字可以随便起, 但函数原型必须声明为 : int CALLBACK EnumFontFamProc (LOGFONT *lpelf, TEXTMETRIC *lpntm, DWORD FontType, LPARAM lparam); 传给回调函数的第一个参数是指向 LOGFONT 结构的指针, 用来描述被枚举的字体 第 2 个参数, 指向 textmetric 的指针, 进一步描述该字体 字体类型参数指明字体是光栅字体还是 TrueType 字体 FontList 示例程序 FontList 程序用两种方式使用 EnumFontFamilies 函数来枚举系统里所有字体清单 2-2 FontList.h ================================================= ===============
Header file Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== Returns number of elements #define dim(x) (sizeof(x) / sizeof(x[0])) ---------------------------------------------------------------------- Generic defines and data types struct decodeuint { Structure associates UINT Code; messages with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); ; struct decodecmd { Structure associates UINT Code; menu IDs with a LRESULT (*Fxn)(HWND, WORD, HWND, WORD); function. ; ---------------------------------------------------------------------- Program-specific structures #define FAMILYMAX 24 typedef struct { int nnumfonts; TCHAR szfontfamily[lf_facesize]; FONTFAMSTRUCT; typedef FONTFAMSTRUCT *PFONTFAMSTRUCT; typedef struct { INT ycurrent; HDC hdc; PAINTFONTINFO; typedef PAINTFONTINFO *PPAINTFONTINFO; ---------------------------------------------------------------------- Function prototypes HWND InitInstance (HINSTANCE, LPWSTR, int); int TermInstance (HINSTANCE, int); Window procedures LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
Message handlers LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM); FontList.cpp ================================================= ===================== FontList - Lists the available fonts in the system Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include "FontList.h" Program-specific stuff ---------------------------------------------------------------------- Global data const TCHAR szappname[] = TEXT ("FontList"); HINSTANCE hinst; Program instance handle FONTFAMSTRUCT ffs[familymax]; INT sfamilycnt = 0; Message dispatch table for MainWindowProc const struct decodeuint MainMessages[] = { WM_CREATE, DoCreateMain, WM_PAINT, DoPaintMain, WM_DESTROY, DoDestroyMain, ; ================================================= ===================== Program entry point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { MSG msg; int rc = 0; HWND hwndmain; Initialize this instance. hwndmain = InitInstance (hinstance, lpcmdline, ncmdshow);
if (hwndmain == 0) return 0x10; Application message loop while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); Instance cleanup return TermInstance (hinstance, msg.wparam); ---------------------------------------------------------------------- InitInstance - Instance initialization HWND InitInstance (HINSTANCE hinstance, LPWSTR lpcmdline, int ncmdshow) { WNDCLASS wc; HWND hwnd; Save program instance handle in global variable. hinst = hinstance; #if defined(win32_platform_pspc) If Pocket PC, allow only one instance of the application. hwnd = FindWindow (szappname, NULL); if (hwnd) { SetForegroundWindow ((HWND)(((DWORD)hWnd) 0x01)); return 0; #endif Register application main window class. wc.style = 0; Window style wc.lpfnwndproc = MainWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = szappname; Window class name if (RegisterClass (&wc) == 0) return 0; Create main window. hwnd = CreateWindowEx (WS_EX_NODRAG, Ex style flags
szappname, Window class TEXT("Font Listing"), Window title Style flags WS_VISIBLE WS_CAPTION WS_SYSMENU, CW_USEDEFAULT, x position CW_USEDEFAULT, y position CW_USEDEFAULT, Initial width CW_USEDEFAULT, Initial height NULL, Parent NULL, Menu, must be null hinstance, Application instance NULL); Pointer to create parameters Return fail code if window not created. if (!IsWindow (hwnd)) return 0; Standard show and update calls ShowWindow (hwnd, ncmdshow); UpdateWindow (hwnd); return hwnd; ---------------------------------------------------------------------- TermInstance - Program cleanup int TermInstance (HINSTANCE hinstance, int ndefrc) { return ndefrc; ================================================= ===================== Font callback functions ---------------------------------------------------------------------- FontFamilyCallback - Callback function that enumerates the font families int CALLBACK FontFamilyCallback (CONST LOGFONT *lplf, CONST TEXTMETRIC *lpntm, DWORD nfonttype, LPARAM lparam) { int rc = 1; Stop enumeration if array filled. if (sfamilycnt >= FAMILYMAX) return 0; Copy face name of font.
lstrcpy (ffs[sfamilycnt++].szfontfamily, lplf->lffacename); return rc; ---------------------------------------------------------------------- EnumSingleFontFamily - Callback function that enumerates fonts int CALLBACK EnumSingleFontFamily (CONST LOGFONT *lplf, CONST TEXTMETRIC *lpntm, DWORD nfonttype, LPARAM lparam) { PFONTFAMSTRUCT pffs; pffs = (PFONTFAMSTRUCT) lparam; pffs->nnumfonts++; Increment count of fonts in family return 1; ---------------------------------------------------------------- PaintSingleFontFamily - Callback function that draws a font int CALLBACK PaintSingleFontFamily (CONST LOGFONT *lplf, CONST TEXTMETRIC *lpntm, DWORD nfonttype, LPARAM lparam) { PPAINTFONTINFO ppfi; TCHAR szout[256]; INT nfontheight, npointsize; HFONT hfont, holdfont; ppfi = (PPAINTFONTINFO) lparam; Translate lparam into struct pointer. Create the font from the LOGFONT structure passed. hfont = CreateFontIndirect (lplf); Select the font into the device context. holdfont = (HFONT)SelectObject (ppfi->hdc, hfont); Compute font size. npointsize = (lplf->lfheight * 72) / GetDeviceCaps(ppfi->hdc,LOGPIXELSY); Format string and paint on display. wsprintf (szout, TEXT ("%s Point:%d"), lplf->lffacename, npointsize); ExtTextOut (ppfi->hdc, 25, ppfi->ycurrent, 0, NULL,
szout, lstrlen (szout), NULL); Compute the height of the default font. nfontheight = lpntm->tmheight + lpntm->tmexternalleading; Update new draw point. ppfi->ycurrent += nfontheight; Deselect font and delete. SelectObject (ppfi->hdc, holdfont); DeleteObject (hfont); return 1; ================================================= =============== Message handling procedures for MainWindow ---------------------------------------------------------------- MainWndProc - Callback function for application window LRESULT CALLBACK MainWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { INT i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(mainmessages); i++) { if (wmsg == MainMessages[i].Code) return (*MainMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoCreateMain - Process WM_CREATE message for window. LRESULT DoCreateMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { HDC hdc; INT i, rc; Enumerate the available fonts. hdc = GetDC (hwnd); rc = EnumFontFamilies ((HDC)hdc, (LPTSTR)NULL, FontFamilyCallback, 0);
for (i = 0; i < sfamilycnt; i++) { ffs[i].nnumfonts = 0; rc = EnumFontFamilies ((HDC)hdc, ffs[i].szfontfamily, EnumSingleFontFamily, (LPARAM)(PFONTFAMSTRUCT)&ffs[i]); ReleaseDC (hwnd, hdc); return 0; --------------------------------------------------------------- DoPaintMain - Process WM_PAINT message for window. LRESULT DoPaintMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PAINTSTRUCT ps; RECT rect; HDC hdc; TEXTMETRIC tm; INT nfontheight, i; TCHAR szout[256]; PAINTFONTINFO pfi; GetClientRect (hwnd, &rect); hdc = BeginPaint (hwnd, &ps); Get the height of the default font. GetTextMetrics (hdc, &tm); nfontheight = tm.tmheight + tm.tmexternalleading; Initialize struct that is passed to enumerate function. pfi.ycurrent = rect.top; pfi.hdc = hdc; for (i = 0; i < sfamilycnt; i++) { Format output string, and paint font family name. wsprintf (szout, TEXT("Family: %s "), ffs[i].szfontfamily); ExtTextOut (hdc, 5, pfi.ycurrent, 0, NULL, szout, lstrlen (szout), NULL); pfi.ycurrent += nfontheight; Enumerate each family to draw a sample of that font. EnumFontFamilies ((HDC)hdc, ffs[i].szfontfamily,
PaintSingleFontFamily, (LPARAM)&pfi); EndPaint (hwnd, &ps); return 0; ---------------------------------------------------------------- DoDestroyMain - Process WM_DESTROY message for window. LRESULT DoDestroyMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PostQuitMessage (0); return 0; 当应用程序在 OnCreateMain 中处理 WM_CREATE 消息时, 首先枚举不同的字体 此处调用 EnumFontFamilies 函数, 其中 FontFamily 域设为 NULL 表示每个字系都将被枚举 在回调函数 FontFamilyCallback 里, 字系的名字被复制进一个字符串数组里 在处理 WM_PAINT 消息的过程里完成剩余的工作 OnPaint 函数以惯常的标准方式开头, 依然是先获取客户区域大小并调用 BeginPaint 函数, 该函数返回窗口的设备环境句柄 接下来调用 GetTextMetrics 来计算默认字体的行高 之后进入循环, 为 OnCreateMain 中枚举出的每个字系调用 EnumFontFamilies 函数 用于这个回调序列的回调过程是迄今为止比较复杂的代码 用于枚举单个字体的回调过程 PaintSingleFontFamily 使用 lparam 参数来获得一个指向 PAINTFONTINFO 结构的指针 该结构包含当前垂直绘制坐标以及设备环境的句柄 通过使用 lparam 指针,FontList 可以避免声明全局变量来和回调过程通信 接下来回调过程使用传进来的 LOGFONT 指针创建字体 新字体随后被选进设备环境, 之前被选择的字体句柄被保存在 holdfont 中 使用本章前面提到的转换公式来计算被枚举的字体的磅值 接下来输出一行文本, 显示字系名称以及该字体的磅值 没有使用 DrawText, 回调过程用的是 ExtTextOut 来绘制字符串的 显示完文本后, 函数通过使用传入的 TEXTMETRIC 结构里的 tmheight 和 tmexternalleading 域来计算刚绘制的文本的行高 之后用最初选择的字体句柄调用 SelectObject 来取消对新字体的选择 随后用 DeleteObject 来删除新字体 最后, 回调函数返回一个非零值来通知 Windows 一起正常, 可以开始另一次枚举回调了 图 2-4 显示了字体列表窗口 注意字体名字体名称, 每个字体有特定的大小集合 图 2-4( 略 ): 字体列表窗口显示了手持 PC 上一些可以使用的字体 未完的事情如果你仔细看图 2-4, 你会注意到一个显示上的问题 字体列表显示在了 Font 列表窗口的底边上 解决方法是为窗口增加滚动条 因为我将在第四章详细描述窗口控件, 其中就包括滚动条, 所以我暂时不讲述如何实现该方法
WinCE 程序设计 (3rd 版 )--2.3 位图位图位图是一种图形化对象, 用于在设备环境里创建 绘制 操纵和接收图片 从 [ 开始按钮 ] 上的小 Winodws 标志到标题栏上的 [ 关闭 ] 按钮, 位图在 Windows 里无处不在 位图可以看作是一种由像素数组构成的图片, 这些像素可以在屏幕上进行绘制 和所有图片一样, 位图有自己的高度和宽度 也提供方法来判断位图使用什么颜色 最后, 位图也是一个描述位图中每个像素的位 (bits) 数组 习惯上,Windows 下的位图被划分成两种类型 : 设备相关位图 (DDBs) 和设备无关位图 (DIBs) DDBs 是一种和具体 DC 的特性有紧密关系的位图, 不容易在有不同特性的 DC 上绘制 DIBs 则相反, 它与具体设备无关, 因此需要携带足够的信息以便于在任何设备上准确的绘制 Windwos CE 包含了许多在其它 Windows 版本里可以使用的位图函数 不同之处包括只有 Windows CE 才支持的一种新的四色格式和对 DIBs 不同的操纵方式 设备相关位图可以使用 CreateBitmap 函数来创建设备相关位图, 函数原型如下 : HBITMAP CreateBitmap (int nwidth, int nheight, UINT cplanes, UINT cbitsperpel, CONST VOID *lpvbits); nwidth 和 nheight 表示位图的尺寸 cplanes 是一个历史产物, 当时显示器采用不同的硬件平面来实现像素里的每个颜色 对 Windows CE 来说, 该参数必须是 1 cbitspperpel 表示每个像素使用的位数 颜色数是 cbitspperpel 的 2 次幂 在 Windows CE 下, 允许使用的是 1 2 4 8 16 和 24 正如我说过的,4 色位图是 Windows CE 特有的, 其它 Windows 平台不则不支持 最后一个参数是指向位图位数的指针 在 Windows CE 下, 位数总是按压缩像素格式排列的 也就是, 每个像素按字节存储成一系列的比特位, 下一个像素紧接上一个 位数组的第一个像素是位图左上角的像素 像素沿位图顶行排列, 随后是第二行, 依次类推 位图每行必须是双字 (4 字节 ) 对齐排列 为了对齐下一行, 可在行尾使用 0 来填充 图 2-5 演示了这种排列方式, 图中展示了一个 126*64 像素的位图, 每个像素使用 8 位 图 2-5( 略 ) 位图里的字节布局函数 CreateCompatibleBitmap, 其原型如下 : HBITMAP CreateCompatibleBitmap (HDC hdc, int nwidth, int nheight); 用该函数可以创建一个格式与传入的设备环境兼容的位图 所以如果设备环境是四色 DC, 创建的位图也是一个四色位图 当您要在屏幕上操纵图片时, 使用该函数很就很方便, 因为该函数可以很容易创建一个与屏幕直接颜色兼容的空位图 设备无关位图设备无关位图和设备相关位图之间的基本差异是存储成 DIBs 的图象有自己的颜色信息 自从使用 BMP 作为扩展名的 Windows 3.0 起, 几乎每个位图文件都含有在 Windows 里创建 DIB 时所需要的信息 在 Windows 早期, 写程序手工读 DIB 文件并把数据转换为位图是程序员必备的技能 现在, 这个烦冗的任务可以通过 Windows CE 特有的函数 SHLoadDIBitmap 来完成, 函数原型如下 : HBITMAP SHLoadDIBitmap (LPCTSTR szfilename);
该函数直接从位图文件里装载位图并提供位图句柄 在 Windows XP 里可以使用带 LR_LOADFROMFILE 参数标志的 LoadImage 函数来完成同样的处理, 但 Windows CE 下的 LoadImage 不支持这个标志 DIB 片段虽然 Windows CE 里很容易装载位图文件, 但有时您必须读屏幕上图象 操纵图象以及将图象重画到屏幕上 这是 DIBs 比 DDBs 更好一些的地方之一 虽然设备相关位图的位数据可以获取得到, 但缓冲区的格式直接依赖于屏幕格式 而通过使用 DIB, 或者更准确地说, 通过使用 DIB 片段, 您的程序可以把位图读到预定义格式的缓冲区里, 而不用担心显示设备的格式 虽然从 Windows 3.0 起就不断加入许多 DIB 创建函数, 但 Windows CE 只支持 XP 中的一部分 DIB 函数 CreateDIBSection 是这些函数中的第一个 : HBITMAP CreateDIBSection (HDC hdc, const BITMAPINFO *pbmi, UINT iusage, void *ppvbits, HANDLE hsection, DWORD dwoffset); 因为他们是相当晚才加到 Win32 API 里的, 所以 DIB 片段对程序员来说可能是比较新鲜的 使用 DIB 片段是为了改进 Winodows NT 上直接操纵位图的应用程序性能 简而言之,DIB 片段允许程序员在直接访问位图位数据时, 选择一个设备环境里的 DIB 为达到这个目的,DIB 片段将一个缓冲区与内存 DC 结合到一起, 该缓冲区同时包含了该 DC 的位数据 因为图象是映射到一个 DC 的, 所以可以使用其它图形函数调用来修改图片 同时,DC 里的 DIB 格式的原始位数据可以被直接操纵 能改进在 NT 上的性能固然是很好, 但对 Window CE 程序员来说, 能够简化位图的使用和操作位图的内容才是最有价值的 该函数中最重要的参数是指向 BITMAPINFO 结构的指针 该结构描述了设备无关位图的布局和颜色构成, 该结构包含一个 BITMAPINFOHEADER 结构和一个代表位图使用的调色板的 RGBQUAD 数组 BITMAPINFOHEADER 定义如下 typedef struct tagbitmapinfoheader{ DWORD bisize; LONG biwidth; LONG biheight; WORD biplanes; WORD bibitcount; DWORD bicompression; DWORD bisizeimage; LONG bixpelspermeter; LONG biypelspermeter; DWORD biclrused; DWORD biclrimportant; BITMAPINFOHEADER; 如你所见, 该结构包含的信息远多于传给 CreateBitmap 的参数 第一个域是该结构的尺寸, 必须由调用者填充, 用于区别由 OS/2 管理器沿袭来的 BITMAPCOREINFOHEADER 结构 biwidth, biheight, biplanes, 和 bibitcount 都和 CreateBitmap 里的同名参数类似, 但有一个例外,biHeight 的正负号指定了位数组的组织排列方式 如果 biheight 是正数, 位数组按由上到下的格式排列, 这一点和 CreateBitmap 相同 如果 biheight 是负数, 位数组则按由
下到上的格式排列, 也就是位图的底部行定义在该位数组的首位 和 CreateBitmap 一样, biplanes 必须设置为 1 bicompression 指出位数组使用的压缩方式 Windows CE 里, 允许使用的标志有,BI_RGB, 指出缓冲区没有压缩 ;BI_BITFIELDS, 指出像素格式被定义在颜色表的头三个入口里 bisizeimage 用于指出位数组的大小 但是, 当使用 BI_RGB 标志时,biSizeImage 可以设置为 0, 表示用 BITMAPINFOHEADER 结构里提供的尺寸 (dimensions ) 和像素的位数来计算数组的大小 bixpelspermeter 和 biypelspermeter 提供图片的准确尺寸信息 但是对于 CreateBIBSection 来说, 这些参数可以设置为 0 biclrused 指出实际使用的调色板里的颜色数 在 256 色图片里, 调色板有 256 个入口, 但畏途自身可能只需要大约 100 个不同的颜色 这个域帮助调色板管理器 --Windows 管理颜色匹配的部件 -- 将系统调色板里的颜色同位图要求的颜色进行匹配 biclrimportant 进一步指出真正需要的颜色 对更多颜色的位图, 这两个域设置为 0, 表示使用所有颜色并且所有颜色都重要 前面提到过,BITMAPINFOHEADER 结构之后是 RGBQUAD 结构数组 该结构定义如下 : typedef struct tagrgbquad { /* rgbq */ BYTE rgbblue; BYTE rgbgreen; BYTE rgbred; BYTE rgbreserved; RGBQUAD 该结构允许有红蓝绿各 256 级色度 (shade ) 虽然用该结构可以创建几乎任何色度, 但设备上实际渲染的颜色是受设备能显示的颜色的限制的 总体来看,RGBQUAD 结构数组描述了 DIB 的调色板 调色板是位图里的颜色列表 如果位图有调色板, 位图数组的每个入口包含的就不再是颜色, 而是包含每个像素颜色的调色板索引 虽然对单色位图来说是多余的, 但当在彩色设备上绘制彩色位图时, 调色板就相当重要了 例如, 虽然 256 色位图中每个像素一个字节, 但该字节指向一个代表红绿蓝色的 24 位值 所以尽管 256 色位图只能包含 256 个不同的颜色, 但由于是使用 24 位调色板入口进行颜色绘制的, 因而这些颜色中的每个都可使用出的 1 千 6 百万种颜色中的一个 为了方便在 32 位中使用, 每个只包含 24 位颜色信息的调色板入口都被扩充到 32 位宽度了, 这也是 RGBQUAD 名字的来源 ( 译者注 :QUAD 有四个一套的意思 ) CreateDIBSection 剩余的四个参数中只有两个用于 Windows CE IUsage 指出调色板里的颜色是如何被绘制的 如果该参数是 DIB_RGB_COLORS, 表示位图里的位数据包含了每个像素的全部 RGB 颜色信息 ;DIB_PAL_COLORS, 表示位图像素包含 DC 里当前选择的调色板的索引 PpvBits 是指向构成位图图象的位数据的指针 最后两个参数,hSection 和 dwoffset, Windows CE 不支持它们, 必须设置为 0 在 Windows 的其它版本里, 它们允许使用内存映射文件来给出位数据 因为 Windows CE 不支持内存映射文件, 所以它们不能被 CreateDIBSection 支持 GetDIBColorTable 和 SetDIBColorTable 是管理 DIB 调色板的两个函数, 它们的原型如下 : UINT GetDIBColorTable (HDC hdc, UINT ustartindex, UINT centries, RGBQUAD *pcolors); 和
UINT SetDIBColorTable (HDC hdc, UINT ustartindex, UINT centries, RGBQUAD *pcolors); 对这两个函数来说,uStartIndex 指出将被设置或者查询的调色板的第一个入口 CEntries 指出有多少调色板入口将改变 指向 RGBQUAD 数组的指针是用于设置 ( 对 SetDIBColorTable) 或者查询 ( 对 GetDIBColorTable) 的颜色数组 绘制位图能够创建和装载位图固然很好, 但如果您创建的位图不能绘制在屏幕上, 那就没什么大用处 绘制位图可能并不是您想象的那么简单 位图被绘制到屏幕 DC 之前, 必须先将位图选进一个 DC, 再将其复制到屏幕设备环境里 虽然这个过程听起来可能有点费解, 但这是有合理的原因的 把位图选择到一个设备环境的过程与把逻辑字体选择到设备环境的过程类似 下面让我们把理想变成现实吧 正如 Windows 要为请求的字体找到最可能匹配的字体一样, 位图选择过程也必须为位图要求的颜色找到匹配的设备上可用的颜色 只有在这个过程完成后, 位图才能绘制到屏幕上 为了帮助完成这一中间步骤,Windows 提供了一个替身 DC 内存设备环境 要创建内存设备环境, 可以使用函数 CreateCompatibleDC: HDC CreateCompatibleDC (HDC hdc); 该函数创建一个与当前屏幕 DC 兼容的内存 DC 一旦创建成功, 可以使用您以前用来选择逻辑字体的 SelectObject 函数将源位图选进这个内存 DC 最后, 用 BitBlt 或 StretchBlt 将位图从内存 DC 复制到屏幕 DC 位图函数的主力是 BOOL BitBlt (HDC hdcdest, int nxdest, int nydest, int nwidth, hdcsrc, int nxsrc, int nysrc, DWORD dwrop); int nheight, HDC BitBlt 函数发音为 bit blit, 它是一个有意思的函数, 它在设备环境上操作, 而不是内存里, 它有时是一个很特别的函数 第一个参数是位图即将被复制到其上的目的设备环境的句柄 接下来的 4 个参数规定了位图最终位于的目的矩形的位置和大小 接下来的 3 个参数规定了源设备环境的句柄以及源图象左上角在该 DC 里的位置 最后一个参数 dwrop 规定图象如何从源设备环境复制到目的设备环境 ROP 代码规定了源位图和当前目的设备如何组合来产生最终图片 ROP 代码为 SRCOPY, 表示简单复制源图象 ROP 代码为 SRCPAINT, 表示源图象和目的之间进行或操作 ROP 代码为 SRCINVERT, 表示复制一个逻辑反转图象, 本质上是一个负的源图象 一些 ROP 代码还将当前选择的画刷 (brush) 一起作为计算结果图象的因素 ROP 代码很多, 所以这里不可能覆盖全, 要获得全部列表, 请参考 Windows CE 编程文档 下面的代码片段总结了如何绘制位图 : Create a DC that matches the device. hdcmem = CreateCompatibleDC (hdc); Select the bitmap into the compatible device context. holdsel = SelectObject (hdcmem, hbitmap); Get the bitmap dimensions from the bitmap. GetObject (hbitmap, sizeof (BITMAP), &bmp);
Copy the bitmap image from the memory DC to the screen DC. BitBlt (hdc, rect.left, rect.top, bmp.bmwidth, bmp.bmheight, hdcmem, 0, 0, SRCCOPY); Restore original bitmap selection and destroy the memory DC. SelectObject (hdcmem, holdsel); DeleteDC (hdcmem); 创建内存设备环境, 即将被绘制的位图被选进该 DC 因为您可能没有存储即将被绘制的位图尺寸, 通常可以调用 GetObject 来获得 GetObject 返回关于图形对象的信息, 在本例中是一个位图 可以用这个很有用的函数来查询字体和其它图象对象的信息 接下来, 使用 BitBlit 将位图复制进屏幕 DC 为了清理, 需要将位图从内存设备环境中取消选择, 并使用 DeleteDC 将该内存 DC 删除 不要将 DeleteDC 同 ReleaseDC 混淆,ReleaseDC 是释放一个显示 DC DeleteDC 只应该和 CreateCompatibleDC 一起使用,ReleaseDC 只应该和 GetDC 或 GetWindowsDC 一起使用 用 StretchBlt 除了复制位图, 还可以拉伸或者压缩位图 该函数原型如下 : BOOL StretchBlt (HDC hdcdest, int nxorigindest, int nyorigindest, int nwidthdest, int nheightdest, HDC hdcsrc, int nxoriginsrc, int nyoriginsrc, int nwidthsrc, int nheightsrc, DWORD dwrop); StretchBlt 里的参数和 BitBlt 里的基本相同, 但是有一点例外的是现在可以指定源图象的宽度和高度 同样的, 这里的 ROP 代码规定了源和目的之间如何组合来产生最终图象 Windows CE 还有另外一个位图函数 TransparentImage, 原型如下 : BOOL TransparentImage (HDC hdcdest, LONG DstX, LONG DstY, LONG DstCx, LONG DstCy, HANDLE hsrc, LONG SrcX, LONG SrcY, LONG SrcCx, LONG SrcCy, COLORREF TransparentColor); 该函数同 StretchBlt 类似, 但有两个很重要的例外 首先, 您可以指定位图里作为透明色的颜色 当把位图复制到目标中时, 位图里透明色的像素不被复制 第二个不同是 hsrc 参数要么是设备环境要么是位图句柄, 该参数允许您不必理会在屏幕上绘制图象前必须将源图象选进一个设备环境的要求 TransparentImage 与 Windows 2000 中的 TransparentBlt 函数基本相同, 除了 TransparetBlt 不能直接使用位图作为源图象以外 和其它版本的 Windows 一样,Windows CE 还支持 2 个其它 blit 函数 :PatBlt 和 MaskBlt PatBlt 函数将当前选择的画刷同目的 DC 里当前图象进行组合, 产生结果图象 我在本章后面会谈到画刷 MaskBlt 函数与 BitBlt 类似, 但包含一个掩码图象, 用来提供只绘制源图象的一部分到目的 DC 里的功能 WinCE 程序设计 (3rd 版 )--2.4 线条和形状线条和形状同 Windows 其它版本相比,Windows CE 提供相当少的功能的领域之一就是基本线条绘制和形状绘制功能 用来创建复杂环形的 Chord, Arc, 和 Pie 函数被去掉了 大部分使用 " 当前点 [current point]" 概念的函数也被去掉了 除了 MoveToEx, LineTo 和 GetCurrentPositionEx 外, 处理当前点的其它 GDI 函数都不被 Windows CE 支持 因此想用 ArcTo PolyBezierTo
等函数来绘制一系列连接的直线和曲线是不可能了 不过即使在缺少很多图形函数的情况下, Windows CE 依然提供了绘制直线和形状所需要的基本函数 线条简单调用 Polyline 就可以绘制一个或者多个线条了 函数原型如下 : BOOL Polyline (HDC hdc, const POINT *lppt, int cpoints); 第 2 个参数是指向 POINT 结构数组的指针, 该结构定义如下 : typedef struct tagpoint { LONG x; LONG y; POINT 每个 X Y 结合起来描述一个从屏幕左上角开始的像素 第三个参数是数组里的 point 结构的数量 因此绘制一个从 (0,0) 到 (50,100) 的直线, 代码看起来如下 : POINTS pts[2]; pts[0].x = 0; pts[0].y = 0; pts[1].x = 50; pts[1].y = 100; PolyLine (hdc, &pts, 2); 绘制该直线的另外一个方法是使用 MoveToEx 和 LineTo 函数 它们的原型如下 : BOOL WINAPI MoveToEx (HDC hdc, int X, int Y, LPPOINT lppoint); BOOL WINAPI LineTo (HDC hdc, int X, int Y); 要使用这两个函数绘制线条, 首先要调用 MoveToEx 将当前点移动到线条的起始坐标处, 接下来用终点坐标调用 LineTo 调用代码如下: MoveToEx (hdc, 0, 0, NULL); LineTo (hdc, 50, 100); 要查询当前点, 可调用函数 GetCurrentPositionEx, 原型如下 : WINGDIAPI BOOL WINAPI GetCurrentPositionEx (HDC hdc, LPPOINT ppoint); 和前面绘制文本的例子一样, 这些代码片段对设备描述表的状态做了大量假设 例如, 绘制的 (0, 0) 和 (50, 100) 之间的线条是什么样子? 宽度和颜色是什么以及是实心线条吗? 所有版本的 Windows, 包括 Windows CE 在内, 都允许指定这些参数 画笔画笔 (pen) 是用于指定线条外观和形状轮廓的工具 画笔是另一个 GDI 对象, 像本章里描述的其它 GDI 对象一样, 画笔要被创建 选进设备描述表, 使用 取消选择, 最后被销毁 同其它备用 GDI 对象一样, 可以使用 GetStockObject 来检索备用画笔 该函数原型如下 : HGDIOBJ GetStockObject (int fnobject); 所有版本的 Windows 都提供三种备用画笔, 每个 1 像素宽 这些备用画笔有 3 种颜色 : 白色 黑色和 NULL 当您使用 GetStockObject 时, 该函数分别使用参数 WHITE_PEN, BLACK_PEN 和 NULL_PEN 来检索这些画笔中的一个 与应用程序创建的标准图形对象不同, 备用对象不应
该被应用程序删除 相反, 当画笔不再需要的时候, 应用程序只应简单地将画笔从设备描述表中取消选择即可 要在 Windows 下创建自定义画笔, 可以使用以下两个函数 第一个是 : HPEN CreatePen (int fnpenstyle, int nwidth, COLORREF crcolor); fnpenstyle 规定要绘制的线条的外观 例如, 使用 PS_DASH 标志可以创建一个虚线 Windows CE 只支持 PS_SOLID PS_DASH 和 PS_NULL 这三个风格标志 nwidth 参数规定画笔的宽度 最后,crColor 规定画笔的颜色 crcolor 的参数类型是 COLORREF, 可以使用 RGB 宏来构造该类型 RGB 宏定义如下 : COLORREF RGB (BYTE bred, BYTE bgreen, BYTE bblue); 因此要创建一个红色实心线条, 代码看起来像这样 : hpen = CreatePen (PS_SOLID, 1, RGB (0xff, 0, 0)); 另一种画笔创建函数如下 : HPEN CreatePenIndirect (const LOGPEN *lplgpn); 其中逻辑画笔结构 LOGPEN 定义如下 : typedef struct taglogpen { UINT lopnstyle; POINT lopnwidth; COLORREF lopncolor; LOGPEN; CreatePenIndirect 用不同的形式为 Windows 提供了同样的参数 用 CreatePenIndirect 创建同样是 1 像素宽的红色画笔, 代码如下 : LOGPEN lp; HPEN hpen; lp.lopnstyle = PS_SOLID; lp.lopnwidth.x = 1; lp.lopnwidth.y = 1; lp.lopncolor = RGB (0xff, 0, 0); hpen = CreatePenIndirect (&lp); Windows CE 不支持复杂画笔, 比如宽度超过 1 像素的虚线 要确定支持什么, 我们熟悉的 GetDeviceCaps 就派上用场了, 给它的第 2 个参数取 LINECAPS 即可 具体可以参考 Windows CE 文档 形状线条很有用, 不过 Windows 还提供了绘制形状的函数, 包括填充和非填充的形状 Windows CE 提供了 Windows 程序员大部分常见的函数 Rectangle, RoundRect, Ellipse 和 Polygon 都支持 画刷在讨论矩形和椭圆形之前, 需要先讲述另一个曾经简要提到过的 GDI 对象 -- 画刷 (brush) 画刷通常是一个 8*8 像素的位图, 用于填充形状 Windows 也用它来填充客户窗口地背景 Windows CE 提供许多备用画刷, 并提供从应用程序定义的图案创建画刷的能力 许多纯色备用画刷可以使用 GetStockObject 来检索 在众多
可用画刷中, 有一个是用于四色灰度级显示器的每个灰度的, 四种灰度是 : 白色 浅灰色 深灰色和黑色 要创建纯色画刷, 可以调用以下函数 : HBRUSH CreateSolidBrush (COLORREF crcolor); crcolor 规定了画刷的颜色 颜色可以使用 RGB 宏来指定 Windows CE 下用 Win32 函数 CreateDIBPatternBrushPt 创建自定义图案的画刷 函数原型如下 : HBRUSH CreateDIBPatternBrushPt (const void *lppackeddib, UINT iusage); 第一个参数指向紧凑格式的 DIB 这意味着指针指向一个缓冲区, 包含有 BITMAPINFO 结构, 并且紧随其后的是位图的位数据 您应该还记得 BITMAPINFO 结构实际上由 BITMAPINFOHEADER 结构及紧随其后的 RGBQUAD 格式的调色板构成, 所以该缓冲区包含了创建 DIB 所需要的每个信息, 即位图信息 调色板 位图的位数据 如果第二个参数设置为 DIB_RGB_COLORS, 则调色板在每个入口都包含有 RGBQUAD 值 对于每像素 8 位的位图来说, 可以设置 DIB_PAL_COLORS 标志, 但 Windows CE 会忽略位图的颜色表 在 Windows CE 下 CreateDIBPatternBrushPt 更加重要, 因为 Windows CE 下不支持阴影画刷, 而其它版本的 Windows 使用 CreateHatchBrush 函数来支持阴影画刷 阴影画刷是由水平线条 垂直线条或斜线构成的画刷 这些画刷在灰度级显示器上特别有用, 因为您可以使用不同阴影图案来突出图表的不同区域 不过, 您可以使用 CreateDIBPatternBrushPt 和适当的位图图案来复制出这些画刷 本章后面的示例代码演示了在 Windows CE 下创建阴影画刷的方法 默认情况下, 画刷原点在窗口左上角 这并不总是你所希望的 例如, 一个条形图使用阴影画刷填充从 (100,100) 到 (125,220) 的矩形 因为该矩形不能被 8( 画刷通常是 8*8 像素的正方形 ) 整除, 所以就用一个不怎么美观的局部画刷来填充条形图的左上角 为了避免这种情况, 您可以移动画刷的原点, 这样可以使用与形状的边角正确对齐的画刷来绘制各个形状了 用来完成这一调整的函数如下 :BOOL SetBrushOrgEx (HDC hdc, int nxorg, int nyorg, LPPOINT lppt); nxorg 和 nyorg 允许将原点设置为 0 到 7 之间, 这样就可以将原点定位在画刷 8*8 范围内任何一点 lppt 填充的是画刷先前的版本, 这样可以在必要的时候恢复先前的原点 矩形矩形函数绘制一个填充矩形或者一个中空矩形 该函数定义如下 : BOOL Rectangle (HDC hdc, int nleftrect, int ntoprect, int nrightrect, int nbottomrect); 该函数使用当前选择的画笔绘制矩形外框, 使用当前画刷填充内部 要绘制中空矩形, 需要在调用 Rectangle 之前把空画刷选择进设备描述表中 理解绘制边框的实际像素是很重要的 假定我们要在 (0,0) 处绘制一个 5*7 的矩形, 函数调用如下 : Rectangle(0,0,5,7); 假设画笔是 1 像素宽, 结果矩形如图 2-6 所示 图 2-6: 放大后的用 Rectangele 绘制的矩形视图 注意观察矩形右边界实际上是如何绘制到第 4 列的, 底部边缘是如何绘制到第 6 行的 这是标准的 Windows 惯例 矩形在 Rectangle 函数指定的右边界和底边界以内进行绘制 如果选择
的画笔宽度超过 1 个像素, 右边界和底边界以边框矩形居中进行绘制 (Windows 其它版本允许使用 PS_INSIDEFRAME 画笔风格忽略画笔宽度, 强制在框架内绘制矩形 ) 圆和椭圆可以用 Ellipse 函数绘制圆和椭圆, 该函数原型如下 : BOOL Ellipse (HDC hdc, int nleftrect, int ntoprect, int nrightrect, int nbottomrect); 使用传入的矩形作为边界矩形 (bounding rectangle) 来绘制椭圆, 如图 2-7 所示 对于 Rectangle 函数, 使用当前画刷来填充椭圆内部, 使用当前画笔来绘制椭圆外框 图 2-7: 展示了 Ellipse 使用传入的边界矩形绘制出的椭圆 圆角矩形 RoundRect 函数绘制一个圆角矩形, 原型如下 : BOOL RoundRect (HDC hdc, int nleftrect, int ntoprect, int nrightrect, int nbottomrect, int nwidth, int nheight); 最后两个参数给出了用于圆角的椭圆的宽度和高度, 如图 2-8 所示 指定椭圆的高度和宽度可以让程序绘制出完全一样的均匀的圆角 缩短椭圆的高度, 可以使矩形的两侧更平, 而缩短椭圆的宽度可以使矩形的顶部和底部更平 图 2-8: 椭圆的高度和宽度规定了由 RoundRect 绘制的矩形的圆角多边形最后,Polygon 绘制了一个多边形, 函数原型如下 :BOOL Polygon (HDC hdc, const POINT *lppoints, int ncount); 第 2 个参数是一个指向 Point 结构数组的指针, 该数组定义了描述多边形的各个点 从最终形状上看, 会被点数多一条边, 因为函数自动把最后一个点和第一个点连接起来, 绘制出多边形的最后一条边 填充函数前面提到的函数都是使用画刷和画笔的组合在设备描述表上绘制形状的 只填充区域而不涉及绘制形状轮廓的画笔的函数也是有的 这些函数中的第一个就如下所示 : int FillRect (HDC hdc, CONST RECT* lprc, HBRUSH hbr); FillRect 的参数设备描述表句柄 需要填充的矩形以及用来填充矩形的画刷 要在矩形区域里绘制一个纯色或者图案, 使用 FillRect 函数会是一个快捷方便的方式 虽然 FillRect 很方便, 但 GradientFill 可能更棒一些 GradientFill 函数填充一个矩形区域, 使用一个颜色从一边开始绘制, 并逐渐平滑过度到另外一个颜色直到另外一边 图 2-9 展示了一个客户区使用 GradientFill 进行绘制的窗口 书中印刷的黑白插图不能看出颜色的效果, 但即使在这样的图上, 依然能轻易的看到平滑过度的样子 图 2-9: 使用 GradientFill 函数绘制的窗口 GradientFill 函数原型如下 : BOOL GradientFill (HDC hdc, PTRIVERTEX pvertex, ULONG dwnumvertex, PVOID pmesh, ULONG dwnummesh, ULONG dwmode); 第一个参数依旧是设备描述表 pvertex 是指向 TRIVERTEX 结构数组的指针,dwNumVertex 是 TRIVERTEX 数组中入口的数量 TRIVERTEX 结构定义如下 : struct _TRIVERTEX { LONG x; Long y;
COLOR16 Red; COLOR16 Green; COLOR16 Blue; COLOR16 Alpha;s TRIVERTEX; TRIVERTEX 结构的各个域描述了设备描述表里的一个点和一个 RGB 颜色 这些点应该是要填充的矩形的左上角和右下角 pmesh 是指向 GRADIENT_RECT 结构的指针, 该结构定义如下 : struct _GRADIENT_RECT { ULONG UpperLeft; ULONG LowerRight; GRADIENT_RECT; GRADIENT_RECT 结构简单指出 TRIVERTEX 结构中的哪些入口是描述左上角或者是右下角的 最后,dwNumMesh 参数包含 GRADIENT_RECT 结构的数量 dwmode 结构包含标志位, 指出是从左到右 (GRADIENT_FILL_RECT_H) 填充还是从上到下 (GRADIENT_FILL_RECT_V) 填充 GradientFill 函数实际上比表面上看到的要更复杂, 因为在桌面系统里, 它还执行三角形填充, 这种填充方式在 Windows CE 下不支持 下面是创建图 2-9 的窗口的代码片段 : TRIVERTEX vert[2]; GRADIENT_RECT grect; vert [0].x = prect->left; vert [0].y = prect->top; vert [0].Red = 0x0000; vert [0].Green = 0x0000; vert [0].Blue = 0xff00; vert [0].Alpha = 0x0000; vert [1].x = prect->right; vert [1].y = prect->bottom; vert [1].Red = 0x0000; vert [1].Green = 0xff00; vert [1].Blue = 0x0000; vert [1].Alpha = 0x0000; grect.upperleft = 0; grect.lowerright = 1; GradientFill(hdc,vert,2,&gRect,1,GRADIENT_FILL_RECT_H); Shapes 示例程序清单 2-3 中,Shapes 程序演示了很多相关函数 在 Shapes 中, 绘制了 4 个图形, 每个使用不同的画刷进行了填充 Listing 2-3: Shapes 示例程序 Shapes.h =================================================
=============== Header file Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== Returns number of elements #define dim(x) (sizeof(x) / sizeof(x[0])) ---------------------------------------------------------------------- Generic defines and data types struct decodeuint { Structure associates UINT Code; messages with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); ; struct decodecmd { Structure associates UINT Code; menu IDs with a LRESULT (*Fxn)(HWND, WORD, HWND, WORD); function. ; ---------------------------------------------------------------------- Defines used by MyCreateHatchBrush typedef struct { BITMAPINFOHEADER bmi; COLORREF dwpal[2]; BYTE bbits[64]; BRUSHBMP; #define HS_HORIZONTAL 0 /* ----- */ #define HS_VERTICAL 1 /* */ #define HS_FDIAGONAL 2 /* \\\\\ */ #define HS_BDIAGONAL 3 /* / */ #define HS_CROSS 4 /* +++++ */ #define HS_DIAGCROSS 5 /* xxxxx */ ---------------------------------------------------------------------- Function prototypes HWND InitInstance (HINSTANCE, LPWSTR, int); int TermInstance (HINSTANCE, int);
Window procedures LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM); Message handlers LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM); Shapes.cpp ================================================= ===================== Shapes- Brush and shapes demo for Windows CE Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include "shapes.h" Program-specific stuff ---------------------------------------------------------------- Global data const TCHAR szappname[] = TEXT ("Shapes"); HINSTANCE hinst; Program instance handle Message dispatch table for MainWindowProc const struct decodeuint MainMessages[] = { WM_PAINT, DoPaintMain, WM_DESTROY, DoDestroyMain, ; ================================================= ===================== Program entry point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { MSG msg; HWND hwndmain; Initialize this instance. hwndmain = InitInstance(hInstance, lpcmdline, ncmdshow); if (hwndmain == 0) return 0x10;
Application message loop while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); Instance cleanup return TermInstance (hinstance, msg.wparam); ---------------------------------------------------------------------- InitInstance - Instance initialization HWND InitInstance (HINSTANCE hinstance, LPWSTR lpcmdline, int ncmdshow){ WNDCLASS wc; HWND hwnd; Save program instance handle in global variable. hinst = hinstance; #if defined(win32_platform_pspc) If Pocket PC, allow only one instance of the application. hwnd = FindWindow (szappname, NULL); if (hwnd) { SetForegroundWindow ((HWND)(((DWORD)hWnd) 0x01)); return 0; #endif Register application main window class. wc.style = 0; Window style wc.lpfnwndproc = MainWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = szappname; Window class name if (RegisterClass (&wc) == 0) return 0; Create main window. hwnd = CreateWindowEx (WS_EX_NODRAG, Ex Style szappname, Window class TEXT("Shapes"), Window title
WS_VISIBLE, Style flags CW_USEDEFAULT, x position CW_USEDEFAULT, y position CW_USEDEFAULT, Initial width CW_USEDEFAULT, Initial height NULL, Parent NULL, Menu, must be null hinstance, Application instance NULL); Pointer to create parameters Return fail code if window not created. if (!IsWindow (hwnd)) return 0; Standard show and update calls ShowWindow (hwnd, ncmdshow); UpdateWindow (hwnd); return hwnd; ---------------------------------------------------------------------- TermInstance - Program cleanup int TermInstance (HINSTANCE hinstance, int ndefrc) { return ndefrc; ================================================= ===================== Message handling procedures for MainWindow ---------------------------------------------------------------------- MainWndProc - Callback function for application window LRESULT CALLBACK MainWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { INT i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(mainmessages); i++) { if (wmsg == MainMessages[i].Code) return (*MainMessages[i].Fxn)(hWnd, wmsg, wparam, lparam);
return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------- MyCreateHatchBrush - Creates hatched brushes HBRUSH MyCreateHatchBrush (INT fnstyle, COLORREF clrref) { BRUSHBMP brbmp; BYTE *pbytes; int i; DWORD dwbits[6][2] = { {0x000000ff,0x00000000, {0x10101010,0x10101010, {0x01020408,0x10204080, {0x80402010,0x08040201, {0x101010ff,0x10101010, {0x81422418,0x18244281, ; if ((fnstyle < 0) (fnstyle > dim(dwbits))) return 0; memset (&brbmp, 0, sizeof (brbmp)); brbmp.bmi.bisize = sizeof (BITMAPINFOHEADER); brbmp.bmi.biwidth = 8; brbmp.bmi.biheight = 8; brbmp.bmi.biplanes = 1; brbmp.bmi.bibitcount = 1; brbmp.bmi.biclrused = 2; brbmp.bmi.biclrimportant = 2; Initialize the palette of the bitmap. brbmp.dwpal[0] = PALETTERGB(0xff,0xff,0xff); brbmp.dwpal[1] = PALETTERGB((BYTE)((clrref >> 16) & 0xff), (BYTE)((clrref >> 8) & 0xff), (BYTE)(clrref & 0xff)); Write the hatch data to the bitmap. pbytes = (BYTE *)&dwbits[fnstyle]; for (i = 0; i < 8; i++) brbmp.bbits[i*4] = *pbytes++; Return the handle of the brush created. return CreateDIBPatternBrushPt (&brbmp, DIB_RGB_COLORS); ---------------------------------------------------------------------- DoPaintMain - Process WM_PAINT message for window.
LRESULT DoPaintMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PAINTSTRUCT ps; RECT rect; HDC hdc; POINT ptarray[6]; HBRUSH hbr, holdbr; TCHAR sztext[128]; GetClientRect (hwnd, &rect); hdc = BeginPaint (hwnd, &ps); Draw ellipse. hbr = (HBRUSH) GetStockObject (DKGRAY_BRUSH); holdbr = (HBRUSH) SelectObject (hdc, hbr); Ellipse (hdc, 10, 50, 90, 130); SelectObject (hdc, holdbr); Draw round rectangle. hbr = (HBRUSH) GetStockObject (LTGRAY_BRUSH); holdbr = (HBRUSH) SelectObject (hdc, hbr); RoundRect (hdc, 95, 50, 150, 130, 30, 30); SelectObject (hdc, holdbr); Draw hexagon using Polygon. hbr = (HBRUSH) GetStockObject (WHITE_BRUSH); holdbr = (HBRUSH) SelectObject (hdc, hbr); ptarray[0].x = 192; ptarray[0].y = 50; ptarray[1].x = 155; ptarray[1].y = 75; ptarray[2].x = 155; ptarray[2].y = 105; ptarray[3].x = 192; ptarray[3].y = 130; ptarray[4].x = 230; ptarray[4].y = 105; ptarray[5].x = 230; ptarray[5].y = 75; Polygon (hdc, ptarray, 6); SelectObject (hdc, holdbr); hbr = (HBRUSH) MyCreateHatchBrush (HS_DIAGCROSS, RGB (0, 0, 0));
holdbr = (HBRUSH) SelectObject (hdc, hbr); Rectangle (hdc, 10, 145, 225, 210); SelectObject (hdc, holdbr); DeleteObject (hbr); SetBkMode (hdc, OPAQUE); lstrcpy (sztext, TEXT ("Opaque background")); ExtTextOut (hdc, 20, 160, 0, NULL, sztext, lstrlen (sztext), NULL); SetBkMode (hdc, TRANSPARENT); lstrcpy (sztext, TEXT ("Transparent background")); ExtTextOut (hdc, 20, 185, 0, NULL, sztext, lstrlen (sztext), NULL); EndPaint (hwnd, &ps); return 0; ---------------------------------------------------------------------- DoDestroyMain - Process WM_DESTROY message for window LRESULT DoDestroyMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PostQuitMessage (0); return 0; 在 Shapes 里,OnPaintMain 使用先前讨论过的那些不同的函数绘制了四个图形 对每个形状, 都创建了一个不同的画刷, 并选择进设备描述表, 绘制图形, 最后从设备描述表中取消选择 第一个图形使用纯灰色阴影来填充, 用的是 GetStockObject 函数来装载这些纯色画刷的 最后一个图形则是使用由 CreateDIBPatternBrushPt 函数创建的画刷来填充的 该画刷的创建过程封装到 MyCreateHatchBrush 函数里了, 该函数模拟了 Windows CE 下不支持的 CreateHatchBrush 函数 通过填充一个位图结构和设置构成阴影图案的位数据, 创建了一个黑白位图, 用于创建阴影画刷 这个位图是一个由 CreateDIBPatternBrushPta 函数指定的 8*8 的位图 因为该位图是单色的, 所以包括调色板和头在内, 它的总大小只有大约 100 字节 注意, 因为位图的每个扫描行必须是双字对齐的, 所以每个一字节扫描行的最后 3 个字节是没有使用的 最后, 程序写两行文字到最后的矩形里 文本进一步演示了 opaque 和 transparent 这两个系统绘图模式之间的差异 在本例中, 文本绘制时使用 opaque 模式会更适合实际情况, 因为在 transparent 模式下, 阴影线会使字母变的模糊 图 2-10 展示了 Shapes 窗口 图 2-10 演示了如何绘制不同填充的图形 为了使事情简单一些, 例子 Shapes 假设其运行在至少 240 像素宽的显示器上 这使得 Shapes 程序在手持式 PC 和 Pocket PC 上都能很好的运行 我只是浅显的描述了 Windows CE 中 GWE 的 GDI 部分, 本章的目标并不是为 GDI 编程的各方面提供完整的描述 相反, 我想去演示
Windows CE 下用于基本图画和文本的方法 在本书其它章节里, 扩展了本章涉及的技术 通常, 在演示如何在代码中使用的时候, 我会谈论这些新技术和新引入的函数 为了扩展您的知识, 我推荐 Windows 程序设计 (Programming Windows, 5th edition, by Charles Petzold) (Microsoft Press, 1998) 一书作为学习 Windows GDI 的最佳资料 既然我们已经看了输出, 那么是时候把注意力转到系统输出 -- 键盘和触摸板方面了 WinCE 程序设计 (3rd 版 )-- 第 3 章输入 : 键盘 鼠标和触摸屏 -- 概述概述传统上, 微软 Windows 平台为用户提供两种输入方式 : 键盘和鼠标 Windows CE 继承了这一方式, 但在其大部分系统上, 使用输入笔和触摸屏替代了鼠标 从程序角度看, 这种变化很小, 因为输入笔的消息已经被映射成 Windows 其它版本中使用的鼠标消息了 同 PC 版的 Windows 比,Windows CE 上一个很细微但又很重要的变化是要么是只有一个很小的键盘, 要么根本没有键盘 这种配置使笔式输入对 Windows CE 更加重要了 WinCE 程序设计 (3rd 版 )--3.1 键盘 键盘虽然键盘在 Windows CE 中作用减少了, 但键盘依然是录入大量信息的最好方法 即使像在 Pocket PC 这类没有物理键盘的系统上, 用户使用最多的也还是软键盘 -- 在触摸屏上模拟键盘 的控件 基于此, 除了极其特殊的 Windows CE 应用程序外, 对键盘输入的适当操作是很重要 的 虽然在本书后面章节我会详细讨论软键盘, 但有一点应该先提一下 对于应用程序, 软键盘 的输入同传统硬件键盘的输入是没什么不同的 输入焦点 在 Windows 操作系统下, 同时只有一个窗口拥有输入焦点 有焦点的窗口接收所有键盘输入, 直到焦点切换到另外一个窗口 虽然系统使用很多规则来分配键盘焦点, 但通常有焦点的窗口就 是当前活动窗口 活动窗口就是用户当前正在交互的顶层窗口 除了极少数例外, 活动窗口通常 位于 Z 坐标的顶部, 也就是说, 活动窗口是绘制在系统里其它窗口上面的 在资源浏览器 (Explorer) 里, 用户可以按 Alt-Esc 键在程序间切换, 以改变活动窗口, 或者在任务栏里点 另一个顶层窗口的按钮来切换活动窗口 焦点窗口是活动窗口或者其子窗口之一 Windows 下, 程序可以通过调用 GetFocus 来判断哪个窗口拥有输入焦点, 函数原型如下 : HWND GetFocus (void); 通过调用 SetFocus 可以把焦点切换到另外一个窗口, 函数原型如下 :HWND SetFocus (HWND hwnd); 在 Windows CE 下, 对 SetFocus 的目标窗口有一些限制 通过 SetFocus 来获取焦点的窗口必须是调用 SetFocus 的线程创建的窗口 该规则的一个例外是 : 如果失去焦点的窗口和即将获得焦点的窗口是父子或兄弟关系, 那即使这两个窗口是被不同线程创建的, 也可以切换焦点 当窗口失去焦点时,Windows 会给该窗口发送 WM_KILLFOCUS 消息, 通知窗口新的状态信息 wparam 参数则包含即将获得焦点的窗口句柄 获得焦点的窗口会收到 WM_SETFOCUS 消息, 消息的 wparam 参数包含了失去焦点的窗口的句柄 还要再叮嘱一下 程序不应该在没有用户输入的情况下改变焦点窗口 否则, 用户很容易变的迷惑 SetFocus 的一个适当用途是给活动窗口里的子窗口 ( 更多是控件 ) 设置输入焦点 在这种
情况下, 程序让想接收键盘消息的窗口用其子窗口的句柄来调用 SetFocus, 以响应 WM_SETFOCUS 消息 键盘消息除了一些小的例外,Windows CE 与桌面版的 Windows 具有相同的键盘消息处理过程, 当键被按下,Windows 给焦点窗口发送一系列消息, 通常都是以 WM_KEYDOWN 消息开始的 如果被按下的键代表诸如字母或数字等字符,Windows 会在 WM_KEYDOWN 之后发送一个 WM_CHAR 消息 ( 一些按键, 例如功能键和光标键等, 不代表字符, 则不会发送 WM_CHAR 对这些按键, 程序必须翻译 WM_KEYDOWN 消息来了解这些按键是什么时候被按下的 ) 当按键被释放,Windows 会发送一个 WM_KEYUP 消息 如果按键按的时间长一些, 则自动重复功能就会开启, 多条 WM_KEYDOWN 消息和 WM_CHAR 消息会被送出, 直到最后键被释放, 发出 WM_KEYUP 消息 当 Alt 键和另一个键一起被按下时, 上面讨论的消息会被 WM_SYSKEYDOWN WM_SYSKEYCHAR 和 WM_SYSKEYUP 消息替代 对所有这些消息, 几乎都按相同的方式使用参数 wparam 和 lparam 对 WM_KEYxx 和 WM_SYSKEYxx 消息,wParam 包含虚拟键值, 用于指出当前被按下的键 所有版本的 Windows 都在键盘硬件和应用程序之间提供了一个中间层, 用于把键盘返回的扫描码转换成虚拟键盘值 表 3-1 列出了 VK_xx 值及对应的键 虽然虚拟键表很大, 但不是所有表中列的键都能用于 Windows CE 设备 例如, 作为 PC 键盘上很主要的键并列在虚拟键表中的功能键, 却并没有出现在大部分 Windows CE 键盘上 实际上,PC 键盘上的许多键都从空间受限的 Windows CE 键盘上去除了 图 3-1 给出了通常很少用在 Windows CE 设备上的键列表, 该表只是告诉你这些键在 Windows CE 键盘上可能不存在, 但并不是说绝对不存在 表 3-1: 虚拟键略图 3-1:PC 键盘中很少用于 Windows CE 键盘的键对 WM_CHAR 和 WM_SYSCHAR 消息来说,wParam 包含键对应的 Unicode 字符 多数情况下, 应用程序只是处理 WM_CHAR 消息, 而忽略 WM_KEYDOWN 和 WM_KEYUP 消息 WM_CHAR 消息作为第 2 级抽象, 使应用程序不必考虑键的释放或按下状态, 而将精力集中在键盘输入的字符上 这些键盘消息中的 lparam 值含有关于按下的键的进一步的信息 图 3-2 给出了 lparam 参数的格式 从 0 到 15 的底字位, 包含键的重复次数 有时 Windows CE 设备上的键被按的很快, 超过了 Windows CE 发送消息到焦点应用程序的速度 在这种情况下, 重复计数器包含键被按下的次数 第 29 比特位是上下文标志位 如果键被按下的同时,Alt 键被按下, 则设置该位 30 位是键的先前状态 如果键先前是按下的, 则该位被设置, 否则该位是 0 该标志可以用来判断键消息是否是自动重复的结果 31 位指出转换状态 如果键从按下转成释放, 该位被设置 16-28 位用来指出键的扫描码 在许多情况下,Windows CE 并不支持该域 然而, 在一些扫描码作为必需品的新的 Windows CE 平台上, 这个域包含扫描码 除非您知道在您的特定平台支持它, 否则您不应该设想扫描码域是可用的 图 3-2:lParam 用于键消息时的布局一个额外的键盘消息,WM_DEADCHAR, 有时可能会有用 当按下的键代表一个诸如元音变音的死字符, 用它和一个字符组合起来创建一个不同的字符的时候, 您需要发送该消息 在这种情况下,WM_DEADCHAR 消息用来防止光标移动到下一格, 直到第 2 个键被按下, 这样您可以完成组合后的字符
WM_DEADCHAR 消息在 Windows 下总是有的, 但在 Windows CE 下它的作用可能更大一点 随着运行 Windows CE 的小消费品设备国际化, 程序员应该, 也有必要使用在外语系统里经常需要的 WM_DEADCHAR 消息 键盘函数您会发现用于判断键盘状态的函数对 Windows 应用程序是很有用的 在键盘函数中, 有两个密切相关但又经常混淆的函数 :GetKeyState 和 GetAsyncKeyState GetKeyState 的原型如下 : short GetKeyState (int nvirtkey); 它返回大小写字母键盘 ( 换挡键 ) Ctrl Alt 和 Shift 的按下 / 释放状态, 指出这些键是否在转换状态 如果键盘有两个键具有同样的功能, 例如, 键盘两边的那两个 Shift 键, 则该函数可以用来区分是哪个被按下 ( 大部分键盘有左右 Shife 键, 一些有左右 Ctrl 和 Alt 键 ) nvirkey 表示要查询的键的虚拟键码 如果返回值的高位被设置, 则该键被按下 如果返回值的最低有效位被设置, 则该键在转换状态 ; 也就是说, 自从系统启动开始, 该键已被按下奇数次 返回的状态是消息从消息队列里读出时的状态, 并不是键的实时状态 需要注意的是,Alt 键的虚拟键是 VK_MENU 注意 GetKeyState 函数在 Windows CE 下查询换挡键的状态是受限制的 在 Windows 的其它版本里,GetKeyState 可以用来判断键盘上的每个键的状态 要判断键的实时状态, 可以使用 short GetAsyncKeyState(int vkey); 同 GetKeyState 一样, 传入要查询的键的虚拟键码 GetAsyncKeyState 函数返回值同 GetKeyState 的略微不同 和 GetKeyState 一样, 当键被按下, 返回值的高位被设置 然而, 如果在上次调用 GetAsyncKeyState 后, 键被按下, 则最低有效位也被设置 同 GetKeyState 一样,GetAsyncKeyState 可以区分左右 Shift Ctrl 及 Alt 键 另外, 通过传递虚拟值 VK_LBUTTON,GetAsyncKeyState 可以判断触摸笔是否正在触摸屏幕 通过 keybd_event 函数, 应用程序可以模拟击键动作 void keybd_event(byte bvk, BYTE bscan, DWORD dwflags, DWORD dwextrainfo); 第一个参数是要模拟的键的虚拟键码 bscan 码在 Windows CE 下应该设置为 NULL dwflags 参数可以取两个可能的值 :KEYEVENTF_KEYUP, 表示该调用是模拟键盘释放事件, KEYEVENTF_SILENT 表示模拟的键盘按下动作不会产生通常的键盘滴答声 所以, 要完全模拟键按下操作,keydb_event 应该调用 2 次 一次不带 KEYEVENTF_KEYUP, 来模拟键按下, 之后在用 KEYEVENTF_KEYUP 调用一次来模拟键盘释放 当模拟换挡键时, 要指出具体的左右 VK 码, 比如 VK_LSHIF 或 VK_RCONTROL Windows CE 特有的函数是 BOOL PostKeybdMessage (HWND hwnd, UINT VKey, KEY_STATE_FLAGS KeyStateFlags, UINT ccharacters, UINT *pshiftstatebuffer, UINT *pcharacterbuffer ); 该函数发送一系列的键到指定的窗口 hwnd 参数是目标窗口, 该窗口必须是调用线程所拥有的 VKey 参数为 0 KeyStateFlags 为所有即将发送的键规定键的状态 ccharacters 参数指定要发送的键的数量 pshiftstatebuffer 参数指向一个数组, 包含了每个键的 shift 状态 ; pcharacterbuffer 指向键的 VK 码 不像 keybd_event, 该函数不改变键盘的全局状态
最后一个键盘函数 MapVirtualKey 把虚拟键码翻译成字符 Windows CE 下的 MapVirtualKey 并不进行键盘扫描码和虚拟码之间的转换, 虽然在 Windows 的其它版本里它会这么做 函数原型如下 : UINT MapVirtualKey (UINT ucode, UINT umaptype); Windows CE 下, 第一个参数是待翻译的虚拟键码, 第二个参数 umaptype 指出如何翻译键码 MapVirtualKey 依赖于实现支持功能的键盘设备驱动程序 许多 OEM 设备并不实现这种支持函数, 所以在这种系统上,MapVirtualKey 会失败 键盘测试要判断系统里是否有键盘, 可以调用 DWORD GetKeyboardStatus (VOID); 如果系统里键盘, 则该函数返回 KBDI_KEYBOARD_PRESENT 标志 如果键盘启用, 则该函数返回 KBDI_KEYBOARD_ENABLED 标志 要让键盘失效, 可以把 benable 设置为 FALSE 来调用 BOOL EnableHardwareKeyboard (BOOL benable) 对键盘折叠在屏幕后面的系统, 您可能需要使键盘失效, 因为在这种系统里, 当使用触摸笔时, 用户可能会意外地按下键盘 KeyTrac 示例程序下面的示例程序 KeyTrac 显示了键盘消息的顺序 从程序上讲,KeyTrac 与书中早期的程序差别不大 不同点在于所有键盘消息都被捕获和记录在数组里, 并在 WM_PAINT 消息里被显示出来 对每个键盘消息, 消息名称记录 wparam lparam 值以及指出转换键状态的标志集合都被记录下来 键盘消息之所以被记录到数组里是因为这些消息比重绘操作要产生的快 图 3-3 显示了一些键被按下后的 KeyTrac 窗口 图 3-3 略 :Shift-A 键组合以及小写 a 键被按下后的 KeyTrac 窗口了解键盘消息顺序的最好方式是运行 KeyTrac, 按一些键, 之后观察消息滚动显示在屏幕上 按一个字符键, 例如 a, 会产生三个消息 :WM_KEYDOWN,WM_CHAR 和 WM_KEYUP 按 a 的同时按下 Shift 键, 随后释放 Shift 键, 会产生 Shift 键按下的消息, 随后是为 a 键产生的三个消息, 最后是为 Shift 键产生的键释放消息 因为 Shift 键本身不是字符键, 所以没有发送 WM_CHAR 消息 然而, 在 a 键对应的 WM_CHAR 消息中, 现在 wparam 包含的是 0x41, 表示一个大写的 A 被输入, 而不是小写的 a 清单 3-1 列出了 KeyTrac 的源代码 清单 3-1:KeyTrac 程序 KeyTrac.h ================================================= ===================== Header file Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== Returns number of elements #define dim(x) (sizeof(x) / sizeof(x[0])) ---------------------------------------------------------------------- Generic defines and data types
struct decodeuint { Structure associates UINT Code; messages with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); ; struct decodecmd { Structure associates UINT Code; menu IDs with a LRESULT (*Fxn)(HWND, WORD, HWND, WORD); function. ; ---------------------------------------------------------------------- Program-specific defines and structures typedef struct { UINT wkeymsg; INT wparam; INT lparam; LPCTSTR pszmsgtxt; TCHAR szshift[20]; MYKEYARRAY, *PMYKEYARRAY; Structure to associate messages with text name of message typedef struct { UINT wmsg; LPCTSTR pname; KEYNAMESTRUCT; ---------------------------------------------------------------------- Function prototypes HWND InitInstance (HINSTANCE, LPWSTR, int); int TermInstance (HINSTANCE, int); Window procedures LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM); Message handlers LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoKeysMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM); KeyTrac.cpp ================================================= ===================== KeyTrac - displays keyboard messages
Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include <commctrl.h> Command bar includes #include "keytrac.h" Program-specific stuff The include and lib files for the Pocket PC are conditionally included so that this example can share the same project file. This is necessary since this example must have a menu bar on the Pocket PC to have a SIP button. #if defined(win32_platform_pspc) #include <aygshell.h> Add Pocket PC includes. #pragma comment( lib, "aygshell" ) Link Pocket PC lib for menu bar. #endif ---------------------------------------------------------------------- Global data const TCHAR szappname[] = TEXT ("KeyTrac"); HINSTANCE hinst; Program instance handle Program-specific global data MYKEYARRAY ka[16]; int nkeycnt = 0; int nfontheight; Array associates key messages with text tags KEYNAMESTRUCT knarray[] = {{WM_KEYDOWN, TEXT ("WM_KEYDOWN"), {WM_KEYUP, TEXT ("WM_KEYUP"), {WM_CHAR, TEXT ("WM_CHAR"), {WM_SYSCHAR, TEXT ("WM_SYSCHAR"), {WM_SYSKEYUP, TEXT ("WM_SYSKEYUP"), {WM_SYSKEYDOWN, TEXT ("WM_SYSKEYDOWN"), {WM_DEADCHAR, TEXT ("WM_DEADCHAR"), {WM_SYSDEADCHAR, TEXT ("WM_SYSDEADCHAR"); Message dispatch table for MainWindowProc const struct decodeuint MainMessages[] = { WM_CREATE, DoCreateMain, WM_PAINT, DoPaintMain, WM_KEYUP, DoKeysMain, WM_KEYDOWN, DoKeysMain, WM_CHAR, DoKeysMain,
WM_DEADCHAR, DoKeysMain, WM_SYSCHAR, DoKeysMain, WM_SYSDEADCHAR, DoKeysMain, WM_SYSKEYDOWN, DoKeysMain, WM_SYSKEYUP, DoKeysMain, WM_DESTROY, DoDestroyMain, ; ================================================= ===================== Program entry point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { MSG msg; int rc = 0; HWND hwndmain; Initialize this instance. hwndmain = InitInstance (hinstance, lpcmdline, ncmdshow); if (hwndmain == 0) return 0x10; Application message loop while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); Instance cleanup return TermInstance (hinstance, msg.wparam); ---------------------------------------------------------------------- InitInstance - Instance initialization HWND InitInstance (HINSTANCE hinstance, LPWSTR lpcmdline, int ncmdshow) { WNDCLASS wc; HWND hwnd; #if defined(win32_platform_pspc) If Pocket PC, allow only one instance of the application hwnd = FindWindow (szappname, NULL); if (hwnd) { SetForegroundWindow ((HWND)(((DWORD)hWnd) 0x01)); return 0;
#endif hinst = hinstance; Save program instance handle Register application main window class. wc.style = 0; Window style wc.lpfnwndproc = MainWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = szappname; Window class name if (RegisterClass(&wc) == 0) return 0; Create main window. hwnd = CreateWindowEx (WS_EX_NODRAG, szappname, TEXT ("KeyTrac"), WS_VISIBLE WS_CAPTION WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL); Fail if window not created if (!IsWindow (hwnd)) return 0; Standard show and update calls ShowWindow (hwnd, ncmdshow); UpdateWindow (hwnd); return hwnd; ---------------------------------------------------------------------- TermInstance - Program cleanup int TermInstance (HINSTANCE hinstance, int ndefrc) { return ndefrc; ================================================= ===================== Message handling procedures for MainWindow ----------------------------------------------------------------------
MainWndProc - Callback function for application window LRESULT CALLBACK MainWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { INT i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(mainmessages); i++) { if (wmsg == MainMessages[i].Code) return (*MainMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoCreateMain - Process WM_CREATE message for window. LRESULT DoCreateMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { HDC hdc; TEXTMETRIC tm; #if defined(win32_platform_pspc) && (_WIN32_WCE >= 300) SHMENUBARINFO mbi; For Pocket PC, create memset(&mbi, 0, sizeof(shmenubarinfo)); menu bar so that we mbi.cbsize = sizeof(shmenubarinfo); have a sip button mbi.hwndparent = hwnd; mbi.dwflags = SHCMBF_EMPTYBAR; No menu SHCreateMenuBar(&mbi); #endif Get the height of the default font. hdc = GetDC (hwnd); GetTextMetrics (hdc, &tm); nfontheight = tm.tmheight + tm.tmexternalleading; ReleaseDC (hwnd, hdc); return 0; ---------------------------------------------------------------------- DoPaintMain - Process WM_PAINT message for window. LRESULT DoPaintMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) {
PAINTSTRUCT ps; RECT rect, rectout; TCHAR szout[256]; HDC hdc; INT i, j; LPCTSTR pkeytext; GetClientRect (hwnd, &rect); Create a drawing rectangle for the top line of the window. rectout = rect; rectout.bottom = rectout.top + nfontheight; hdc = BeginPaint (hwnd, &ps); if (nkeycnt) { for (i = 0; i < nkeycnt; i++) { Create string containing wparam, lparam, and shift data. wsprintf (szout, TEXT ("wp:%08x lp:%08x shift: %s"), ka[i].wparam, ka[i].lparam, ka[i].szshift); Look up name of key message. for (j = 0; j < dim (knarray); j++) if (knarray[j].wmsg == ka[i].wkeymsg) break; See if we found the message. if (j < dim (knarray)) pkeytext = knarray[j].pname; else pkeytext = TEXT ("Unknown"); Scroll the window one line. ScrollDC (hdc, 0, nfontheight, &rect, &rect, NULL, NULL); See if wide or narrow screen. if (GetSystemMetrics (SM_CXSCREEN) < 480) { If Pocket PC, display info on 2 lines ExtTextOut (hdc, 10, rect.top, ETO_OPAQUE, &rectout, szout, lstrlen (szout), NULL); Scroll the window another line. ScrollDC(hdc, 0, nfontheight, &rect, &rect, NULL, NULL); ExtTextOut (hdc, 5, rect.top, ETO_OPAQUE, &rectout, pkeytext, lstrlen (pkeytext), NULL); else {
Wide screen, print all on one line. ExtTextOut (hdc, 5, rect.top, ETO_OPAQUE, &rectout, pkeytext, lstrlen (pkeytext), NULL); ExtTextOut (hdc, 100, rect.top, 0, NULL, szout, lstrlen (szout), NULL); nkeycnt = 0; EndPaint (hwnd, &ps); return 0; ---------------------------------------------------------------------- DoKeysMain - Process all keyboard messages for window. LRESULT DoKeysMain (HWND hwnd, UINT wmsg, WPARAM wparam, lparam) { LPARAM if (nkeycnt >= 16) return 0; ka[nkeycnt].wkeymsg = wmsg; ka[nkeycnt].wparam = wparam; ka[nkeycnt].lparam = lparam; Capture the state of the shift flags. ka[nkeycnt].szshift[0] = TEXT ('\0'); if (GetKeyState (VK_LMENU)) lstrcat (ka[nkeycnt].szshift, TEXT ("la ")); if (GetKeyState (VK_RMENU)) lstrcat (ka[nkeycnt].szshift, TEXT ("ra ")); if (GetKeyState (VK_MENU)) lstrcat (ka[nkeycnt].szshift, TEXT ("A ")); if (GetKeyState (VK_LCONTROL)) lstrcat (ka[nkeycnt].szshift, TEXT ("lc ")); if (GetKeyState (VK_RCONTROL)) lstrcat (ka[nkeycnt].szshift, TEXT ("rc ")); if (GetKeyState (VK_CONTROL)) lstrcat (ka[nkeycnt].szshift, TEXT ("C ")); if (GetKeyState (VK_LSHIFT)) lstrcat (ka[nkeycnt].szshift, TEXT ("ls ")); if (GetKeyState (VK_RSHIFT)) lstrcat (ka[nkeycnt].szshift, TEXT ("rs ")); if (GetKeyState (VK_SHIFT)) lstrcat (ka[nkeycnt].szshift, TEXT ("S "));
nkeycnt++; InvalidateRect (hwnd, NULL, FALSE); return 0; ---------------------------------------------------------------------- DoDestroyMain - Process WM_DESTROY message for window. LRESULT DoDestroyMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PostQuitMessage (0); return 0; 下面是 KeyTrac 中需要注意的一些特性 在每个键盘消息被记录后, 一个 InvalidateRect 函数被调用, 用来强制重绘窗口, 并产生一个 WM_PAINT 消息 正如我在第二章中提到的, 一个程序不应该尝试发送或者提交 (send or post)wm_paint 消息给窗口, 因为在 Windows 用 WM_PAINT 消息调用一个窗口之前, 它需要执行一些设置工作 KeyTrac 中使用的另一个设备描述表函数是 BOOL ScrollDC (HDC hdc, int dx, int dy, const RECT *lprcscroll, const RECT *lprcclip, HRGN hrgnupdate, LPRECT lprcupdate); 该函数水平或者垂直滚动设备描述表的一个区域, 但在 Windows CE 下, 同时不会有两个方向 接下来的三个矩形参数定义了即将被滚动的区域, 滚动区域里将别裁减的区域和在滚动结束后将被绘制的区域 一个区域句柄可以传递给 ScrollDC 由 ScrollDC 定义的这个区域包含了在滚动后需要绘制的区域 最后, 如果 KeyTrac 窗口被覆盖和重新显示出来, 屏幕上显示的信息将丢失, 原因是设备描述表没有存储显示屏的位信息 由应用程序负责存储用于恢复屏幕客户区域所需要的任何信息 因为 KeyTrac 没有存储这些信息, 所以当窗口被覆盖的时候, 这些信息就丢失了 WinCE 程序设计 (3rd 版 )--3.2 鼠标和触摸屏 鼠标和触摸屏和桌面 PC 不同,Windows CE 设备并不总是有鼠标的 作为替代, 许多 Windows CE 设备都 有触摸屏和手写笔 但对有鼠标的 Windows CE 系统来说, 编程接口和桌面系统是一样的 鼠标消息 鼠标光标无论在什么时候移过屏幕, 光标下的最顶层窗口都会收到一个 WM_MOUSEMOVE 消 息 如果用户点鼠标左键或者右键, 窗口会收到 WM_LBUTTONDOWN 或 WM_RBUTTONDOWN 消息 ; 而当用户释放按键时, 窗口则会收到 WM_LBUTTONUP 或 WM_RBUTTONUP 消息 如果用户按下并释放鼠标滚轮, 窗口会收到 WM_MBUTTONDOWN 及 WM_MBUTTONUP 消息
对所有这些消息,wParam 和 lparam 都具有相同的值 wparam 包含一个标志位集合, 用来指出当前键盘上 Ctrl 或 Shift 键是否被按下 同 Windows 的其它版本一样, 在这些消息里没有提供 Alt 键的状态 要想获得消息发送时 Alt 键的状态, 可以使用 GetKeyState 函数 lparam 包含两个 16 位的值, 用来指出点击点在屏幕上的位置 低 16 位是相对窗口客户区左上角的 x( 水平 ) 坐标位置, 高 16 位是 y( 垂直 ) 坐标位置 如果用户双击, 也就是在预定义的时间内在屏幕同一位置点两次,Windows 会向被双击的窗口发送 WM_LBUTTONDBLCLK 消息, 不过只有当窗口类注册了 CS_DBLCLKS 风格时才会这么做 可以在用 RegisterClass 注册窗口类时设置类风格 您可以通过对比发送给窗口的消息来区分单击和双击 当双击发生时, 窗口首先收到来自最初单击的 WM_LBUTTONDOWN 和 WM_LBUTTONUP 消息 接下来一个 WM_LBUTTONDBLCLK 消息会在 WM_LBUTTONUP 后发出 一个技巧是, 禁止用任何方式响应 WM_LBUTTONDOWN 消息, 因为这会阻止随后的 WM_LBUTTONDBLCLK 消息 通常来说这没什么问题, 因为单击通常是选择一个对象, 而双击则是启动一个对象的默认行为 如果用户滚动鼠标轮, 窗口会收到 WM_MOUSEWHEEL 消息 对于该消息,lParam 的内容和其它鼠标消息的内容一样, 都是鼠标光标的水平和垂直位置 wparam 的低字位也是同样的位标志, 指出当前被按下的键 但 wparam 的高字位则包含的是滚轮的距离, 用常量 WHEEL_DELTA 的倍数来表示滚动的距离 如果该值为正, 表示滚轮是朝远离用户方向滚动 ; 如果该值为负, 表示滚轮是朝用户方向滚动 使用触摸屏触摸屏和手写笔这一组合对 Windows 平台来说相对是比较新的, 但幸运的是, 要把它们集成到 Windows CE 应用程序里相对还是比较容易的 处理手写笔的最好方法就是把它看成是一个单键鼠标 手写笔产生的鼠标消息, 同其它版本的 Windows 以及有鼠标的 Windows CE 里鼠标提供的消息相同 鼠标和手写笔的不同在于这两种输入设备的物理实体的不同 和鼠标不同, 手写笔没有光标来指示其当前位置 因此, 手写笔不能像鼠标光标那样在屏幕的一个点上盘旋 当用户将光标移过一个窗口而不按鼠标键的话, 光标就会盘旋 这个概念不适用于手写笔编程, 因为当手写笔不和屏幕接触的时候, 触摸屏是检测不到手写笔的位置的 手写笔和鼠标之间的差异带来的另一个后果是 : 没有鼠标光标, 那么应用程序不能通过改变盘旋光标的外貌来给用户提供反馈 基于触摸屏的 Windows CE 系统为这种典型的 Windows 反馈方式提供了光标设置功能 提示用户必须等待系统完成处理的沙漏光标, 在 Windows CE 下得到支持, 应用程序可以通过 SetCursor 函数来显示繁忙的沙漏, 这和其它版本的 Windows 里的应用程序使用的方式一样 手写笔消息当用户用手写笔在屏幕上压触时, 压触点下的顶层窗口如果此前没有输入焦点的话就会收到焦点, 随后收到 WM_LBUTTONDOWN 消息 当用户抬起手写笔时, 窗口会收到 WM_LBUTTONUP 消息 在手写笔按下的同时在同一个窗口内移动它, 窗口就会收到 WM_MOUSEMOVE 消息 电子墨水对手持设备来说最典型的应用是捕捉屏幕上用户在的笔迹并存储下来 这个过程不是手写识别, 只是简单的墨迹存储 在开始阶段, 完成这个功能的最好方法应该是存储由 WM_MOUSEMOVE
消息传入的手写笔的各个点 但有个问题, 就是有时候这些小型 CE 设备不能快速的发送消息, 导致不能获得满意的分辨率 因此在 Windows CE 下, 增加了一个函数来帮助程序员追踪手写笔 BOOL GetMouseMovePoints (PPOINT pptbuf, UINT nbufpoints, UINT *pnpointsretrieved); GetMouseMovePoints 返回没有产生 WM_MOUSEMOVE 消息的手写笔点数 函数参数包括点数组 数组大小和一个指向整数的指针, 用来接收返回给应用程序的点数 一旦接收完, 这些附加的点可以用来填充上一个 WM_MOUSEMOVE 消息和当前 WM_MOUSEMOVE 消息之间的空白 GetMouseMovePoints 产生一条曲线 它是按触摸板的分辨率返回点的, 而不是按屏幕的 触摸板的分辨率通常设置为屏幕分辨率的 4 倍, 所以您需要把 GetMouseMovePoints 返回的坐标除以 4 来转换成屏幕坐标 额外的分辨率是用在手写识别之类的程序中的 在简短的示例程序 PenTrac 中, 演示了 GetMouseMovePoints 带来的不同之处 图 3-4 显示了 PenTrac 窗口 注意观察窗口上的两条点线 上面的线是仅仅使用来自 WM_MOUSEMOVE 的点绘制的 底下的线则是包括了用 GetMouseMovePoints 查询到的点, 其中黑色点是来自 WM_MOUSEMOVE, 而红色 ( 浅色 ) 点是来自 GetMouseMovePoints 图 3-4( 略 ): 显示了两条线的 PenTrac 窗口清单 3-2 给出了 PenTrac 的源代码 该程序为每个接收到的 WM_MOUSEMOVE 或 WM_LBUTTONDOWN 消息在屏幕上绘制一个点 如果在鼠标移动期间 Shift 键被按下, PenTrac 会调用 GetMouseMovePoints 并把获得的这些点显示成红色, 用于和来自鼠标消息的点进行区分 为了加强 GetMouseMovePoints 的效果,PenTrac 做了一些手脚 在处理 WM_MOUSEMOVE 和 WM_LBUTTONDOWN 消息的 DoMouseMain 例程里, 调用了 sleep 函数来消耗掉一些毫秒时间 这个延迟模拟了那些没有时间处理及时处理每个鼠标移动消息的响应缓慢的应用程序 清单 3-2:PenTrac 程序 PenTrac.h ================================================= ===================== Header file Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== Returns number of elements. #define dim(x) (sizeof(x) / sizeof(x[0])) ---------------------------------------------------------------------- Generic defines and data types struct decodeuint { Structure associates
UINT Code; messages with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); ; struct decodecmd { Structure associates UINT Code; menu IDs with a LRESULT (*Fxn)(HWND, WORD, HWND, WORD); function. ; ---------------------------------------------------------------------- Function prototypes HWND InitInstance (HINSTANCE, LPWSTR, int); int TermInstance (HINSTANCE, int); Window procedures LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM); Message handlers LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoMouseMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM); PenTrac.cpp ================================================= ===================== PenTrac - Tracks stylus movement Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include "pentrac.h" Program-specific stuff ---------------------------------------------------------------------- Global data const TCHAR szappname[] = TEXT ("PenTrac"); HINSTANCE hinst; Program instance handle Message dispatch table for MainWindowProc const struct decodeuint MainMessages[] = { WM_LBUTTONDOWN, DoMouseMain, WM_MOUSEMOVE, DoMouseMain,
; WM_DESTROY, DoDestroyMain, ================================================= ===================== Program entry point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { MSG msg; int rc = 0; HWND hwndmain; Initialize this instance. hwndmain = InitInstance (hinstance, lpcmdline, ncmdshow); if (hwndmain == 0) return 0x10; Application message loop while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); Instance cleanup return TermInstance (hinstance, msg.wparam); ---------------------------------------------------------------------- InitApp - Application initialization HWND InitInstance (HINSTANCE hinstance, LPWSTR lpcmdline, int ncmdshow) { WNDCLASS wc; HWND hwnd; #if defined(win32_platform_pspc) If Pocket PC, allow only one instance of the application hwnd = FindWindow (szappname, NULL); if (hwnd) { SetForegroundWindow ((HWND)(((DWORD)hWnd) 0x01)); return 0; #endif Save program instance handle in global variable. hinst = hinstance;
Register application main window class. wc.style = 0; Window style wc.lpfnwndproc = MainWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = szappname; Window class name if (RegisterClass (&wc) == 0) return 0; Create main window. hwnd = CreateWindowEx (WS_EX_NODRAG, szappname, TEXT ("PenTrac"), WS_VISIBLE WS_CAPTION WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL); Return fail code if window not created. if (!IsWindow (hwnd)) return 0; Standard show and update calls ShowWindow (hwnd, ncmdshow); UpdateWindow (hwnd); return hwnd; ---------------------------------------------------------------------- TermInstance - Program cleanup int TermInstance (HINSTANCE hinstance, int ndefrc) { return ndefrc; ================================================= ===================== Message handling procedures for MainWindow ---------------------------------------------------------------------- MainWndProc - Callback function for application window LRESULT CALLBACK MainWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { INT i;
Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(mainmessages); i++) { if (wmsg == MainMessages[i].Code) return (*MainMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoMouseMain - Process WM_LBUTTONDOWN and WM_MOUSEMOVE messages for window. LRESULT DoMouseMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { POINT pt[64]; POINT ptm; UINT i, upoints = 0; HDC hdc; ptm.x = LOWORD (lparam); ptm.y = HIWORD (lparam); hdc = GetDC (hwnd); If shift and mouse move, see if any lost points. if (wmsg == WM_MOUSEMOVE) { if (wparam & MK_SHIFT) GetMouseMovePoints (pt, 64, &upoints); for (i = 0; i < upoints; i++) { pt[i].x /= 4; Convert move pts to screen coords pt[i].y /= 4; Covert screen coordinates to window coordinates MapWindowPoints (HWND_DESKTOP, hwnd, &pt[i], 1); SetPixel (hdc, pt[i].x, pt[i].y, RGB (255, 0, 0)); SetPixel (hdc, pt[i].x+1, pt[i].y, RGB (255, 0, 0)); SetPixel (hdc, pt[i].x, pt[i].y+1, RGB (255, 0, 0)); SetPixel (hdc, pt[i].x+1, pt[i].y+1, RGB (255, 0, 0)); The original point is drawn last in case one of the points returned by GetMouseMovePoints overlaps it. SetPixel (hdc, ptm.x, ptm.y, RGB (0, 0, 0)); SetPixel (hdc, ptm.x+1, ptm.y, RGB (0, 0, 0));
SetPixel (hdc, ptm.x, ptm.y+1, RGB (0, 0, 0)); SetPixel (hdc, ptm.x+1, ptm.y+1, RGB (0, 0, 0)); ReleaseDC (hwnd, hdc); Kill time to make believe we are busy. Sleep(25); return 0; ---------------------------------------------------------------------- DoDestroyMain - Process WM_DESTROY message for window. LRESULT DoDestroyMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PostQuitMessage (0); return 0; 输入焦点和鼠标消息对于如何以及何时把手写笔产生的鼠标消息发送到不同的窗口, 这个过程中涉及的相关规则是需要关注一下的 正如我前面提到的, 当手写笔压触到一个窗口上时, 系统的输入焦点将发生变化 但是, 把手写笔从一个窗口拖动到另一个窗口并不会使新窗口获得输入焦点 因为是下压时才设置焦点, 而拖动手写笔滑过窗口是不会设置的 当手写笔拖动出窗口时, 该窗口就停止接收 WM_MOUSEMOVE 消息了, 但会继续保持输入焦点 因为手写笔的笔尖依然下压, 所以没有其它窗口会接收 WM_MOUSEMOVE 消息了 这一点与保持鼠标按键按下并拖动出一个窗口很类似 要想在手写笔即使被移动到窗口外时继续接收手写笔消息, 应用程序只要用接收鼠标消息的窗口句柄做参数, 调用 HWND SetCapture (HWND hwnd) 函数就可以了 该函数返回前一个捕捉鼠标消息的窗口的句炳, 如果之前没有捕捉过则返回 NULL 要停止接收手写笔输入产生的鼠标消息, 窗口可以调用 BOOL ReleaseCapture(void) 函数 任何时候都只有一个窗口可以捕捉手写笔的输入 要判断手写笔是否被捕捉了, 可以调用 HWND GetCapture(void) 函数, 它返回捕捉手写笔的窗口的句柄, 如果没有窗口捕捉手写笔的输入, 则返回 0( 不过要注意一个警告 : 返回 0 只是表示该线程没有捕捉鼠标, 并不表示其它线程或进程没有捕捉鼠标 ) 捕捉手写笔的窗口必须何调用该函数的的窗口在同样的线程环境里 这个限制意味着, 如果手写笔被另一个应用里的窗口捕捉了,GetCapture 依然返回 0 如果一个窗口捕捉了手写笔而另一个窗口调用了 GetCapture, 那么最初捕捉手写笔的窗口会收到一个 WM_CAPTURECHANGED 消息 消息的 lparam 参数中包含了获得手写笔捕捉的窗口的句柄 您不应该试图调用 GetCapture 来取会捕捉 通常, 因为手写笔是共享资源, 应用程序应该小心谨慎的捕捉手写笔一段时间, 并且应该能够优雅地处理捕捉丢失的情况 另外一个有趣的事情是 : 正因为窗口捕捉了鼠标, 所以它不能阻止在另一个窗口上点击来获得输入焦点 您可以使用其它方法来防止输入焦点的更换, 但几乎在所有情况下, 最好是让用户而不是程序来决定哪个顶层窗口应该拥有输入焦点 点击右键在 Windows 系统里, 当您在一个对象上单击鼠标右键, 通常地会调出上下文相关的 独立的菜
单, 显示针对该具体对象能做什么的功能项集合 在有鼠标的系统中,Windows 发送 WM_RBUTTONDOWN 和 WM_RBUTTONUP 消息, 指出右键点击了 但是当使用手写笔的时候, 不会有右键 不过 Windows CE 指导方针中允许您使用手写笔模拟右键点击 指导方针规定, 如果用户按下 Alt 键的同时用手写笔点击屏幕, 程序会当成是右键鼠标被点击, 并显示相关的上下文菜单 在 WM_LBUTTONDOWN 的 wparam 中没有 MK_ALT 标志, 所以判断 Alt 键是否被按的最好方法是用 VK_MENU 做参数调用 GetKeyState, 并测试返回值的相关位是否被设置了 在这种情况下,GetKeyState 是最合适的, 因为返回的是鼠标消息从消息队列里取出时的键的状态 在没有键盘的系统上, 采取压下并保持这一姿势来模拟鼠标右键点击 SHRecognizeGesture 函数可以用在 Pocket PC 和具有适当 Shell 组件的嵌入式 Windows CE 系统中, 用来检查压下并保持这一姿势 函数原型如下 : WINSHELLAPI DWORD SHRecongnizeGesture(SHRGINFO * shrg); 唯一的参数是一 SHRGINFO 结构的地址, 该结构定义如下 : typedef struct tagshrgi{ DWORD cbsize; HWND hwndclient; POINT ptdown; DWORD dwflags; SHRGINFO,*PSHRGINFO; cbsize 需要用结构的大小来填充 hwndclient 则需要设置为调用该函数的窗口的句柄 ptdown 结构需要用识别出姿势时的点来填充 dwflags 则包含许多标志 SHRG_RETURNCMD 标志表示如果用户做出正确的下压保持姿势, 则让函数返回 GN_CONTEXTMENU; 否则就返回 0 SHRG_NOTIFYPARENT 标志表示如果识别出正确姿势的话, 就给父窗口发送一个 WM_NOTIFY 消息 SHRG_LONDELAY 消息要求在识别出姿势之前, 用户需要保持点压一段时间 TicTac1 示例程序为了演示手写笔编程, 我写了一个 tic-tac-toe 小游戏 图 3-5 显示了 TicTac1 窗口 清单 3-3 显示了程序的源代码 这个程序并不提供人机游戏, 也不判断游戏结束, 只是简单绘制边界并记录 X 和 O 的位置 尽管如此, 该程序已经演示了手写笔的基本交互功能 图 3-5( 略 ):TicTac1 窗口清单 3-3:TicTac1 程序 TicTac1.h ================================================= ===================== Header file Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== Returns number of elements #define dim(x) (sizeof(x) / sizeof(x[0])) ----------------------------------------------------------------------
Generic defines and data types struct decodeuint { Structure associates UINT Code; messages with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); ; struct decodecmd { Structure associates UINT Code; menu IDs with a LRESULT (*Fxn)(HWND, WORD, HWND, WORD); function. ; ---------------------------------------------------------------------- Function prototypes HWND InitInstance (HINSTANCE, LPWSTR, int); int TermInstance (HINSTANCE, int); Window procedures LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM); Message handlers LRESULT DoSizeMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoLButtonDownMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoLButtonUpMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM); Game function prototypes void DrawXO (HDC hdc, HPEN hpen, RECT *prect, INT ncell, INT ntype); void DrawBoard (HDC hdc, RECT *prect); TicTac1.cpp ================================================= ===================== TicTac1 - Simple tic-tac-toe game Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include <commctrl.h> Command bar includes #include "tictac1.h" Program-specific stuff
---------------------------------------------------------------------- Global data const TCHAR szappname[] = TEXT ("TicTac1"); HINSTANCE hinst; Program instance handle State data for game RECT rectboard = {0, 0, 0, 0; RECT rectprompt; BYTE bboard[9]; BYTE bturn = 0; Used to place game board. Used to place prompt. Keeps track of X's and O's. Keeps track of the turn. Message dispatch table for MainWindowProc const struct decodeuint MainMessages[] = { WM_SIZE, DoSizeMain, WM_PAINT, DoPaintMain, WM_LBUTTONUP, DoLButtonUpMain, WM_DESTROY, DoDestroyMain, ; ================================================= ===================== Program entry point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { MSG msg; HWND hwndmain; Initialize this instance. hwndmain = InitInstance (hinstance, lpcmdline, ncmdshow); if (hwndmain == 0) return 0x10; Application message loop while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); Instance cleanup return TermInstance (hinstance, msg.wparam); ----------------------------------------------------------------------
InitInstance - Instance initialization HWND InitInstance (HINSTANCE hinstance, LPWSTR lpcmdline, int ncmdshow) { WNDCLASS wc; HWND hwnd; Save program instance handle in global variable. hinst = hinstance; #if defined(win32_platform_pspc) If Pocket PC, allow only one instance of the application. hwnd = FindWindow (szappname, NULL); if (hwnd) { SetForegroundWindow ((HWND)(((DWORD)hWnd) 0x01)); return 0; #endif Register application main window class. wc.style = 0; Window style wc.lpfnwndproc = MainWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = szappname; Window class name if (RegisterClass (&wc) == 0) return 0; Create main window. hwnd = CreateWindowEx (WS_EX_NODRAG, szappname, TEXT ("TicTac1"), WS_VISIBLE WS_CAPTION WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL); Return fail code if window not created. if (!IsWindow (hwnd)) return 0; Standard show and update calls ShowWindow (hwnd, ncmdshow); UpdateWindow (hwnd); return hwnd;
---------------------------------------------------------------------- TermInstance - Program cleanup int TermInstance (HINSTANCE hinstance, int ndefrc) { return ndefrc; ================================================= ===================== Message handling procedures for MainWindow ---------------------------------------------------------------------- MainWndProc - Callback function for application window LRESULT CALLBACK MainWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { INT i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(mainmessages); i++) { if (wmsg == MainMessages[i].Code) return (*MainMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc(hWnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoSizeMain - Process WM_SIZE message for window. LRESULT DoSizeMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { RECT rect; INT i; Adjust the size of the client rect to take into account the command bar height. GetClientRect (hwnd, &rect); Initialize the board rectangle if not yet initialized. if (rectboard.right == 0) { Initialize the board. for (i = 0; i < dim(bboard); i++) bboard[i] = 0;
Define the playing board rect. rectboard = rect; rectprompt = rect; Layout depends on portrait or landscape screen. if (rect.right - rect.left > rect.bottom - rect.top) { rectboard.left += 20; rectboard.top += 10; rectboard.bottom -= 10; rectboard.right = rectboard.bottom - rectboard.top + 10; rectprompt.left = rectboard.right + 10; else { rectboard.left += 20; rectboard.right -= 20; rectboard.top += 10; rectboard.bottom = rectboard.right - rectboard.left + 10; rectprompt.top = rectboard.bottom + 10; return 0; ---------------------------------------------------------------------- DoPaintMain - Process WM_PAINT message for window. LRESULT DoPaintMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PAINTSTRUCT ps; RECT rect; HFONT hfont, holdfont; HDC hdc; GetClientRect (hwnd, &rect); hdc = BeginPaint (hwnd, &ps); Draw the board. DrawBoard (hdc, &rectboard); Write the prompt to the screen. hfont = (HFONT)GetStockObject (SYSTEM_FONT); holdfont = (HFONT)SelectObject (hdc, hfont);
if (bturn == 0) DrawText (hdc, TEXT (" X's turn"), -1, &rectprompt, DT_CENTER DT_VCENTER DT_SINGLELINE); else DrawText (hdc, TEXT (" O's turn"), -1, &rectprompt, DT_CENTER DT_VCENTER DT_SINGLELINE); SelectObject (hdc, holdfont); EndPaint (hwnd, &ps); return 0; ---------------------------------------------------------------------- DoLButtonUpMain - Process WM_LBUTTONUP message for window. LRESULT DoLButtonUpMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { POINT pt; INT cx, cy, ncell = 0; pt.x = LOWORD (lparam); pt.y = HIWORD (lparam); See if pen on board. If so, determine which cell. if (PtInRect (&rectboard, pt)){ Normalize point to upper left corner of board. pt.x -= rectboard.left; pt.y -= rectboard.top; Compute size of each cell. cx = (rectboard.right - rectboard.left)/3; cy = (rectboard.bottom - rectboard.top)/3; Find column. ncell = (pt.x / cx); Find row. ncell += (pt.y / cy) * 3; If cell empty, fill it with mark. if (bboard[ncell] == 0) { if (bturn) { bboard[ncell] = 2; bturn = 0; else { bboard[ncell] = 1; bturn = 1;
InvalidateRect (hwnd, NULL, FALSE); else { Inform the user of the filled cell. MessageBeep (0); return 0; return 0; ---------------------------------------------------------------------- DoDestroyMain - Process WM_DESTROY message for window. LRESULT DoDestroyMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PostQuitMessage (0); return 0; ================================================= ===================== Game-specific routines ---------------------------------------------------------------------- DrawXO - Draw a single X or O in a square. void DrawXO (HDC hdc, HPEN hpen, RECT *prect, INT ncell, INT ntype) { POINT pt[2]; INT cx, cy; RECT rect; cx = (prect->right - prect->left)/3; cy = (prect->bottom - prect->top)/3; Compute the dimensions of the target cell. rect.left = (cx * (ncell % 3) + prect->left) + 10; rect.right = rect.right = rect.left + cx - 20; rect.top = cy * (ncell / 3) + prect->top + 10; rect.bottom = rect.top + cy - 20; Draw an X? if (ntype == 1) { pt[0].x = rect.left; pt[0].y = rect.top; pt[1].x = rect.right; pt[1].y = rect.bottom;
Polyline (hdc, pt, 2); pt[0].x = rect.right; pt[1].x = rect.left; Polyline (hdc, pt, 2); How about an O? else if (ntype == 2) { Ellipse (hdc, rect.left, rect.top, rect.right, rect.bottom); return; ---------------------------------------------------------------------- DrawBoard - Draw the tic-tac-toe board. VK_MENU void DrawBoard (HDC hdc, RECT *prect) { HPEN hpen, holdpen; POINT pt[2]; LOGPEN lp; INT i, cx, cy; Create a nice thick pen. lp.lopnstyle = PS_SOLID; lp.lopnwidth.x = 5; lp.lopnwidth.y = 5; lp.lopncolor = RGB (0, 0, 0); hpen = CreatePenIndirect (&lp); holdpen = (HPEN)SelectObject (hdc, hpen); cx = (prect->right - prect->left)/3; cy = (prect->bottom - prect->top)/3; Draw lines down. pt[0].x = cx + prect->left; pt[1].x = cx + prect->left; pt[0].y = prect->top; pt[1].y = prect->bottom; Polyline (hdc, pt, 2); pt[0].x += cx; pt[1].x += cx; Polyline (hdc, pt, 2); Draw lines across. pt[0].x = prect->left;
pt[1].x = prect->right; pt[0].y = cy + prect->top; pt[1].y = cy + prect->top; Polyline (hdc, pt, 2); pt[0].y += cy; pt[1].y += cy; Polyline (hdc, pt, 2); Fill in X's and O's. for (i = 0; i < dim (bboard); i++) DrawXO (hdc, hpen, &rectboard, i, bboard[i]); SelectObject (hdc, holdpen); DeleteObject (hpen); return; TicTac 的行为主要集中在 3 个方面 :DrawBoard,DrawXO 和 DoLbuttonUpMain 头两个执行绘制游戏棋盘的工作 判断在棋盘上点击位置的是 DoLButtonUpMain 正如名字所暗示的, 该函数是用来响应 WM_LBUTTONUP 消息的 首先调用 PtInRect 来判断点击是否在游戏棋盘上 PtInRect 原型如下 :BOOL PtInRect(const RECT *lprc, POINT pt); 程序知道点击位置, 因为是包含在消息的 lparam 里的 程序启动时在 DoSizeMain 中计算了棋盘边框矩形 一旦发现在棋盘上进行了点击, 程序会用横纵格数分别除以点击点在棋盘上的坐标, 来确定棋盘里相关单元的位置 前面提到过是在 DoSizeMain 中计算棋盘边框矩形的, 调用该例程是为了响应 WM_SIZE 消息 可能会奇怪 Windows CE 为什么会支持对其它版本 Windows 来说是很普通的 WM_SIZE 消息, 实际上之所以支持这个消息, 是因为窗口尺寸变化频繁 : 首先是在窗口创建的时候, 之后是每次最小化和恢复的时候 您可能会想, 另一个可能确定窗口尺寸的地方是在 WM_CREATE 消息里 lparam 参数指向一个 CREATESTRUCT 结构, 包含了窗口初始大小和位置 用这些数据的问题在于此处的大小是整个窗口的大小, 而不是我们需要的客户区的大小 在 Windows CE 下, 大多数窗口没有标题栏和边框, 但也有一些两个都有并且很多都有滚动条, 所以用这些数据会带来麻烦 对 TicTac1 程序, 我们有一个简单有效的使用手写笔的程序, 虽然还不完整 为了重开一局游戏, 需要退出重起 TicTac1 也不能悔棋或者让 0 先行 我们需要一个方法来发送这些命令给程序 当然, 用按键是可以完成这个目标的 另一种解决方案是在屏幕上创建热点 (hot spots) 来提供必要的输入 很明显本例需要一些额外的功能来使其完整 我已经尽可能的讨论了 Windows, 但没有更完整的讨论操作系统的基础组件 -- 窗口 现在是时候更进一部的学习窗口 子窗口和控件的时候了 WinCE 程序设计 (3rd 版 )-- 第 4 章窗口 控件和菜单 -- 概述概述理解窗口是如何工作的以及窗口之间的关系对理解微软 Windows 操作系统 ( 不论是 XP 还是 CE) 的用户界面是很关键的 您所看到的 Windows 显示的每个东西都是一个窗口 桌面是窗
口, 任务条是窗口, 甚至任务条上的启动按钮也是窗口 根据这种或那种关系模型, 窗口之间彼此是相互关联的 它们可以是父子关系 兄弟关系或者是拥有与被拥有的关系 Windows 支持许多预定义的窗口类, 也称为控件 从简单的按钮到复杂的多行文本编辑器, 控件通过提供一系列预定义的用户界面元素, 简化了程序员的工作 和 Windows 的其它版本相同,Windows CE 支持同样的标准内置控件集合 不要将这些内置控件同公共控件库提供的复杂控件相混淆 我将在下一章里讨论那些控件 WinCE 程序设计 (3rd 版 )--4.1 子窗口 子窗口每个窗口是通过父子关系体系连接到一起的 应用程序创建一个没有父亲的主窗口, 称为顶层窗 口 该窗口可能包含 ( 也可能没有包含 ) 有窗口, 称为子窗口 子窗口会被父窗口裁减 也就是 说, 子窗口超出父窗口边沿的部分是不可见的 当父窗口被销毁时, 子窗口自动被销毁 当父窗 口移动的时候, 子窗口随父窗口一起移动 从程序角度看子窗口同顶层窗口是一样的 您可以使用 CreateWindow 或 CreateWindowsEx 函数来创建它们, 和顶层窗口一样, 每个子窗口都有一个窗口过程来处理消息, 并且每个都可以 包含自己的子窗口 要创建子窗口, 需要将 CreateWindow 或 CreateWindowEx 的参数 dwstyle 设置为 WS_CHILD 另外, 对在顶层 Windows CE 窗口中没有使用的 hmenu 参数, 可以传一个 ID 用以引用该子窗口 在 Windows CE 下, 顶层窗口和子窗口之间还有另外一个主要的不同点 Windows CE shell 只给具有 WS_OVERLAPPED 和 WS_VISIBLE 风格的顶层窗口发送 WM_HIBERNATE 消息 ( 这种情况下的窗口可视与用户是否可以看见是无关的 如果有其它窗口在 Z 轴方向盖住了该 窗口, 该窗口对系统是可见的, 但用户是看不到的 ) 这意味着不能发送 WM_HIBERNATE 消 息给子窗口和大部分对话框 顶层窗口要么在需要的时候手工发送 WM_HIBERNATE 消息给它 们的子窗口, 要么自己执行所有必要的工作来减少应用程序的内存使用量 在使用标准 "Explorer shell" 的 Windows CE 系统里, 支持在任务栏上显示应用程序按钮, 而用于判断 WM_HIBERNATE 消息目标窗口的规则, 一样适用于判断哪些窗口获得了任务栏上的按钮 窗口除了有父子关系外, 还有一种拥有和被拥有的关系 被拥有的窗口并不被其拥有者裁剪 但 是, 被拥有的窗口总是出现在拥有者的 Z 坐标的上方 如果拥有者最小化了, 它拥有的所有窗 口都被隐藏 同样地, 如果该窗口被销毁, 其拥有地所有窗口都被销毁 WinCE 程序设计 (3rd 版 )--4.2 窗口管理函数窗口管理函数了解了 Windows 以窗口为中心的本质, 当您可以从众多用于窗口的函数中进行选择时也就不足为怪了 这些函数允许窗口查询自己的环境, 判断自己在窗口家族里的位置 要找到自己的父窗口, 窗口可以调用 GetParent(HWND hwnd), 该函数接收一个窗口句柄, 返回调用该函数的窗口的父窗口句柄 如果这个窗口没有父窗口, 则函数返回 NULL 枚举窗口 GetWindow 函数是一个多用途函数, 它允许窗口来查询其子窗口 拥有者和兄弟窗口 函数原型如下 :HWND GetWindow(HWND hwnd, UINT ucmd); 第一个参数是查询窗口的句柄, 第二个参数是一个常量, 指出要查询的关系 常量 GW_CHILD,
表示返回窗口第一个子窗口的句柄 GetWindow 是按 Z 坐标顺序来返回窗口的, 所以在这种情况下, 第一个子窗口就是 Z 坐标最高的子窗口 如果窗口没有子窗口, 该函数则返回 NULL 常量 GW_HWNDFIRST 和 GW_HWNDLAST, 按 Z 坐标返回第一个和最后一个窗口 如果传入的窗口句柄是顶层窗口 ( 顶层窗口是指没有父窗口或父窗口是桌面窗口的窗口 ), 这些常量按 Z 坐标返回第一个和最后一个最顶层 (topmost) 窗口 如果传入的窗口句柄是子窗口, GetWindow 返回第一个和最后一个兄弟窗口 常量 GW_HWNDNEXT 和 GW_HWNDPREV 按 Z 坐标返回下一个更低和更高的窗口 这些常量允许窗口获得下一个窗口, 并用获得的窗口的句柄调用 GetWindow 来获得下一个窗口, 依次类推, 最终递归遍历所有的兄弟窗口 常量 GW_OWNER 则返回窗口拥有者的句柄 另外一种遍历窗口的方法是 EnumWindows 函数, 其原型如下 : BOOL EnumWindows(WNDENUMPROC lpenumfunc, LPARAM lparam); 参数 lpenumfunc 指向一个回调函数,EnumWindows 为桌面上的每个顶层窗口调用一次该回调函数, 并依次传入每个窗口的句柄 lparam 的值是应用程序定义的值, 并被传递给枚举函数 该函数比通过 GetWindow 循环来寻找顶层窗口的遍历方式要好, 因为该函数始终返回有效的窗口句柄, 而一个 GetWindow 遍历循环里, 获得的窗口句柄可能在下次调用 GetWindow 之前其对应的窗口就已经被销毁了 但是, 因为 EnumWindows 只对顶层窗口有效, 所以当遍历一系列子窗口时 GetWindow 还是有用武之地的 查找窗口要获得一个指定窗口的句柄, 可以用函数 FindWindow, 其原型如下 :HWND FindWindow(LPCTSTR lpclassname, LPCTSTR lpwindowname) 该函数可以通过窗口的类名或者窗口的标题找到窗口 当应用程序刚启动的时候, 可以用它来判断是否有程序的另一个副本正在运行, 而应用程序所要做的只是用程序主窗口的窗口类名来调用 FindWindow 就可以了 因为在运行时应用程序几乎总是有一个主窗口的, 所以当 FindWindow 返回 NULL, 就表示函数用指定的窗口类没找到另外一个窗口, 由此几乎可以肯定没有另一个副本在运行 要查找桌面窗口 (desktop window) 的句柄, 可以使用下面的函数 :HWND GetDesktopWindow(void); 编辑窗口结构的内容函数对 GetWindowLong 和 SetWindowLong 允许程序编辑窗口结构中的数据 这两个函数原型如下 : LONG GetWindowLong(HWND hwnd, int nindex); LONG SetWindowLong(HWND hwnd, int nindex, LONG dwnewlong); 一起回忆一下传给 RegisterClass 函数的 WNDCLASS 结构吧, 它有一个 cbwndextra 域, 用来控制分配给该结构额外字节数 如果在窗口类注册时您为窗口结构分配了额外的空间, 那么您可以用 GetWindowLong 和 SetWindowLong 函数来访问这些字节 在 Windows CE 下, 该项必须按 4 字节为单位来分配和引用 所以如果窗口类在注册时用 12 填充了 cbwndextra 域, 那么程序可以使用窗口句柄, 通过 GetWindowLong 或 SetWindowLong 函数来访问, 并且可以将 nindex 参数设置为 0 4 和 8 GetWindowLong 和 SetWindowLong 支持一个预定义的索引值集合, 用来使应用程序访问窗口的一些基本参数 下面是 Windows CE 支持的索引值列表 GWL_STYLE 窗口风格标志
GWL_EXSTYLE 窗口扩展风格标志 GWL_WNDPROC 指向窗口过程的指针 GWL_ID 窗口标识 GWL_USERDATA 应用程序使用的 32 位值对话框窗口还支持下面附加的值 : DWL_DLGPROC 指向对话框窗口过程的指针 DWL_MSGRESULT 当对话框函数返回时的返回值 DWL_USER 应用程序使用的 32 位值 Windows CE 不支持 GWL_HINSTANCE 和 GWL_HWNDPARENT 值, 这些值在 Windows 2000 和 Windows XP 下是支持的 改变风格标志编辑窗口结构在很多方面是很有用的 在窗口被创建后, 通过改变其窗口风格位, 可以改变窗口的默认行为和外观 例如, 通过切换 WS_CAPTION 风格位, 可以显示或隐藏窗口的标题栏 任何修改窗口外观的风格标志被改变后, 习惯上都要通过调用 SetWindowPos 来强制系统重新绘制窗口的非客户区 SetWindowPos 是 Windows 中始终使用的函数之一 它允许应用程序移动窗口 改变窗口大小 变换窗口 Z 坐标, 并且在前面提到的情况下, 它会重新绘制窗口的非客户区 它的函数原型如下 : BOOL SetWindowPos(HWND hwnd, HWND hwndinsertafter, int X, int Y, int cx, int cy, UINT uflags); 第一个参数是即将改变的窗口的句柄 可选参数 hwndinsertafter 允许设置窗口的 Z 坐标 该参数要么是窗口句柄, 要么是下面 4 个标志之一, 用来放置窗口到 Z 坐标顶部或底部 标志位如下所示 : HWND_BOTTOM 窗口在桌面上所有窗口之下 HWND_TOP 窗口在所有窗口的顶部 HWND_TOPMOST 窗口始终放置在其它窗口顶部, 即使该窗口处于非活动窗口 HWND_NOTTOPMOST 窗口位于其它非置顶窗口 (nontopmost windows) 之上, 但没有标记为置顶窗口 (topmost window), 这样当有另一个窗口成为活动窗口时, 该窗口可以被覆盖 可选的 X Y cx 和 cy 参数指定了窗口的位置和大小 标志位参数 uflags 包含一个或多个标志位, 用来描述要完成的任务 这些标志如下所示 : SWP_NOMOVE 不移动窗口 SWP_NOSIZE 不改变窗口大小 SWP_NOZORDER 不设置窗口 Z 坐标 SWP_NOACTIVATE 如果设置了 Z 坐标, 则不激活窗口 SWP_DRAWFRAME 重绘非客户区 SWP_FRAMECHANGED 重新计算非客户区, 并重新绘制 另有两个标志,SWP_SHOWWINDOW 和 SWP_HIDEWINDOW, 可以显示和隐藏窗口, 但调用 ShowWindow 函数来显示和隐藏窗口会更容易一些 在风格位改变后, 要用 SetWindowPos 来强制重绘框架的话, 可以如下操作 : SetWindowPos (hwnd, 0, 0, 0, 0, SWP_NOMOVE SWP_NOSIZE SWP_NOZORDER SWP_FRAMECHANGED);
窗口子类化 SetWindowLong 的另一个用途是子类化一个窗口 本质上讲, 窗口子类化是使应用程序从先前存在的窗口类派生出新窗口类的一个实例 子类化的典型应用是修改窗口控件的行为, 例如一个 Edit 控件 子类化的过程实际上是很简单的 先创建一个窗口过程来为被子类化的窗口提供新功能, 再用基础窗口类创建一个窗口 然后为该窗口调用 GetWindowLong 来获得并保存一个指向其初始窗口过程的指针, 之后调用 SetWindowLong 函数, 将窗口实例的窗口过程设置成新的窗口过程 这样, 新的窗口过程就开始接收发给该窗口的消息了 任何没有被新窗口过程响应的消息都通过调用 CallWindowProc 传递给到旧的窗口过程 下面的代码展示了窗口创建及被子类化的过程 子类化窗口过程截获了 WM_LBUTTONDOWN 消息, 并且在窗口收到该消息时使扬声器发出声音 Prototype of subclass procedure LRESULT CALLBACK SCWndProc(HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam); Variable that holds the pointer to the original WndProc WNDPROC lpfnoldproc = 0; Routine that subclasses the requested window. BOOL SubClassThisWnd (HWND hwndsc) { if (lpfnoldproc == 0) { Get and save the pointer to the original window procedure lpfnoldproc = (WNDPROC)GetWindowLong (hwndsc, GWL_WNDPROC); Point to new window procedure return SetWindowLong (hwndsc, GWL_WNDPROC, (DWORD)SCWndProc); return FALSE; Subclass procedure LRESULT CALLBACK SCWndProc(HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { switch (wmsg) { case WM_LBUTTONDOWN: MessageBeep(0); break; return CallWindowProc (lpfnoldproc, hwnd, wmsg, wparam, lparam);
要去除窗口的子类化, 只要调用 SetWindowLong, 把 WndProc 指针设置回最初的窗口过程 即可 WinCE 程序设计 (3rd 版 )--4.3 Windows 控件如果没有 Windows 控件库的话, 编写 Windows 程序将会是一件缓慢而又艰苦的事情 并且, 每个程序将会有自己的外观和反应 这会迫使用户对每种新的应用程序都要学习一套新的操作方式 幸运的是, 通过操作系统提供的一系列控件, 避免了上述情况的发生 简单的说, 控件只不过是预先定义好的窗口类 每个类有一个 Windows 提供的特定的窗口过程, 给这些控件提供预定义的用户和编程接口 因为控件只是又一个窗口, 所以可用 CreateWindows 或 CreateWindowEx 来创建窗口 控件是通过事件来通知父窗口的, 而事件中包含 WM_COMMAND 消息, 并且控件 ID 和句柄都编码在消息的参数中 和所有的消息一样,WM_COMMAND 含有 wparam 和 lparam 这两个通用参数 对一个 WM_COMMAND 消息来说,wParam 的高字位含有通知码, 用来说明发送该消息的原因 wparam 的低字位含有发送该消息的控件的 ID, 通常在创建控件的时候定义该 ID, 为了更好的使用, 应该保证 ID 在控件的所有兄弟窗口中是唯一的 lparam 则包含了控件窗口的句柄 通常, 通过控件 ID 来追踪 WM_COMMAND 消息来源比通过控件的窗口句柄来追踪要更容易一些, 不过这两个信息都可以从该消息中获得 下面是典型的 WM_COMMAND 消息处理程序中的头几行代码 : case WM_COMMAND: WORD iditem, wnotifycode; HWND hwndctl; Parse the parameters. iditem = (WORD) LOWORD (wparam); wnotifycode = (WORD) HIWORD(wParam); hwndctl = (HWND) lparam; 从这里可以看出,WM_COMMAND 消息处理程序通常是用控件 ID 和通知码来判断 WM_COMMAND 消息发送的原因 通过给控件发送预定义的消息, 可以配置和操纵控件 除了这些事情, 应用程序可以设置按钮的状态, 给列表框增加或删除项, 设置编辑框中选择的文本, 所有这些都是通过给控件发送消息来完成的 控件通常是通过 ID 来标识的, 但许多 Window 函数都要求用控件的句柄 这时可以用 GetDlgItem 函数来进行简单的转换 函数原型如下 : HWND GetDlgItem(HWND hdlg, int niddlgitem); 两个参数分别是控件的父窗口句柄和控件的 ID 虽然从名字上看这个函数只能用在对话框上, 但实际上它能用在任何窗口的控件上 关于对话框我将在第六章进行讨论 另外一个很方便的函数是 SendDlgItemMessage, 用于给控件发送消息 该函数发送消息给指定 ID 的子窗口 函数原型如下 : LONG SendDlgItemMessage (HWND hparent, int nidchild, UINT Msg, WPARAM wparam, LPARAM lparam); 这些参数和 SendMessage 中的很类似 实际上, 下面的这段代码从功能上讲和
SendDlgItemMessage 是一样的 LONG SendMessage (GetDlgItem (hparent, nidchild), Msg, wparam, lparam); 唯一的不同在于方便性,SendMessage 没有嵌入 GetDlgItem 而已 下面是六个预定义的窗口控件类 它们是 : Button 各种按钮 Edit 一种用于输入和显示文本的窗口 List 一种包含字符串列表的窗口 Combo 编辑框和列表框的组合 Static 显示用户不能编辑的文本或图片的窗口 Scroll bar 未和具体窗口进行绑定滚动条每个控件都有很多函数, 所以本章难以涉及全部 但我会快速浏览一下这些控件, 对重要的函数也会提及的 后面还会为您展示一个示例程序 --CtlView, 用来演示这些控件及它们同父窗口之间的交互 按钮控件按钮控件允许有几种输入方式, 并且有许多风格, 包括下压按钮 复选框和单选框 每种风格都是为特定用途设计的, 例如, 下压按钮用来接收即时输入, 复选框用于开 / 关 (on/off) 输入, 单选框用于从多个选择中挑选一个 下压按钮下压按钮通常用于激发某种行为 当用户用手写笔按一个下压按钮, 按钮会发送 WM_COMMAND 消息, 其中 wparam 参数的高字位包含 BN_CLICKED( 用于按钮被单击的通知 ) 通知码 复选框复选框包括一个正方形的框和一个标签, 用来让用户指定选择 复选框会保持选择或未选择状态, 直到用户点击它或者程序强制按钮改变状态 除了标准 BS_CHECKBOX 风格外, 复选框还有一个三态风格 BS_3STATE, 允许按钮失效并显示为灰色 另两个风格 BS_AUTOCHECKBOX 和 BS_AUTO3STATE, 会自动更新控件的状态和外观来反映出选择或未选择以及三态复选框下的失效风格 和下压按钮一样, 当单击复选框时, 它会发送 BN_CLICKED 通知 除非复选框具有前面说的自动风格, 否则需要应用程序负责手工改变按钮的状态 通过给按钮发送 BM_SETCHECK 消息来设置按钮的状态, 其中, 把参数 wparam 设置为 0 来取消按钮选择, 设置为 1 则是选择按钮 对于三态复选框, 可以将 BM_SETCHECK 消息的 wparam 参数设置为 2 来表示按钮失效状态 通过 BM_GETCHECK 消息, 应用程序可以判断当前按钮状态 单选框单选框允许用户从多个选项里进行选择 单选框将多个按钮集分组, 每组集合里同时只有一个能被选中 如果使用的是标准风格 BS_RADIOBUTTON, 则需要由应用程序负责选中或取消选中单选框, 来保证同时只有一个被选中 当然, 和复选框一样, 单选框有一个 BS_AUTORADIONBUTTON 风格, 用来自动维护按钮组的状态, 来保证只有一个被选择 分组框您可能会奇怪, 分组框也是一种按钮 分组框看起来象一个带有文本标签的中空的框, 包围着被分成一组的控件集 分组框只是用于分组, 除了其上的标题文字外, 没有其它编程的接口, 该标
题文字是在创建分组框的时候作为其窗口标题文字而指定的 应该在创建分组框内的控件后创建分组框 这可以保证分组框在 Z 坐标上低于其内的控件 在 Windows CE 设备上使用分组框应该小心 问题不在于分组框自身, 而在于 Windows CE 较小的屏幕 分组框占用了颇有价值的屏幕, 而这本应该可以被功能控件更好的使用的 尤其是在具有非常小的屏幕的 Pocket PC 上会出现这种情况 在许多情况下, 在控件集之间绘制一条线就可以明显将控件分组了, 这和使用分组框的效果一样 定制按钮外观通过使用许多附加风格, 您可以进一步定制目前提到的按钮的外观 风格 BS_RIGHT,BS_LEFT,BS_BOTTOM 和 BS_TOP 允许您指定按钮文本的位置, 而不是使用默认在按钮上居中的风格 BS_MULTILINE 风格允许您在按钮上指定多行文本 文字会自动调整来适合按钮的 按钮文字中的换行符 (\n) 可以用来具体指定从哪里换行 Windows CE 并不支持 BS_ICON 和 BS_BITMAP 按钮风格 自绘制按钮通过指定 BS_OWNERDRAW 风格, 您可以完全控制按钮的外观 当按钮被指定为自绘制风格, 则由拥有按钮的窗口负责绘制按钮可能出现的所有状态 当窗口包含有自绘制按钮时, 会收到一个 WM_DRAWITME 消息, 用来通知窗口有一个按钮需要绘制 对于该消息,wParam 参数包含有按钮 ID,lParam 则指向一个 DRAWITEMSTRUCT 结构, 该结构定义如下 : typedef struct tagdrawitemstruct { UINT CtlType; UINT CtlID; UINT itemid; UINT itemaction; UINT itemstate; HWND hwnditem; HDC hdc; RECT rcitem; DWORD itemdata; DRAWITEMSTRUCT; CtlType 设置为 ODT_BUTTON( 自绘制按钮 ),CtlID 则和 wparam 一样, 包含有按钮 ID itemaction 包含标志位, 指出需要绘制什么和为什么绘制 这些域中最重要的是 itemstate, 它包含了按钮选择 失效等状态 hdc 包含了按钮窗口的设备描述表句柄,rcItem 则包含了按钮的尺寸 对自绘制按钮来说,itemDate 应该设置为 NULL 正如您所期望的,WM_DRAWITEM 处理程序中包含有许多 GDI 函数, 用来绘制线条 矩形以及绘制按钮所需要的一切 绘制按钮的一个重要方面是要和系统中其它窗口的标准颜色匹配 因为这些颜色是可变的, 所以不能硬编码到程序里 您可以通过 GetSysColor 函数来查询出合适的颜色 函数原型如下 :DWORD GetSysColor(int nindex); 该函数返回的是为系统中窗口和控件的不同外观而定义的颜色的 RGB 值 在众多作为参数的预定义索引值中,COLOR_BTNFACE 返回的是按钮的表面颜色,COLOR_BTNSHADOW 返回的是用于创建三维按钮的暗色 编辑框编辑框是一种允许用户输入和编辑文本的窗口 正如您所猜想的, 编辑框是 Windows 控件库中
最方便的控件之一 编辑框具有完整的编辑功能, 包括与系统剪切板交互来剪切 复制和粘贴等, 而这些都不需要应用程序协助 编辑框可以显示单行文本, 或者当指定 EX_MULTILINE 风格时, 显示多行文本 桌面版 Windows 中提供的记事本就是一个包含多行编辑框的顶层窗口 编辑框有一些其它特性需要提一下 具有 ES_PASSWORD 风格的编辑框默认将输入的每个字符显示为星号 (*) 控件保存了真正的字符 ES_READONLY 风格则保护控件里的文本只能读和复制, 但不能被修改 ES_LOWERCASE 和 ES_UPPERCASE 风格则把输入的字符强制转换为对应的大小写 通过 WM_SETTEXT 消息可以给编辑框添加文本, 通过 WM_GETTEXT 消息可以检索文本 通过 EM_SETSEL 消息可以控制文本选择 该消息指定选择区域的起始字符和终止字符 其它消息允许查询和设置光标 ( 指出在编辑域里的当前入口点 ) 的位置 多行编辑框包含许多其它消息, 比如控制滚动 按行列位置访问字符等 列表框控件列表框控件显示一个文本项列表, 用户可以从中选择一个或多个项 列表框可以存储文本, 还可以对列表项排序, 管理列表项的显示, 包括滚动显示等 通过设置列表框, 可以允许单选 多选或者禁止选择列表项 通过给列表框发送 LB_ADDSTRING 或 LB_INSERTSTRING 消息, 传递指向字符串的指针到 lparam 参数里, 就可以增加列表项 LB_ADDSTRING 消息会把新添加的字符串放到列表项的尾部, 而 LB_INSERTSTRING 可以把字符串放到列表项的任何位置 使用 LB_FIND 消息可以在列表框中搜索特定的项 使用 LB_GETCURSEL 可以查询单选列表框的选择状态 对于多选框, 使用 LB_GETSELCOUNT 和 LB_GETSELITEMS 来检索当前选择的项 使用 LB_SETCURSEL 和 LB_SETSEL 消息, 可以用编程的方式来选择列表框里的项 除了不支持自绘制列表框以及 LB_DIR 消息族,Windows CE 支持大部分其它版本 Windows 所支持的列表框功能 Windows CE 支持一种新风格 LBS_EX_CONSTRINGDATA 具有这种风格的列表框不保存传给它的字符串, 而是保存指向字符串的指针, 并且由应用程序负责维护字符串 对于可能从资源文件中装载大型字符串数组的情况, 使用这种处理方式可以节省内存, 因为列表框不用维护一个字符串列表的单独副本 组合框组合框 ( 如名字所暗示的 ) 是控件的组合, 也就是一个单行编辑框控件和列表框的组合 在从多个选项列表中选择一项或者为预定义的建议输入值提供编辑功能时, 组合框是很节省空间的 在 Windows CE 下, 组合框具有两种风格 : 下拉方式和下拉列表方式 ( 简化方式的组合框并不支持 ) 下拉风格的组合框包括一个编辑区域和一个位于右端的按钮 点击按钮会显示一个列表框, 包含了更多选项 在选项上选择一个, 会用选择的内容填写组合框的编辑域 下拉列表风格的组合框使用静态文本控件替代了编辑框 这允许用户从列表中选择一项, 并防止用户输入不在列表中的项 因为组合框组合了编辑控件和列表框控件, 所以组合框的消息列表就像是这两个基本控件的消息组合 CB_ADDSTRING,CB_INSERTSTRING 和 CB_FINDSTRING 的效果和列表框里中的同类消息类似 同样地, 对下拉风格或下拉列表风格的组合框使用 CB_SETEDITSELECT 和 CB_GETEDITSELECT 消息, 可以设置和查询编辑框里被选择的字符 而要控制下拉风格或下
拉列表风格的组合框的下拉状态, 可以使用 CB_SHOWDROPDOWN 和 CB_GETDROPPEDSTATE 消息 和列表框的情况一样,Windows CE 不支持自绘制组合框 但是组合框支持扩展风格 --CBS_EX_CONSTSTRINGDATA, 具有这种风格的组合框不保存列表项字符串, 而是保存指向字符串的指针 和具有 LBS_EX_CONSTSTRINGDATA 风格的列表框一样, 当应用程序有存储在 ROM 里的大型字符串数组时, 这种处理方式可以节省内存, 因为组合框不用维护字符串列表的单独副本 静态控件静态控件是显示文本 图标或者位图的窗口, 不具有用户交互性 可以使用静态文本控件来标记窗口中的其它控件 静态文本控件显示的效果是由文本和控件风格来决定的 在 Windows CE 下, 静态控件支持以下几种风格 : SS_LEFT 文本左对齐显示 如果需要, 文本会折行来适应控件大小 SS_CENTER 将文本在控件中间显示 如果需要, 文本会折行来适应控件大小 SS_RIGHT 文本右对齐显示 如果需要, 文本会折行来适应控件大小 SS_LEFTNOWORDWRAP 文本左对齐显示 文本不会折成多行显示, 任何超出控件右边界的文本都会被裁剪掉 SS_BITMAP 显示一个位图 控件的窗口文字指定了包含位图的资源的名字 SS_ICON 显示一个位图 控件的窗口文字指定了包含图标的资源的名字 具有 SS_NOTIFY 风格的静态控件在被点击 有效或失效的时候, 会发送 WM_COMMAND 消息, 但是 Windows CE 版的静态控件在双击的时候不会发送通知消息 SS_CENTERIMAGE 风格在和 SS_BITMAP 或 SS_ICON 一起使用的时候, 会使图片在控件上居中显示 SS_NOPREFIX 风格可以和文本风格一起组合使用 能够避免将 & 符号解释为转义符号, 防止把下个字符解释成加速字符 Windows CE 不支持具有 SS_WHITEFRAME 或 SS_BLACKRECT 风格的显示成填充矩形或中空矩形的静态控件 ; 也不支持自绘制静态控件 滚动条控件滚动条控件是一个与其它控件有些不同的家伙 通常滚动条是绑定在窗口的侧边, 用来控制窗口里数据的显示的 实际上, 诸如编辑框和列表框等窗口控件都内部使用了滚动条控件 正是因为这种和父窗口的紧密关系, 使得滚动条的接口与其它控件的有所不同 滚动条使用 WM_VSCROLL 和 WM_HSCROLL 消息而不是 WM_COMMAND 消息来报告行为 垂直滚动条会发送 WM_VSCROLL 消息, 水平滚动条会发送 WM_HSCROLL 消息 另外, 不使用 SB_SETPOSITION 消息来设置滚动条的位置, 而是有专用的函数来完成 下面来看一下这个独特的接口 滚动条消息一旦用户点击垂直滚动条来改变其位置的时候,WM_VSCROLL 消息会发送到垂直滚动条的拥有者上 WM_HSCROLL 则是当用户点击水平滚动条的时候发送到其拥有者上的 对这两个消息来说,wParam 和 lparam 参数是一样的 wparam 的低字位包含的代码指出为什么会发送该消息 图 4-1 显示了水平和垂直滚动条以及如何在滚动条不同位置点击来产生不同的消息 wparam 的高字位包含滑块的位置, 但仅仅在您处理 SB_THUMBPOSITION 和 SB_THUMBTRACK 代码 ( 后面将简单介绍它们 ) 时, 这个值才是有效的 如果发送消息的滚动条是独立的没有绑定到窗口的控件, 那么 lparam 参数则包含有滚动条的窗口句柄
图 4-1( 略 ): 滚动条和其热点 滚动条发送的消息代码允许程序对滚动条支持的所有用户行为作出响应 表 4-1 列出了每个代码对应的行为 表 4-1: 滚动代码 Codes Response For WM_VSCROLL SB_LINEUP Program should scroll the screen up one line. SB_LINEDOWN Program should scroll the screen down one line. SB_PAGEUP Program should scroll the screen up one screen's worth of data. SB_PAGEDOWN Program should scroll the screen down one screen's worth of data. For WM_HSCROLL SB_LINELEFT Program should scroll the screen left one character. SB_LINERIGHT Program should scroll the screen right one character. SB_PAGELEFT Program should scroll the screen left one screen's worth of data. SB_PAGERIGHT Program should scroll the screen right one screen's worth of data. For both WM_VSCROLL and WM_HSCROLL SB_THUMBTRACK Programs with enough speed to keep up should update the display with the new scroll position. SB_THUMBPOSITION Programs that can't update the display fast enough to keep up with the SB_THUMBTRACK message should update the display with the new scroll position.
Codes Response For WM_VSCROLL SB_ENDSCROLL This code indicates that the scroll bar has completed the scroll event. No action is required by the program. SB_TOP Program should set the display to the top or left end of the data. SB_BOTTOM Program should set the display to the bottom or right end of the data. SB_LINExxx 和 SB_PAGExxx 代码是相当易懂的 每次您可以将滚动位置移动一行或者一页 SB_THUMBPOSITION 和 SB_THUMBTRACK 可以用两种方式之一来处理 当用户拖动滚动条滑块时, 滚动条会发送 SB_THUMBTRACK 代码, 这样程序可以交互地跟踪滑块的拖动 如果您的应用程序足够快, 您就可以只处理 SB_THUMBTRACK 代码并交互的更新显示 如果您填写了 SB_THUMBTRACK 代码, 但是您的应用程序必须快到足以重绘显示, 这样滑块在拖动过程中不会出现停顿 在运行 Windows CE 的较慢设备上, 这会是一个问题 如果您的应用程序 ( 或者操作系统 ) 太慢, 不能为每个 SB_THUMBTRACK 代码进行快速更新显示, 您可以忽略掉 SB_THUMBTRACK, 并等待在用户拖动滚动条滑块时发出的 SB_THUMBPOSITION 代码 这样在用户移动完滑块后, 您只需要更新显示一次即可 配置滚动条要使用滚动条, 应用程序首先应该设置滚动范围的最小值和最大值以及初始位置 和桌面系统里的滚动条一样,Windows CE 滚动条支持按比例调整滑块大小, 这可以给用户提供反馈, 了解当前看到的页占整个滚动范围的比例 要设置这些参数,Windows CE 应用程序可以使用 SetScrollInfo 函数, 其原型如下 : int SetScrollInfo(HWND hwnd, int fnbar, LPSCROLLINFO lpsi, BOOL fredraw); 第一个参数可以是包含滚动条的窗口的句柄, 也可以是滚动条自身的窗口句柄 第二个参数 fnbar 是一个标志位, 用于判断窗口句柄的用法 该标志位可以是下面三个值之一 :SB_HORZ 用于窗口中标准水平滚动条 ;SB_VERT 用于窗口中标准垂直滚动条 ;SB_CTL 用于独立的滚动条控件 除非滚动条是控件, 否则窗口句柄是包含滚动条的窗口的句柄 在句柄是滚动条控件自身的句柄时, 使用 SB_CTL 最后一个参数时 freddraw, 一个布尔值, 指出是否在调用完成后重新绘制滚动条 第三个参数是指向 SCROLLINFO 结构的指针, 该结构定义如下 : typedef struct tagscrollinfo { UINT cbsize; UINT fmask; int nmin; int nmax;
UINT npage; int npos; int ntrackpos; SCROLLINFO 该结构允许您完整的指定滚动条参数 cbsize 必须设置成 SCROLLINFO 结构的大小 fmask 是标志位, 指出结构中其它域包含什么样的有效数据 nmin 和 nmax 包含滚动条的最小和最大滚动值 如果 fmask 参数包含 SIF_RANGE 标志,Windows 就会在这两个域中查找这些值 同样地, 如果 fmask 包含 SIF_POS 标志,nPos 在预定义的范围内设置滚动条位置 npage 域允许程序定义屏幕当前可视区域相对于整个滚动区域的大小 这可以给用户一个大致印象, 整个滚动区域中当前有多少是可视的 只有当 fmask 中包含 SIF_PAGE 标志的时候这个域才有用 SCROLLINFO 结构的最后一个成员是 ntrackpos, 但 SetScrollInfo 不使用并忽略掉它了 fmask 最后一个标志是 SIF_DISABLENOSCROLL, 可以让滚动条失效但可视 当整个滚动范围在可视区域内可视且不再需要滚动的时候, 用这个方法是很方便的 在这种情况下, 使滚动条失效比简单的移去整个滚动条更好一些 细心的读者一定会注意到 SCROLLINFO 结构中域的宽度问题 nmin,nmax 和 npos 是整型, 在 Windows CE 中是 32 位宽 而另一方面,WM_HSCROLL 和 WM_VSCROLL 消息只能在 WParam 参数的高字位中返回一个 16 位的位置数据 如果您使用的滚动范围超过 65,535, 那么可以使用 GetScrollInfo 函数, 其原型如下 :BOOL GetScrollInfo (HWND hwnd, int fnbar, LPSCROLLINFO lpsi); 和 SetScrollInfo 一样,fnBar 中的标志位用来指出传递给函数的窗口句柄种类 SCROLLINFO 结构同 SetScrollInfo 中的一样 在传给 GetScrollInfo 之前, 必须先用结构的大小来始化 cbsize 应用程序必须通过设置 fmask 中的适当标志, 来指明希望函数返回什么数据 fmask 中使用的标志同 SetScrollInfo 中使用的一样, 同时增加了两个 现在可以传递 SIF_TRACKPOS 标志来让滚动条返回当前滑块位置 在 WM_xSCROLL 消息期间调用时, ntrackpos 包含实时位置, 而 npos 包含的是开始拖动滑块时的滚动条位置 滚动条是与众不同的控件, 因为只要简单指定窗口风格, 就可以很容易地把滚动条添加到窗口中 ; 另外它是放置在窗口客户区域外边地 原因是应用程序普遍需要滚动条, 所以 Windows 的开发者就尽量使绑定滚动条到窗口的工作更容易 现在, 来看一看其它基本 Windows 控件 CtlView 示例程序清单 4-1 给出了 CtlView 示例程序, 它演示了我刚才描述的所有控件 该示例程序使用了几个应用程序定义的子窗口, 其中包含了各种控件 您可以通过点主窗口顶部的 5 个单选框, 在不同子窗口之间进行切换 当每个控件通过 WM_COMMAND 消息来发送通知时, 通知消息会显示在窗口右边的列表框里 CtlView 可以很方便的观察到控件给父窗口发送什么了消息和什么时候发送的消息 CtlView 可根据屏幕宽度使用不同控件布局 这意味着即使在具有狭窄屏幕的 Pocket PC 上, 依然可以看到所有控件 清单 4-1:CtlView 程序 CtlView.h =================================================
===================== Header file Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== Returns number of elements #define dim(x) (sizeof(x) / sizeof(x[0])) ---------------------------------------------------------------------- Generic defines and data types struct decodeuint { Structure associates UINT Code; messages with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); ; struct decodecmd { Structure associates UINT Code; menu IDs with a LRESULT (*Fxn)(HWND, WORD, HWND, WORD); function. ; ---------------------------------------------------------------------- Generic defines used by application #define IDI_BTNICON 20 Icon used on button #define ID_ICON 1 Icon ID #define IDC_CMDBAR 2 Command bar ID #define IDC_RPTLIST 3 Report window ID Client window IDs go from 5 through 9. #define IDC_WNDSEL 5 Starting client window IDs Radio button IDs go from 10 through 14. #define IDC_RADIOBTNS 10 Starting ID of radio buttons Button window defines #define IDC_PUSHBTN 100 #define IDC_CHKBOX 101 #define IDC_ACHKBOX 102 #define IDC_A3STBOX 103 #define IDC_RADIO1 104
#define IDC_RADIO2 105 #define IDC_OWNRDRAW 106 Edit window defines #define IDC_SINGLELINE 100 #define IDC_MULTILINE 101 #define IDC_PASSBOX 102 List box window defines #define IDC_COMBOBOX 100 #define IDC_SNGLELIST 101 #define IDC_MULTILIST 102 Static control window defines #define IDC_LEFTTEXT 100 #define IDC_RIGHTTEXT 101 #define IDC_CENTERTEXT 102 #define IDC_ICONCTL 103 #define IDC_BITMAPCTL 104 Scroll bar window defines #define IDC_LRSCROLL 100 #define IDC_UDSCROLL 101 User-defined message to add a line to the window #define MYMSG_ADDLINE (WM_USER + 10) typedef struct { TCHAR *szclass; int nid; TCHAR *sztitle; int x; int y; int cx; int cy; DWORD lstyle; CTLWNDSTRUCT, *PCTLWNDSTRUCT; typedef struct { WORD wmsg; int nid; WPARAM wparam; LPARAM lparam; CTLMSG, * PCTLMSG; typedef struct {
TCHAR *pszlabel; WORD wnotification; NOTELABELS, *PNOTELABELS; ---------------------------------------------------------------------- Function prototypes HWND InitInstance (HINSTANCE, LPWSTR, int); int TermInstance (HINSTANCE, int); Window procedures LRESULT CALLBACK FrameWndProc (HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK ClientWndProc (HWND, UINT, WPARAM, LPARAM); Message handlers LRESULT DoCreateFrame (HWND, UINT, WPARAM, LPARAM); LRESULT DoSizeFrame (HWND, UINT, WPARAM, LPARAM); LRESULT DoCommandFrame (HWND, UINT, WPARAM, LPARAM); LRESULT DoAddLineFrame (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyFrame (HWND, UINT, WPARAM, LPARAM); ---------------------------------------------------------------------- Window prototypes and defines for BtnWnd #define BTNWND TEXT ("ButtonWnd") int InitBtnWnd (HINSTANCE); Window procedures LRESULT CALLBACK BtnWndProc (HWND, UINT, WPARAM, LPARAM); LRESULT DoCreateBtnWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoCtlColorBtnWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoCommandBtnWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoDrawItemBtnWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoMeasureItemBtnWnd (HWND, UINT, WPARAM, LPARAM); ---------------------------------------------------------------------- Window prototypes and defines for EditWnd #define EDITWND TEXT ("EditWnd") int InitEditWnd (HINSTANCE); Window procedures LRESULT CALLBACK EditWndProc (HWND, UINT, WPARAM, LPARAM); LRESULT DoCreateEditWnd (HWND, UINT, WPARAM, LPARAM);
LRESULT DoCommandEditWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoDrawItemEditWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoMeasureItemEditWnd (HWND, UINT, WPARAM, LPARAM); ---------------------------------------------------------------------- Window prototypes and defines for ListWnd #define LISTWND TEXT ("ListWnd") int InitListWnd (HINSTANCE); Window procedures LRESULT CALLBACK ListWndProc (HWND, UINT, WPARAM, LPARAM); LRESULT DoCreateListWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoCommandListWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoDrawItemListWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoMeasureItemListWnd (HWND, UINT, WPARAM, LPARAM); ---------------------------------------------------------------------- Window prototypes and defines for StatWnd #define STATWND TEXT ("StaticWnd") int InitStatWnd (HINSTANCE); Window procedures LRESULT CALLBACK StatWndProc (HWND, UINT, WPARAM, LPARAM); LRESULT DoCreateStatWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoCommandStatWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoDrawItemStatWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoMeasureItemStatWnd (HWND, UINT, WPARAM, LPARAM); ---------------------------------------------------------------------- Window prototypes and defines ScrollWnd #define SCROLLWND TEXT ("ScrollWnd") int InitScrollWnd (HINSTANCE); Window procedures LRESULT CALLBACK ScrollWndProc (HWND, UINT, WPARAM, LPARAM); LRESULT DoCreateScrollWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoVScrollScrollWnd (HWND, UINT, WPARAM, LPARAM); LRESULT DoHScrollScrollWnd (HWND, UINT, WPARAM, LPARAM);
CtlView.cpp ================================================= ===================== CtlView - Lists the available fonts in the system. Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include <commctrl.h> Command bar includes #include "CtlView.h" Program-specific stuff ---------------------------------------------------------------------- Global data const TCHAR szappname[] = TEXT ("CtlView"); HINSTANCE hinst; Program instance handle Message dispatch table for FrameWindowProc const struct decodeuint FrameMessages[] = { WM_CREATE, DoCreateFrame, WM_SIZE, DoSizeFrame, WM_COMMAND, DoCommandFrame, MYMSG_ADDLINE, DoAddLineFrame, WM_DESTROY, DoDestroyFrame, ; typedef struct { TCHAR *sztitle; int nid; TCHAR *szctlwnds; HWND hwndclient; RBTNDATA; Text for main window radio buttons TCHAR *szbtntitle[] = {TEXT ("Buttons"), TEXT ("Edit"), TEXT ("List"), TEXT ("Static"), TEXT ("Scroll"); Class names for child windows containing controls TCHAR *szctlwnds[] = {BTNWND, EDITWND, LISTWND, STATWND, SCROLLWND; int nwndsel = 0; ================================================= =====================
Program entry point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { MSG msg; int rc = 0; HWND hwndframe; Initialize application. hwndframe = InitInstance (hinstance, lpcmdline, ncmdshow); if (hwndframe == 0) return 0x10; Application message loop while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); Instance cleanup return TermInstance (hinstance, msg.wparam); ---------------------------------------------------------------------- InitInstance - Instance initialization HWND InitInstance (HINSTANCE hinstance, LPWSTR lpcmdline, int ncmdshow) { WNDCLASS wc; HWND hwnd; Save program instance handle in global variable. hinst = hinstance; #if defined(win32_platform_pspc) If Pocket PC, allow only one instance of the application hwnd = FindWindow (szappname, NULL); if (hwnd) { SetForegroundWindow ((HWND)(((DWORD)hWnd) 0x01)); return 0; #endif Register application frame window class. wc.style = 0; Window style wc.lpfnwndproc = FrameWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data
wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetSysColorBrush (COLOR_STATIC); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = szappname; Window class name if (RegisterClass (&wc) == 0) return 0; Initialize client window classes if (InitBtnWnd (hinstance)!= 0) return 0; if (InitEditWnd (hinstance)!= 0) return 0; if (InitListWnd (hinstance)!= 0) return 0; if (InitStatWnd (hinstance)!= 0) return 0; if (InitScrollWnd (hinstance)!= 0) return 0; Create frame window. hwnd = CreateWindowEx (WS_EX_NODRAG, szappname, TEXT ("Control View"), WS_VISIBLE WS_CAPTION WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL); Return fail code if window not created. if (!IsWindow (hwnd)) return 0; Standard show and update calls ShowWindow (hwnd, ncmdshow); UpdateWindow (hwnd); return hwnd; ---------------------------------------------------------------------- TermInstance - Program cleanup int TermInstance (HINSTANCE hinstance, int ndefrc) { return ndefrc; ================================================= ===================== Message handling procedures for FrameWindow ---------------------------------------------------------------------- FrameWndProc - Callback function for application window
LRESULT CALLBACK FrameWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(framemessages); i++) { if (wmsg == FrameMessages[i].Code) return (*FrameMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoCreateFrame - Process WM_CREATE message for window. LRESULT DoCreateFrame (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { HWND hwndchild; INT i; Set currently viewed window nwndsel = 0; Create the radio buttons. for (i = 0; i < dim(szbtntitle); i++) { hwndchild = CreateWindow (TEXT ("BUTTON"), szbtntitle[i], BS_AUTORADIOBUTTON WS_VISIBLE WS_CHILD, 0, 0, 80, 20, hwnd, (HMENU)(IDC_RADIOBTNS+i), hinst, NULL); Destroy frame if window not created. if (!IsWindow (hwndchild)) { DestroyWindow (hwnd); break; Create report window. Size it so that it fits either on the right or below the control windows, depending on the size of the screen. hwndchild = CreateWindowEx (WS_EX_CLIENTEDGE, TEXT ("listbox"), TEXT (""), WS_VISIBLE WS_CHILD WS_VSCROLL LBS_USETABSTOPS LBS_NOINTEGRALHEIGHT, 0, 0, 100, 100,hWnd, (HMENU)IDC_RPTLIST, hinst, NULL);
Destroy frame if window not created. if (!IsWindow (hwndchild)) { DestroyWindow (hwnd); return 0; Initialize tab stops for display list box. i = 24; SendMessage (hwndchild, LB_SETTABSTOPS, 1, (LPARAM)&i); Create the child windows. Size them so that they fit under the command bar and fill the left side of the child area. for (i = 0; i < dim(szctlwnds); i++) { hwndchild = CreateWindowEx (WS_EX_CLIENTEDGE, szctlwnds[i], TEXT (""), WS_CHILD, 0, 0, 200, 200, hwnd, (HMENU)(IDC_WNDSEL+i), hinst, NULL); Destroy frame if client window not created. if (!IsWindow (hwndchild)) { DestroyWindow (hwnd); return 0; Check one of the auto radio buttons. SendDlgItemMessage (hwnd, IDC_RADIOBTNS+nWndSel, BM_SETCHECK, 1, 0); hwndchild = GetDlgItem (hwnd, IDC_WNDSEL+nWndSel); ShowWindow (hwndchild, SW_SHOW); return 0; ---------------------------------------------------------------------- DoSizeFrame - Process WM_SIZE message for window. LRESULT DoSizeFrame (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int nwidth, nheight; int i, x, y, cx, cy; BOOL bwide = TRUE; RECT rect; GetWindowRect (hwnd, &rect); GetClientRect (hwnd, &rect); These arrays are used to adjust between wide and narrow screens. POINT ptrbtnsn[] = {{5,0, {90,0, {180,0, {5,20, {90,20; POINT ptrbtnsw[] = {{5,0, {90,0, {180,0, {270,0, {360,0;
LPPOINT pptrbtns = ptrbtnsw; nwidth = LOWORD (lparam); nheight = HIWORD (lparam); Use different layouts for narrow (Pocket PC) screens. if (GetSystemMetrics (SM_CXSCREEN) < 480) { pptrbtns = ptrbtnsn; bwide = FALSE; Move the radio buttons. for (i = 0; i < dim(szbtntitle); i++) SetWindowPos (GetDlgItem (hwnd, IDC_RADIOBTNS+i), 0, pptrbtns[i].x, pptrbtns[i].y, 0, 0, SWP_NOSIZE SWP_NOZORDER); Size report window so that it fits either on the right or below the control windows, depending on the size of the screen. x = bwide? nwidth/2 : 0; y = bwide? 20 : (nheight)/2 + 40; cx = bwide? nwidth/2 : nwidth; cy = nheight - y; SetWindowPos (GetDlgItem (hwnd, IDC_RPTLIST), 0, x, y, cx, cy, SWP_NOZORDER); Size the child windows so that they fit under the command bar and fill the left side of the child area. x = 0; y = bwide? 20 : 40; cx = bwide? nwidth/2 : nwidth; cy = bwide? nheight : (nheight)/2+40; for (i = 0; i < dim(szctlwnds); i++) SetWindowPos (GetDlgItem (hwnd, IDC_WNDSEL+i), 0, x, y, cx, cy, SWP_NOZORDER); return 0; ---------------------------------------------------------------------- DoCommandFrame - Process WM_COMMAND message for window. LRESULT DoCommandFrame (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { HWND hwndtemp;
int nbtn; Don't look at list box messages. if (LOWORD (wparam) == IDC_RPTLIST) return 0; nbtn = LOWORD (wparam) - IDC_RADIOBTNS; if (nwndsel!= nbtn) { Hide the currently visible window. hwndtemp = GetDlgItem (hwnd, IDC_WNDSEL+nWndSel); ShowWindow (hwndtemp, SW_HIDE); Save the current selection. nwndsel = nbtn; Show the window selected via the radio button. hwndtemp = GetDlgItem (hwnd, IDC_WNDSEL+nWndSel); ShowWindow (hwndtemp, SW_SHOW); return 0; ---------------------------------------------------------------------- DoAddLineFrame - Process MYMSG_ADDLINE message for window. LRESULT DoAddLineFrame (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { TCHAR szout[128]; int i; if (LOWORD (wparam) == 0xffff) wsprintf (szout, TEXT (" \t %s"), (LPTSTR)lParam); else wsprintf (szout, TEXT ("id:%3d \t %s"), LOWORD (wparam), (LPTSTR)lParam); i = SendDlgItemMessage (hwnd, IDC_RPTLIST, LB_ADDSTRING, 0, (LPARAM)(LPCTSTR)szOut); if (i!= LB_ERR) SendDlgItemMessage (hwnd, IDC_RPTLIST, LB_SETTOPINDEX, i, (LPARAM)(LPCTSTR)szOut); return 0; ---------------------------------------------------------------------- DoDestroyFrame - Process WM_DESTROY message for window.
LRESULT DoDestroyFrame (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PostQuitMessage (0); return 0; BtnWnd.cpp ================================================= ===================== BtnWnd - Button window code Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include "Ctlview.h" Program-specific stuff extern HINSTANCE hinst; LRESULT DrawButton (HWND hwnd, LPDRAWITEMSTRUCT pdi); ---------------------------------------------------------------------- Global data Message dispatch table for BtnWndWindowProc const struct decodeuint BtnWndMessages[] = { WM_CREATE, DoCreateBtnWnd, WM_CTLCOLORSTATIC, DoCtlColorBtnWnd, WM_COMMAND, DoCommandBtnWnd, WM_DRAWITEM, DoDrawItemBtnWnd, ; Structure defining the controls in the window CTLWNDSTRUCT Btns [] = { {TEXT ("BUTTON"), IDC_PUSHBTN, TEXT ("Button"), 10, 10, 120, 23, BS_PUSHBUTTON BS_NOTIFY, {TEXT ("BUTTON"), IDC_CHKBOX, TEXT ("Check box"), 10, 35, 120, 23, BS_CHECKBOX, {TEXT ("BUTTON"), IDC_ACHKBOX, TEXT ("Auto check box"), 10, 60, 110, 23, BS_AUTOCHECKBOX, {TEXT ("BUTTON"), IDC_A3STBOX, TEXT ("Multiline auto 3-state box"), 140, 60, 90, 52, BS_AUTO3STATE BS_MULTILINE, {TEXT ("BUTTON"), IDC_RADIO1, TEXT ("Auto radio button 1"), 10, 85, 120, 23, BS_AUTORADIOBUTTON,
{TEXT ("BUTTON"), IDC_RADIO2, TEXT ("Auto radio button 2"), 10, 110, 120, 23, BS_AUTORADIOBUTTON, {TEXT ("BUTTON"), IDC_OWNRDRAW, TEXT ("OwnerDraw"), 150, 10, 44, 44, BS_PUSHBUTTON BS_OWNERDRAW, ; Structure labeling the button control WM_COMMAND notifications NOTELABELS nlbtn[] = {{TEXT ("BN_CLICKED "), 0, {TEXT ("BN_PAINT "), 1, {TEXT ("BN_HILITE "), 2, {TEXT ("BN_UNHILITE"), 3, {TEXT ("BN_DISABLE "), 4, {TEXT ("BN_DOUBLECLICKED"), 5, {TEXT ("BN_SETFOCUS "), 6, {TEXT ("BN_KILLFOCUS"), 7 ; Handle for icon used in owner-draw icon HICON hicon = 0; ---------------------------------------------------------------------- InitBtnWnd - BtnWnd window initialization int InitBtnWnd (HINSTANCE hinstance) { WNDCLASS wc; Register application BtnWnd window class. wc.style = 0; Window style wc.lpfnwndproc = BtnWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = BTNWND; Window class name if (RegisterClass (&wc) == 0) return 1; return 0; ================================================= ===================== Message handling procedures for BtnWindow ----------------------------------------------------------------------
BtnWndWndProc - Callback function for application window LRESULT CALLBACK BtnWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(btnwndmessages); i++) { if (wmsg == BtnWndMessages[i].Code) return (*BtnWndMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoCreateBtnWnd - Process WM_CREATE message for window. LRESULT DoCreateBtnWnd (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int i; for (i = 0; i < dim(btns); i++) { CreateWindow (Btns[i].szClass, Btns[i].szTitle, Btns[i].lStyle WS_VISIBLE WS_CHILD, Btns[i].x, Btns[i].y, Btns[i].cx, Btns[i].cy, hwnd, (HMENU) Btns[i].nID, hinst, NULL); hicon = LoadIcon (hinst, TEXT ("TEXTICON")); We need to set the initial state of the radio buttons. CheckRadioButton (hwnd, IDC_RADIO1, IDC_RADIO2, IDC_RADIO1); return 0; ---------------------------------------------------------------------- DoCtlColorBtnWnd - process WM_CTLCOLORxx messages for window. LRESULT DoCtlColorBtnWnd (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { return (LRESULT)GetStockObject (WHITE_BRUSH); ---------------------------------------------------------------------- DoCommandBtnWnd - Process WM_COMMAND message for window.
LRESULT DoCommandBtnWnd (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { TCHAR szout[128]; int i; Since the Check Box button is not an auto check box, it must be set manually. if ((LOWORD (wparam) == IDC_CHKBOX) && (HIWORD (wparam) == BN_CLICKED)) { Get the current state, complement, and set. i = SendDlgItemMessage (hwnd, IDC_CHKBOX, BM_GETCHECK, 0, 0); if (i == 0) SendDlgItemMessage (hwnd, IDC_CHKBOX, BM_SETCHECK, 1, 0); else SendDlgItemMessage (hwnd, IDC_CHKBOX, BM_SETCHECK, 0, 0); Report WM_COMMAND messages to main window. for (i = 0; i < dim(nlbtn); i++) { if (HIWORD (wparam) == nlbtn[i].wnotification) { lstrcpy (szout, nlbtn[i].pszlabel); break; if (i == dim(nlbtn)) wsprintf (szout, TEXT ("notification: %x"), HIWORD (wparam)); SendMessage (GetParent (hwnd), MYMSG_ADDLINE, wparam, (LPARAM)szOut); return 0; ---------------------------------------------------------------------- DoDrawItemBtnWnd - Process WM_DRAWITEM message for window. LRESULT DoDrawItemBtnWnd (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { return DrawButton (hwnd, (LPDRAWITEMSTRUCT)lParam); --------------------------------------------------------------------- DrawButton - Draws an owner-draw button LRESULT DrawButton (HWND hwnd, LPDRAWITEMSTRUCT pdi) {
HPEN hpenshadow, hpenlight, hpendkshadow, hredpen, holdpen; HBRUSH hbr, holdbr; LOGPEN lpen; TCHAR szout[128]; POINT ptout[3], ptin[3]; Reflect the messages to the report window. wsprintf (szout, TEXT ("WM_DRAWITEM Act:%x State:%x"), pdi->itemaction, pdi->itemstate); SendMessage (GetParent (hwnd), MYMSG_ADDLINE, pdi->ctlid, (LPARAM)szOut); Create pens for drawing. lpen.lopnstyle = PS_SOLID; lpen.lopnwidth.x = 3; lpen.lopnwidth.y = 3; lpen.lopncolor = GetSysColor (COLOR_3DSHADOW); hpenshadow = CreatePenIndirect (&lpen); lpen.lopncolor = RGB (255, 0, 0); hredpen = CreatePenIndirect (&lpen); lpen.lopnwidth.x = 1; lpen.lopnwidth.y = 1; lpen.lopncolor = GetSysColor (COLOR_3DLIGHT); hpenlight = CreatePenIndirect (&lpen); lpen.lopncolor = GetSysColor (COLOR_3DDKSHADOW); hpendkshadow = CreatePenIndirect (&lpen); Create a brush for the face of the button. hbr = CreateSolidBrush (GetSysColor (COLOR_3DFACE)); Draw a rectangle with a thick outside border to start the frame drawing. holdpen = (HPEN_SelectObject (pdi->hdc, hpenshadow); holdbr = (HBRUSH)SelectObject (pdi->hdc, hbr); Rectangle (pdi->hdc, pdi->rcitem.left, pdi->rcitem.top, pdi->rcitem.right, pdi->rcitem.bottom); Draw the upper left inside line. ptin[0].x = pdi->rcitem.left + 1; ptin[0].y = pdi->rcitem.bottom - 2; ptin[1].x = pdi->rcitem.left + 1;
ptin[1].y = pdi->rcitem.top + 1; ptin[2].x = pdi->rcitem.right - 2; ptin[2].y = pdi->rcitem.top + 1; Select a pen to draw shadow or light side of button. if (pdi->itemstate & ODS_SELECTED) { SelectObject (pdi->hdc, hpendkshadow); else { SelectObject (pdi->hdc, hpenlight); Polyline (pdi->hdc, ptin, 3); If selected, also draw a bright line inside the lower right corner. if (pdi->itemstate & ODS_SELECTED) { SelectObject (pdi->hdc, hpenlight); ptin[1].x = pdi->rcitem.right - 2; ptin[1].y = pdi->rcitem.bottom - 2; Polyline (pdi->hdc, ptin, 3); Now draw the black outside line on either the upper left or lower right corner. ptout[0].x = pdi->rcitem.left; ptout[0].y = pdi->rcitem.bottom - 1; ptout[2].x = pdi->rcitem.right - 1; ptout[2].y = pdi->rcitem.top; SelectObject (pdi->hdc, hpendkshadow); if (pdi->itemstate & ODS_SELECTED) { ptout[1].x = pdi->rcitem.left; ptout[1].y = pdi->rcitem.top; else { ptout[1].x = pdi->rcitem.right - 1; ptout[1].y = pdi->rcitem.bottom - 1; Polyline (pdi->hdc, ptout, 3); Draw the triangle. ptout[0].x = (pdi->rcitem.right - pdi->rcitem.left)/2; ptout[0].y = pdi->rcitem.top + 4; ptout[1].x = pdi->rcitem.left + 3; ptout[1].y = pdi->rcitem.bottom - 6; ptout[2].x = pdi->rcitem.right - 6; ptout[2].y = pdi->rcitem.bottom - 6; SelectObject (pdi->hdc, hredpen);
Polygon (pdi->hdc, ptout, 3); If button has the focus, draw the dotted rect inside the button. if (pdi->itemstate & ODS_FOCUS) { pdi->rcitem.left += 3; pdi->rcitem.top += 3; pdi->rcitem.right -= 4; pdi->rcitem.bottom -= 4; DrawFocusRect (pdi->hdc, &pdi->rcitem); Clean up. First select the original brush and pen into the DC. SelectObject (pdi->hdc, holdbr); SelectObject (pdi->hdc, holdpen); Now delete the brushes and pens created. DeleteObject (hbr); DeleteObject (hpenshadow); DeleteObject (hpendkshadow); DeleteObject (hpenlight); return 0; EditWnd.cpp ================================================= ===================== EditWnd - Edit control window code Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include "Ctlview.h" Program-specific stuff extern HINSTANCE hinst; ---------------------------------------------------------------------- Global data Message dispatch table for EditWndWindowProc const struct decodeuint EditWndMessages[] = { WM_CREATE, DoCreateEditWnd, WM_COMMAND, DoCommandEditWnd, ; Structure defining the controls in the window
CTLWNDSTRUCT Edits[] = { {TEXT ("edit"), IDC_SINGLELINE, TEXT ("Single line edit control"), 10, 10, 180, 23, ES_AUTOHSCROLL, {TEXT ("edit"), IDC_MULTILINE, TEXT ("Multiline edit control"), 10, 35, 180, 70, ES_MULTILINE ES_AUTOVSCROLL, {TEXT ("edit"), IDC_PASSBOX, TEXT (""), 10, 107, 180, 23, ES_PASSWORD, ; Structure labeling the edit control WM_COMMAND notifications NOTELABELS nledit[] = {{TEXT ("EN_SETFOCUS "), 0x0100, {TEXT ("EN_KILLFOCUS"), 0x0200, {TEXT ("EN_CHANGE "), 0x0300, {TEXT ("EN_UPDATE "), 0x0400, {TEXT ("EN_ERRSPACE "), 0x0500, {TEXT ("EN_MAXTEXT "), 0x0501, {TEXT ("EN_HSCROLL "), 0x0601, {TEXT ("EN_VSCROLL "), 0x0602, ; ---------------------------------------------------------------------- InitEditWnd - EditWnd window initialization int InitEditWnd (HINSTANCE hinstance) { WNDCLASS wc; Register application EditWnd window class. wc.style = 0; Window style wc.lpfnwndproc = EditWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = EDITWND; Window class name if (RegisterClass (&wc) == 0) return 1; return 0; ================================================= ===================== Message handling procedures for EditWindow
---------------------------------------------------------------------- EditWndWndProc - Callback function for application window LRESULT CALLBACK EditWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(editwndmessages); i++) { if (wmsg == EditWndMessages[i].Code) return (*EditWndMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoCreateEditWnd - Process WM_CREATE message for window. LRESULT DoCreateEditWnd (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int i; for (i = 0; i < dim(edits); i++) { CreateWindow (Edits[i].szClass, Edits[i].szTitle, Edits[i].lStyle WS_VISIBLE WS_CHILD WS_BORDER, Edits[i].x, Edits[i].y, Edits[i].cx, Edits[i].cy, hwnd, (HMENU) Edits[i].nID, hinst, NULL); return 0; ---------------------------------------------------------------------- DoCommandEditWnd - Process WM_COMMAND message for window. LRESULT DoCommandEditWnd (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { TCHAR szout[128]; int i; for (i = 0; i < dim(nledit); i++) { if (HIWORD (wparam) == nledit[i].wnotification) { lstrcpy (szout, nledit[i].pszlabel); break;
if (i == dim(nledit)) wsprintf (szout, TEXT ("notification: %x"), HIWORD (wparam)); SendMessage (GetParent (hwnd), MYMSG_ADDLINE, wparam, return 0; (LPARAM)szOut); ListWnd.cpp ================================================= ===================== ListWnd - List box control window code Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include "Ctlview.h" Program-specific stuff extern HINSTANCE hinst; ---------------------------------------------------------------------- Global data Message dispatch table for ListWndWindowProc const struct decodeuint ListWndMessages[] = { WM_CREATE, DoCreateListWnd, WM_COMMAND, DoCommandListWnd, ; Structure defining the controls in the window CTLWNDSTRUCT Lists[] = { {TEXT ("combobox"), IDC_COMBOBOX, TEXT (""), 10, 10, 205, 100, WS_VSCROLL, {TEXT ("Listbox"), IDC_SNGLELIST, TEXT (""), 10, 35, 100, 90, WS_VSCROLL LBS_NOTIFY, {TEXT ("Listbox"), IDC_MULTILIST, TEXT (""), 115, 35, 100, 90, WS_VSCROLL LBS_EXTENDEDSEL LBS_NOTIFY ; Structure labeling the list box control WM_COMMAND notifications
NOTELABELS nllist[] = {{TEXT ("LBN_ERRSPACE "), (-2), {TEXT ("LBN_SELCHANGE"), 1, {TEXT ("LBN_DBLCLK "), 2, {TEXT ("LBN_SELCANCEL"), 3, {TEXT ("LBN_SETFOCUS "), 4, {TEXT ("LBN_KILLFOCUS"), 5, ; Structure labeling the combo box control WM_COMMAND notifications NOTELABELS nlcombo[] = {{TEXT ("CBN_ERRSPACE "), (-1), {TEXT ("CBN_SELCHANGE "), 1, {TEXT ("CBN_DBLCLK "), 2, {TEXT ("CBN_SETFOCUS "), 3, {TEXT ("CBN_KILLFOCUS "), 4, {TEXT ("CBN_EDITCHANGE "), 5, {TEXT ("CBN_EDITUPDATE "), 6, {TEXT ("CBN_DROPDOWN "), 7, {TEXT ("CBN_CLOSEUP "), 8, {TEXT ("CBN_SELENDOK "), 9, {TEXT ("CBN_SELENDCANCEL"), 10, ; ---------------------------------------------------------------------- InitListWnd - ListWnd window initialization int InitListWnd (HINSTANCE hinstance) { WNDCLASS wc; Register application ListWnd window class. wc.style = 0; Window style wc.lpfnwndproc = ListWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = LISTWND; Window class name if (RegisterClass (&wc) == 0) return 1; return 0; ================================================= =====================
Message handling procedures for ListWindow ---------------------------------------------------------------------- ListWndProc - Callback function for application window LRESULT CALLBACK ListWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(listwndmessages); i++) { if (wmsg == ListWndMessages[i].Code) return (*ListWndMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoCreateListWnd - Process WM_CREATE message for window. LRESULT DoCreateListWnd (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int i; TCHAR szout[64]; for (i = 0; i < dim(lists); i++) { CreateWindow (Lists[i].szClass, Lists[i].szTitle, Lists[i].lStyle WS_VISIBLE WS_CHILD WS_BORDER, Lists[i].x, Lists[i].y, Lists[i].cx, Lists[i].cy, hwnd, (HMENU) Lists[i].nID, hinst, NULL); for (i = 0; i < 20; i++) { wsprintf (szout, TEXT ("Item %d"), i); SendDlgItemMessage (hwnd, IDC_SNGLELIST, LB_ADDSTRING, 0, (LPARAM)szOut); SendDlgItemMessage (hwnd, IDC_MULTILIST, LB_ADDSTRING, 0, (LPARAM)szOut); SendDlgItemMessage (hwnd, IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)szOut); Set initial selection. SendDlgItemMessage (hwnd, IDC_COMBOBOX, CB_SETCURSEL, 0, 0);
return 0; ---------------------------------------------------------------------- DoCommandListWnd - Process WM_COMMAND message for window. LRESULT DoCommandListWnd (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { TCHAR szout[128]; int i; if (LOWORD (wparam) == IDC_COMBOBOX) { for (i = 0; i < dim(nlcombo); i++) { if (HIWORD (wparam) == nlcombo[i].wnotification) { lstrcpy (szout, nlcombo[i].pszlabel); break; if (i == dim(nllist)) wsprintf (szout, TEXT ("notification: %x"), HIWORD (wparam)); else { for (i = 0; i < dim(nllist); i++) { if (HIWORD (wparam) == nllist[i].wnotification) { lstrcpy (szout, nllist[i].pszlabel); break; if (i == dim(nllist)) wsprintf (szout, TEXT ("notification: %x"), HIWORD (wparam)); SendMessage (GetParent (hwnd), MYMSG_ADDLINE, wparam, (LPARAM)szOut); return 0; StatWnd.cpp ================================================= ===================== StatWnd - Static control window code Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include "Ctlview.h" Program-specific stuff
extern HINSTANCE hinst; ---------------------------------------------------------------------- Global data Message dispatch table for StatWndWindowProc const struct decodeuint StatWndMessages[] = { WM_CREATE, DoCreateStatWnd, WM_COMMAND, DoCommandStatWnd, ; Structure defining the controls in the window CTLWNDSTRUCT Stats [] = { {TEXT ("static"), IDC_LEFTTEXT, TEXT ("Left text"), 10, 10, 120, 23, SS_LEFT SS_NOTIFY, {TEXT ("static"), IDC_RIGHTTEXT, TEXT ("Right text"), 10, 35, 120, 23, SS_RIGHT, {TEXT ("static"), IDC_CENTERTEXT, TEXT ("Center text"), 10, 60, 120, 23, SS_CENTER WS_BORDER, ; Structure labeling the static control WM_COMMAND notifications NOTELABELS nlstatic[] = {{TEXT ("STN_CLICKED"), 0, {TEXT ("STN_ENABLE "), 2, {TEXT ("STN_DISABLE"), 3, ; ---------------------------------------------------------------------- InitStatWnd - StatWnd window initialization int InitStatWnd (HINSTANCE hinstance) { WNDCLASS wc; Register application StatWnd window class. wc.style = 0; Window style wc.lpfnwndproc = StatWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = STATWND; Window class name
if (RegisterClass (&wc) == 0) return 1; return 0; ================================================= ===================== Message handling procedures for StatWindow ---------------------------------------------------------------------- StatWndProc - Callback function for application window LRESULT CALLBACK StatWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(statwndmessages); i++) { if (wmsg == StatWndMessages[i].Code) return (*StatWndMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoCreateStatWnd - Process WM_CREATE message for window. LRESULT DoCreateStatWnd (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int i; for (i = 0; i < dim(stats); i++) { CreateWindow (Stats[i].szClass, Stats[i].szTitle, Stats[i].lStyle WS_VISIBLE WS_CHILD, Stats[i].x, Stats[i].y, Stats[i].cx, Stats[i].cy, hwnd, (HMENU) Stats[i].nID, hinst, NULL); return 0; ---------------------------------------------------------------------- DoCommandStatWnd - Process WM_COMMAND message for window. LRESULT DoCommandStatWnd (HWND hwnd, UINT wmsg, WPARAM wparam,
TCHAR szout[128]; int i; LPARAM lparam) { for (i = 0; i < dim(nlstatic); i++) { if (HIWORD (wparam) == nlstatic[i].wnotification) { lstrcpy (szout, nlstatic[i].pszlabel); break; if (i == dim(nlstatic)) wsprintf (szout, TEXT ("notification: %x"), HIWORD (wparam)); SendMessage (GetParent (hwnd), MYMSG_ADDLINE, wparam, (LPARAM)szOut); return 0; ScrollWnd.cpp ================================================= ===================== ScrollWnd - Scroll bar control window code Written for the book Programming Windows CE Copyright (C) 2001 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include "Ctlview.h" Program-specific stuff extern HINSTANCE hinst; ---------------------------------------------------------------------- Global data Message dispatch table for ScrollWndWindowProc const struct decodeuint ScrollWndMessages[] = { WM_CREATE, DoCreateScrollWnd, WM_HSCROLL, DoVScrollScrollWnd, WM_VSCROLL, DoVScrollScrollWnd, ; Structure defining the controls in the window CTLWNDSTRUCT Scrolls [] = { {TEXT ("Scrollbar"), IDC_LRSCROLL, TEXT (""), 10, 10, 150, 23, SBS_HORZ,
; {TEXT ("Scrollbar"), IDC_UDSCROLL, TEXT (""), 180, 10, 23, 120, SBS_VERT, Structure labeling the scroll bar control scroll codes for WM_VSCROLL NOTELABELS nlvscroll[] = {{TEXT ("SB_LINEUP "), 0, {TEXT ("SB_LINEDOWN "), 1, {TEXT ("SB_PAGEUP "), 2, {TEXT ("SB_PAGEDOWN "), 3, {TEXT ("SB_THUMBPOSITION"), 4, {TEXT ("SB_THUMBTRACK "), 5, {TEXT ("SB_TOP "), 6, {TEXT ("SB_BOTTOM "), 7, {TEXT ("SB_ENDSCROLL "), 8, ; Structure labeling the scroll bar control scroll codes for WM_HSCROLL NOTELABELS nlhscroll[] = {{TEXT ("SB_LINELEFT "), 0, {TEXT ("SB_LINERIGHT "), 1, {TEXT ("SB_PAGELEFT "), 2, {TEXT ("SB_PAGERIGHT "), 3, {TEXT ("SB_THUMBPOSITION"), 4, {TEXT ("SB_THUMBTRACK "), 5, {TEXT ("SB_LEFT "), 6, {TEXT ("SB_RIGHT "), 7, {TEXT ("SB_ENDSCROLL "), 8, ; ---------------------------------------------------------------------- InitScrollWnd - ScrollWnd window initialization int InitScrollWnd (HINSTANCE hinstance) { WNDCLASS wc; Register application ScrollWnd window class. wc.style = 0; Window style wc.lpfnwndproc = ScrollWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = SCROLLWND; Window class name
if (RegisterClass (&wc) == 0) return 1; return 0; ================================================= ===================== Message handling procedures for ScrollWindow ---------------------------------------------------------------------- ScrollWndProc - Callback function for application window LRESULT CALLBACK ScrollWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(scrollwndmessages); i++) { if (wmsg == ScrollWndMessages[i].Code) return (*ScrollWndMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoCreateScrollWnd - Process WM_CREATE message for window. LRESULT DoCreateScrollWnd (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int i; for (i = 0; i < dim(scrolls); i++) { CreateWindow (Scrolls[i].szClass, Scrolls[i].szTitle, Scrolls[i].lStyle WS_VISIBLE WS_CHILD, Scrolls[i].x, Scrolls[i].y, Scrolls[i].cx, Scrolls[i].cy, hwnd, (HMENU) Scrolls[i].nID, hinst, NULL); return 0; ---------------------------------------------------------------------- DoVScrollScrollWnd - Process WM_VSCROLL message for window. LRESULT DoVScrollScrollWnd (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) {
TCHAR szout[128]; SCROLLINFO si; int i, spos; Update the report window. if (GetDlgItem (hwnd, 101) == (HWND)lParam) { for (i = 0; i < dim(nlvscroll); i++) { if (LOWORD (wparam) == nlvscroll[i].wnotification) { lstrcpy (szout, nlvscroll[i].pszlabel); break; if (i == dim(nlvscroll)) wsprintf (szout, TEXT ("notification: %x"), HIWORD (wparam)); else { for (i = 0; i < dim(nlhscroll); i++) { if (LOWORD (wparam) == nlhscroll[i].wnotification) { lstrcpy (szout, nlhscroll[i].pszlabel); break; if (i == dim(nlhscroll)) wsprintf (szout, TEXT ("notification: %x"), HIWORD (wparam)); SendMessage (GetParent (hwnd), MYMSG_ADDLINE, -1, (LPARAM)szOut); Get scroll bar position. si.cbsize = sizeof (si); si.fmask = SIF_POS; GetScrollInfo ((HWND)lParam, SB_CTL, &si); spos = si.npos; Act on the scroll code. switch (LOWORD (wparam)) { case SB_LINEUP: Also SB_LINELEFT spos -= 2; break; case SB_LINEDOWN: Also SB_LINERIGHT spos += 2; break; case SB_PAGEUP: spos -= 10; break; Also SB_PAGELEFT
case SB_PAGEDOWN: spos += 10; break; Also SB_PAGERIGHT case SB_THUMBPOSITION: spos = HIWORD (wparam); break; Check range. if (spos < 0) spos = 0; if (spos > 100) spos = 100; Update scroll bar position. si.cbsize = sizeof (si); si.npos = spos; si.fmask = SIF_POS; SetScrollInfo ((HWND)lParam, SB_CTL, &si, TRUE); return 0; 当 CtlView 启动时, 主窗口 WM_CREATE 消息的处理程序 DocreateFrame 会在窗口顶部创建一行单选按钮, 一个用于消息报告的列表框, 以及 5 个子窗口 ( 这 5 个子窗口创建的时候都没有使用 WS_VISIBLE 风格, 所以最初他们是隐藏的 ) 每个子窗口依次创建了许多控件 在 DoCreateFrame 返回之前,CtlView 选中一个自动风格的单选框, 并使用 ShowWindow 使 BtnWnd 子窗口 ( 包含示例按钮控件的窗口 ) 可视 主窗口 WM_SIZE 处理程序 DoSizeMain 用来决定框架窗口中每个子窗口的位置 之所以要在这里处理, 是因为在 WM_CREATE 里的窗口尺寸参数没有计算标题栏的尺寸 当子窗口中的各个控件被压触 点击或者选中, 控件会发送 WM_COMMAND 消息给其父窗口 该父窗口又将 WM_COMMAND 消息里的信息通过 MYMSG_ADDLINE 消息发送给自己的父窗口 -- 框架窗口,MYMSG_ADDLINE 消息是应用程序自定义的消息 在那里, 通知消息中的数据被格式化并显示在主框架右边的列表框里, 如果是 Pocket PC 的话则显示在主框架的下面 框架窗口的另一个功能是在不同子窗口之间切换 通过只显示框架窗口顶部单选按钮选中的子窗口, 应用程序达到切换的目的 这一处理过程位于 CtlVoiew.cpp 中处理 WM_COMMAND 的 DoCommandFrame 例程中 观察控件如何及何时发送通知的最好方法就是运行示例程序并使用每个控件 图 4-2 给出了显示按钮控件时的 CtlView 示例窗口 当每个按钮被点击, 会发送一个 BN_CLICKED 通知给控件的父窗口 父窗口简单标记该通知并显示在列表框里 因为复选框不是自动风格的, 所以当用户选择的时候,CtlView 必须手工改变复选框的状态 其它复选框何单选框是可以自动改变状态, 因为它们是使用 BS_AUTOCHECKBOX, BS_AUTO3STATE, 和 BS_AUTORADIOBUTTON
风格创建的 在三角形图标里有个感叹号的那个矩形按钮是一个自绘制按钮 图 4-2( 略 ): 在左边格子中显示按钮子窗口的 CtlView 窗口 每个子窗口的源代码都包含在一个独立的文件中 包含按钮控件的源代码包含在 BtnWnd.cpp 中 该文件包含用于窗口自身的注册窗口及窗口过程的初始化例程 按钮控件自身在 WM_CREATE 消息中使用 CreateWindow 来创建 每个控件的位置 风格和其它外观都包含在 Btns 结构数组中 DoCreateBtnWnd 函数循环遍历数组里的每个入口, 并为每个入口调用 CreateWindow CtlView 中的每个子窗口都使用了类似的过程来创建自己的控件 为了支持自绘制按钮,BtnWndProc 必须处理 WM_DRAWITEM 消息 当按钮因为状态改变 获得 / 失去焦点或者从覆盖中暴露而需要重新绘制的时候, 会发送 WM_DRAWITEM 消息 尽管 DrawButton 函数 ( 每当收到一个 WM_DRAWITEM 小时就被调用 ) 花了很大力气来使按钮看上去像一个标准按钮, 但一个按钮不能有我们需要的外观是没有理由的 另外的窗口过程只为它们的控件提供了基本支持 WM_COMMAND 处理程序只是简单的将通知消息反射回主窗口 ScrollWnd 子窗口过程 ScrollWndProc 处理 WM_VSCROLL 和 WM_HSCROLL 消息, 因为滚动条控件就是通过这些消息和父窗口通信的 控件和颜色最后, 简单说一下颜色 在 CtlView 中, 框架窗口类的注册方式同先前程序里的注册方式略微有所不同 在本例中, 我使用下面代码来设置框架窗口的背景画刷 wc.hbrbackground = (HBRUSH) GetSysColorBrush (COLOR_STATIC); 这样设置的框架窗口背景色和用于绘制单选框的背景色是一样的 GetSysColorBrush 返回一个画刷, 它和系统用于绘制各种对象的颜色相匹配 在本例中, 给 GetSysColorBrush 传入的是常量 COLOR_STATIC, 会返回 Windows 用来绘制静态文本以及复选框 单选框文本的背景色 这使框架窗口背景和静态文本的背景一致 在包含按钮控件的窗口里, 通过产生 WM_CTLCOLORSTATIC 消息, 改变复选框和单选框的背景色来和窗口的白色背景匹配 当绘制复选框或单选框的时候, 该控件会向父窗口询问使用何种颜色, 此时该消息被发送给静态控件或按钮控件的父窗口 在 CtlView 中, 按钮窗口返回一个白色画刷的句柄, 这使得控件的背景色何窗口的白色背景相匹配 通过产生 WM_CTLCOLORBUTTON 消息, 您可以修改下压按钮的颜色 其它控件会发送不同的 WM_CTLCOLORxxx 消息, 这样父窗口就可以修改用于绘制控件的颜色了 另一个使用 WM_CTLCOLORSTATIC 消息的例子请参见第 18 章的 PowerBar 示例程序 WinCE 程序设计 (3rd 版 )--4.4 菜单 翻译 tellmenow 菜单在 Windows 输入中占据重要位置 虽然每个应用程序可能有不同的键盘和手写笔界面, 但几乎所有的应用程序都按 Windows 用户熟悉的结构来组织菜单 在 Windows CE 程序中使用菜单有些不同于其它版本的 Windows 程序, 最显著的不同是, 在 Windows CE 里, 菜单不是标准顶层窗口的一部分 相反, 菜单被绑定到为窗口创建的命令条或者菜单条控件上 除了这一变化外, 菜单的功能以及菜单的选择方式同其它版本的 Windows 大部分是相同的 鉴于这个普遍相似性, 在本节我只对 Windows 菜单管理做一个基本介绍
要创建菜单, 只要简单调用 HMENU CreateMenu(void) 即可, 该函数返回一个空菜单的句柄 要给菜单添加项, 可以使用两种调用方式 第一种是调用 BOOL AppendMenu (HMENU hmenu, UINT fuflags, UINT idnewitem, LPCTSTR lpsznewitem), 将在菜单末尾添加一个单独菜单项 可以用一系列标志来设置 fuflages 参数, 用来指示菜单项的初始情形 例如, 菜单项开始可能是失效的 ( 使用 MF_GRAYED 标志 ) 或者有一个选择标记在旁边 ( 使用 MF_CHECKED 标志 ) 几乎所有的调用都会指定 MF_STRING 标志, 表示 lpsznewitem 参数是包含菜单项文本的字符串 idnewitem 表示用户选择的菜单项 ID 或者需要改变的菜单项的状态 添加菜单项的另一种方法是调用 BOOL InsertMenu(HMENU hmenu, UINT uposition, UINT uflags, UINT uidnewitem, LPCTSTR lpnewitem); 该函数与 AppendMenu 类似, 但增加了灵活性, 菜单项可以插到菜单的任何位置 对该函数来说, 可以传给 uflags 两个附加标志之一 :MF_BYCOMMAND 或 MF_BYPOSITION, 用来指出如何确定菜单项在菜单里的位置 菜单可以采用嵌套来达到级联效果 要增加一个级联菜单或者子菜单, 可以使用 HMENU CreatePopMenu(void) 来创建您想绑定的菜单, 再用 InsertMenu 或者 AppendMenu 来构造该菜单, 之后通过把标志位设置为 MF_POPUP, 调用 InsertMenu 或者 AppendMenu 来将该子菜单插入或者附加到主菜单上 在这种情况下,uIDNewItem 包含的是子菜单的句柄, 而 lpnewitem 包含的是显示在菜单项里的字符串 有许多函数可以让您查询或者操纵菜单项, 完成增加 / 去除选择标记 有效 / 无效菜单项等 用函数 EnableMenuItem 来使菜单项有效 / 失效 其函数原型如下 :BOOL EnableMenuItem (HMENU hmenu, UINT uidenableitem, UINT uenable); uenable 中使用的标志和其它菜单函数中用的标志类似 在 Windows CE 中, 使用 MF_GRAYED 而不是 MF_DISABLED 标志来使菜单项失效 用函数 CheckMenuItem 来选择菜单项 / 去除选择 其函数原型如下 :DWORD CheckMenuItem (HMENU hmenu, UINT uidcheckitem, UINT ucheck); 还有许多其它函数可以用来查询和操纵菜单项 要获取更多细节, 请参阅 SDK 文档 下面的代码片段创建了一个简单的菜单结构 : hmainmenu = CreateMenu (); hmenu = CreatePopupMenu (); AppendMenu (hmenu, MF_STRING MF_ENABLED, 100, TEXT ("&New")); AppendMenu (hmenu, MF_STRING MF_ENABLED, 101, TEXT ("&Open")); AppendMenu (hmenu, MF_STRING MF_ENABLED, 101, TEXT ("&Save")); AppendMenu (hmenu, MF_STRING MF_ENABLED, 101, TEXT ("E&xit")); AppendMenu (hmainmenu, MF_STRING MF_ENABLED MF_POPUP, (UINT)hMenu, TEXT ("&File")); hmenu = CreatePopupMenu (); AppendMenu (hmenu, MF_STRING MF_ENABLED, 100, TEXT ("C&ut"));
AppendMenu (hmenu, MF_STRING MF_ENABLED, 101, TEXT ("&Copy")); AppendMenu (hmenu, MF_STRING MF_ENABLED, 101, TEXT ("&Paste")); AppendMenu (hmainmenu, MF_STRING MF_ENABLED MF_POPUP, (UINT)hMenu, TEXT ("&Edit")); hmenu = CreatePopupMenu (); AppendMenu (hmenu, MF_STRING MF_ENABLED, 100, TEXT ("&About")); AppendMenu (hmainmenu, MF_STRING MF_ENABLED MF_POPUP, (UINT)hMenu, TEXT ("&Help")); 一旦创建完菜单, 就可以使用 TrackPopupMenu 来显示它 函数原型如下 : BOOL TrackPopupMenu (HMENU hmenu, UINT uflags, int x, int y, HWND hwnd, LPTPMPARAMS lptpm); 第一个参数是菜单句柄 根据位置参数 x 和 y, 标志位 uflags 用来设置菜单的对齐方式, 其中一个标志 TPM_RETURNCMD, 会让函数返回被选择的菜单项的 ID, 而不是发送一个 WM_COMMAND 消息 hwnd 是接收所有与菜单相关消息的窗口句柄, 其中也包括用户选择菜单项时产生的 WM_COMMAND 消息 最后一个参数 lptpm, 是指向 TPMPARAMS 结构的指针, 该结构包含一个尺寸值和一个矩形结构 矩形结构定义了菜单不能覆盖的屏幕矩形 如果没有要排除的矩形, 则该参数可以是 null 处理菜单命令当用户选择了一个菜单项,Windows 会向拥有该菜单的窗口发送 WM_COMMAND 消息 wparam 的低字位包含被选择的菜单项的 ID 其高字位则包含通知码, 对 菜单选择 这个动作来说, 该值总是 0 由选择菜单引发的 WM_COMMAND 消息, 其 lparam 是 0 所以为了响应一个菜单选择动作, 窗口需要回应 WM_COMMAND 消息, 解析出传入的 ID, 并根据选择的菜单项进行响应 既然我已经涉及了菜单创建的基本过程, 您可能想知道创建菜单的代码在 Windows 程序的哪里 我的回答是, 它不存在 除了在运行中动态创建菜单外, 大部分 Windows 程序都是简单的从资源中装载一个菜单模板 要了解更多关于资源的知识, 就让我们用本章剩余的部分来看一看资源吧 Windows CE 程序设计 (3rd 版 )--4.5 资源翻译 tellmenow 资源资源是应用程序或 DLL 的一个只读数据段, 在模块被编译后, 资源被链接到模块中 资源为开发者提供了一个与编译器无关的位置, 用来存储常量数据, 例如对话框 字符串 位图 图标以及菜单 因为资源并不编译在程序里, 所以改变它们并不用重新编译程序 您可以通过构造一个描述资源的的 ASCII 文件 -- 资源脚本 -- 来创建资源 ASCII 文件的扩展名叫 RC 您可以用资源编译器来编译该文件, 每个 Windows 开发工具的制造者都提供该编译器 随后您可以用链接器把该文件链接到编译后的可执行文件中 现在, 这些步骤被厚厚的可视工具
层掩盖了, 但功能依然保持相同 例如, 即使很少还有程序员会直接看资源文件, 但 evc++ 依然创建和维护一个 ASCII 资源文件 (RC) 对编程书籍的作者来说, 决定如何选择工具是一件很痛苦的事情 一些工具侧重于高级指令, 谈论菜单选择, 描述对话框 另一些则使用 ASCII 文件和命令行编译器, 为读者展示如何从头开始构建程序的所有组件 我可以描述如何使用可视化工具或者如何为资源创建基本的 ASCII 文件 在本书中, 我着重在 ASCII 资源脚本级别, 因为目标是讲授 Windows CE 编程, 而不是如何使用特殊的工具集 我还将演示如何通过创建和使用 ASCII RC 文件来增加菜单等 但在本书后面与资源文件不相关的地方, 我不会总是将 RC 文件包含在清单里的 当然, 这些文件会包含在附书的 CD 里 资源脚本创建资源脚本同使用记事本创建一个文本文件一样容易 使用的语言简单, 有些像 C 语言 使用 来标记注释行, 使用 #include 来包含文件 菜单模板示例如下 : A menu template ID_MENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open...", 100 MENUITEM "&Save...", 101 MENUITEM SEPARATOR MENUITEM "E&xit", 120 END POPUP "&Help" BEGIN MENUITEM "&About", 200 END END 开头的 ID_MENU 是资源的 ID 值 作为可替代的方式, 该 ID 值可以用字符串来标记资源 用 ID 的方式可以提供更紧凑的代码, 而使用字符串方式则可以在应用程序从源文件里装载资源的时候提供更好的可读性 接下来的关键字 MENU 则标记了资源的类型 菜单用 POPUP 作为开始, 指出该菜单项 File 实际上是一个绑定到主菜单的弹出式级联菜单 因为式菜单里的菜单, 所以它也使用 BEGIN 和 END 关键字来包围对 File 菜单的描述信息 字符 & 告诉 Windows 下一个字符是菜单项的键盘快捷符 在菜单项被显示的时候, 其后的字符会被 Windows 自动加下划线 当用户按下 Alt 键和该字符, 菜单项会被选择 菜单里的每一项被关键字 MENUITEM 标记, 后面跟随的是用在菜单中的字符串 在 Open 和 Save 字符串后的省略号是 Windows 界面习惯, 用来告诉用户选择该项会显示一个对话框 Open, Save, Exit 和 About 菜单项后的数字是菜单 ID 值 这些值用在 WM_COMMAND 消息中来标记菜单项 用定义在通用包含文件里的值替代这些值, 使它们和 WM_COMMAND 处理代码中的值一致, 这是一个好的编程习惯
表 4-2 列出了您可以在资源文件中找到其它资源类型 DISCARDABLE 关键字是可选的, 用来告诉 Windows 如果没有足够的内存时, 这些资源可以从内存中丢弃 剩下的是 BEGIN 和 END 关键字, 使用括号 { 也是可以的 表 4-2: 资源编译器支持的资源类型 Resource Type Explanation MENU Defines a menu ACCELERATORS Defines a keyboard accelerator table DIALOG Defines a dialog box template BITMAP Includes a bitmap file as a resource ICON Includes an icon file as a resource FONT Includes a font file as a resource RCDATA Defines application-defined binary data block STRINGTABLE Defines a list of strings VERSIONINFO Includes file version information [*] The SHMENUBAR resource type used by the Pocket PC is actually defined as RCDATA inside a wizardgenerated include file. 图标既然我们在使用资源文件, 那么修改 Windows CE Shell 用来显示程序的图标就是一件很容易的事情 使用您喜欢的图标编辑器创建图标, 并用以下语句加到资源文件 :ID_ICON "iconname.ico" 当在 Windows Explorer 中显示程序的时候,Windows 在 EXE 文件中查找, 从资源列表中找到第一个图标来代表该程序 让图标代表应用程序的窗口可能略微有些烦琐 Windows CE 用工具条上一个小的 16*16 像素的图标代表左面上的窗口 在桌面版的 Windows 里, 可以用 RegisterClassEx 函数将小图标和窗口绑定到一起, 但在 Windows CE 里不支持该函数 作为替代, 图标必须显式装载和分配给窗口 下面的代码片段为窗口分派了一个小图标 hicon = (HICON) SendMessage (hwnd, WM_GETICON, FALSE, 0); if (hicon == 0) {
hicon = LoadImage (hinst, MAKEINTRESOURCE (ID_ICON1), IMAGE_ICON, 16, 16, 0); SendMessage (hwnd, WM_SETICON, FALSE, (LPARAM)hIcon); 第一个 SendMessage 调用是用来获取窗口当前分配的图标 wparam 中的 FALSE 值指出要查询窗口的小图标 如果返回 0, 表示没有图标分配过, 就调用 LoadImage 函数从应用程序资源中装载一个图标 LoadImage 函数可以用文本字符串或者 ID 值来标记要装载的资源 在当前情况下,MAKEINTRESOURCE 宏用来为函数生成一个 ID 值 在 Windows CE 中, 被装载的图标必须是 16*16 的图标 LoadImage 不会把图标调整到要求的尺寸 在 Windows CE 下,LoadImage 只限于从资源中装载图标和位图 Windows CE 另外提供了 SHLoadDIBitmap 函数用于从文件中装载位图 不同于其它版本的 Windows,Windows CE 将窗口图标存储在一个基础类里 所以如果在一个应用程序里的两个窗口具有同样的类, 它们将使用相同的窗口图标 这里有一个忠告 : 窗口类是针对应用程序特定实例的 如果您有应用程序 FOOBAR 的两个不同实例, 它们有不同的窗口类, 所以它们可能有不同的窗口图标, 即使它们使用相同的类信息进行的注册 如果 FOOBAR 的第二个实例有两个同类的窗口被打开, 这两个窗口会使用同样的图标, 同 FOOBAR 第一个实例中的窗口图标无关 加速键可以装载的另一种资源是键盘加速键表 通过使用该表,Windows 允许开发者为应用程序中的特定菜单或控键指定快捷键 具体来说, 键组合用来产生发送到窗口的 WM_COMMAND 消息, 而加速键就是为键组合提供了一个直接的方法 这些加速键不同于那些用来访问 File 菜单的类似 Alt-F 的键组合 只要 File 菜单项字符串中使用 "&" 字符, 例如 "&File",File 菜单键组合就会被自动处理 虽然通常键盘加速键被用来模拟菜单操作, 例如使用 Ctrl-o 来打开一个文件, 但键盘加速键是独立于菜单或者其它控件的 下面是一个简短的资源脚本, 定义了一对加速键 ID_ACCEL ACCELERATORS DISCARDABLE BEGIN "N", IDM_NEWGAME, VIRTKEY, CONTROL "Z", IDM_UNDO, VIRTKEY, CONTROL END 和菜单资源一样, 该结构以一个 ID 值做为开始 该 ID 值后面是资源的类型, 随后是可选的 discardable 关键字 加速键表的入口项包含标记加速键的字母, 其后是命令 ID 值,VIRTKEY 表示字母实际上是虚拟键值,CONTROL 关键字表示需要 Ctrl 键与加速键一起按 只是在资源文件中有加速键表还远远不够 应用程序必须装载加速键表 对从消息队列中取出的每个消息, 应用程序还要检查是否有键入了加速键 幸运地是, 通过简单修改主消息循环就可以完成这些 下面是一个修改后的处理键盘加速键的主消息循环 Load accelerator table. haccel = LoadAccelerators (hinst, MAKEINTRESOURCE (ID_ACCEL)); Application message loop while (GetMessage (&msg, NULL, 0, 0)) {
Translate accelerators if (!TranslateAccelerator (hwndmain, haccel, &msg)) { TranslateMessage (&msg); DispatchMessage (&msg); 主消息循环中的第一个不同点是使用 LoadAccelerators 函数装载加速键表 之后, 每个消息从消息队列中取出后, 都调用 TranslateAccelerator 如果该函数转换了消息, 返回值是 TRUE, 这将跳过标准的 TranslateMessage 和 DispatchMessage 循环体 如果没有转换, 循环体就正常执行 位图也可以被存储为资源 Windows CE 中使用位图资源会略微不同于 Windows 的其它版本 在 Windows CE 里, 调用 LoadBitmap 来装载位图的只读版本 这意味着当位图被选择进设备环境变量后, 该图象就不能被这个 DC 里的其它绘制操作所修改 该函数原型如下 : BITMAP LoadBitmap(HINSTANCE hinstance, LPCTSTR lpbitmapname); 要装载一个位图资源的可读写版, 可以使用 LoadImage 函数 字符串使用字符串资源, 将和语言相关的信息放到编译的代码之外, 是一种减少应用程序内存使用的好方法 应用程序可以调用 int LoadString(HINSTANCE hinstance, UINT uid, LPTSTR lpbuffer, int nbuffermax) 来从资源中装载字符串 uid 是字符串资源的 ID,lpBuffer 指向接收字符串的缓冲区,nBufferMax 是缓冲区的大小 在 Windows CE 里, 为了节省内存, LoadString 有一个新特性 如果 lpbuffer 是 NULL,LoadString 返回一个指向字符串的只读的指针作为返回值 可以简单地把返回值转换成一个指针, 并在需要的时候使用该字符串 字符串的长度位于字符串起始位置之前的一个字里 需要注意的是, 默认情况下, 资源编译器从字符串资源中去除结尾的 0 如果您想直接读取字符串资源, 并让它们以 0 做终结符, 那么您可以使用 -r 命令行开关来激活资源编译器 虽然我将在第七章涉及为了节约内存而采取的内存管理和策略, 但是这里还是有个快速提醒 : 从资源文件中装载许多字符串到内存里并不是一个好的方法 这将使用资源和 RAM 里的内存 如果您同时需要很多字符串, 使用 LoadString 的新特性返回一个直接指向资源自身的指针会是一个比较好的策略 作为替换, 您可以用编译到程序只读段里的字符串 您失去了单独字符串表的优势, 但您减少了内存使用量 Windows CE 程序设计 (3rd 版 )--4.6 DOIView 示例程序翻译 tellmenow 在下面的 DOIView 示例程序中, 演示了资源 键盘加速键和弹出式菜单的用法 DOIView 是 Declaration of Independence View 的缩写, 显示了美国独立宣言 程序中的文本被存储为一系列字符串资源 DOIView 将文本格式化来适应程序窗口, 并使用滚动条来滚动文本 图 4-3 显示了 DOIView 窗口 按 Ctr-H 和 Ctr-E 可以将文档滚动到开头和结尾 您也可以在窗口上点压来显示一个快捷菜单, 用来快速滚动到文档开头 结尾以及结束程序 图 4-3( 略 ): 显示快捷菜单的 DOI View 窗口 DOIView 的源文件显示在清单 4-2 中 请注意第 3 个包含文件 DOIView.rc, 它包含了程序的资源脚本 DOIView.rc 包含有菜单资源, 包含一行程序使用的图标, 还包含一个字符串表, 里
面是用来显示的文字 因为字符串资源被限制为 4092 个字符, 所以文字被包含在多个字符串里 清单 4-2:DOIView 程序 DOIView.rc ================================================= ===================== DOIView - Resource file Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include "DOIView.h" ---------------------------------------------------------------------- Icon ID_ICON ICON "DOIView.ico" ---------------------------------------------------------------------- Menu ID_MENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Goto Start\tCtrl-H", IDM_HOME MENUITEM "&Goto End\tCtrl-E", IDM_END MENUITEM SEPARATOR MENUITEM "E&xit", IDM_EXIT END END ---------------------------------------------------------------------- Accelerator table ID_ACCEL ACCELERATORS DISCARDABLE BEGIN "H", IDM_HOME, VIRTKEY, CONTROL "E", IDM_END, VIRTKEY, CONTROL END ---------------------------------------------------------------------- String table
STRINGTABLE DISCARDABLE BEGIN IDS_DOITEXT, "IN CONGRESS, July 4, 1776.\012The unanimous \ Declaration of the thirteen united States of America,\012WHEN in the \ Course of human Events, it becomes necessary for one People to \ dissolve the Political Bands which have connected them with another, \ and to assume among the Powers of the Earth, the separate and equal \ Station to which the Laws of Nature and of Nature's God entitle them, \ a decent Respect to the Opinions of Mankind requires that they should \ declare the causes which impel them to the Separation.\012\ WE hold these Truths to be self-evident, that all Men are created \ equal, that they are endowed by their Creator with certain \ unalienable Rights, that among these are Life, Liberty and the Pursuit \ of Happiness -- That to secure these Rights, Governments are \ instituted among Men, deriving their just Powers from the Consent of \ the Governed, that whenever any Form of Government becomes destructive \ of these Ends, it is the Right of the People to alter or to abolish \ it, and to institute new Government, laying its Foundation on such \ Principles, and organizing its Powers in such Form, as to them shall \ seem most likely to effect their Safety and Happiness. Prudence, \ indeed, will dictate that Governments long established should \ not be changed for light and transient Causes; and accordingly all \ Experience hath shewn, that Mankind are more disposed to suffer, while \ Evils are sufferable, than to right themselves by abolishing the Forms \ to which they are accustomed. But when a long Train of Abuses and \ Usurpations, pursuing invariably the same Object, evinces a Design to \ reduce them under absolute Despotism, it is their Right, it is their \ Duty, to throw off such Government, and to provide new Guards for \ their future Security. Such has been the patient Sufferance of these \ Colonies; and \ such is now the Necessity which constrains them to alter their \ former Systems of Government. The History of the present King of Great \ Britain is a History of repeated Injuries and Usurpations, all having \ in direct Object the Establishment of an absolute Tyranny over these \ States. To prove this, let Facts be submitted to a candid World.\012\ HE has refused his Assent to Laws, the most wholesome and \ necessary for the public Good.\012HE has forbidden his Governors to \ pass Laws of immediate and pressing Importance, unless suspended in \ their Operation till his Assent should be obtained; and when so \ suspended, he has utterly neglected to attend to them.\012\ HE has refused to pass other Laws for the Accommodation of large \ Districts of People, unless those People would relinquish the Right of \ Representation in the Legislature, a Right inestimable to them, and \ formidable to Tyrants only.\012he has called together Legislative \
Bodies at Places unusual, uncomfortable, and distant from the \ Depository of their public Records, for the sole Purpose of fatiguing \ them into Compliance with his Measures.\012\ HE has dissolved Representative Houses repeatedly, for opposing \ with manly Firmness his Invasions on the Rights of the People.\012HE \ has refused for a long Time, after such Dissolutions, to cause others \ to be elected; whereby the Legislative Powers, incapable of the \ Annihilation, have returned to the People at large for their exercise; \ the State remaining in the mean time exposed to all the Dangers of \ Invasion from without, and the Convulsions within.\012\ HE has endeavoured to prevent the Population of these States; \ for that Purpose obstructing the Laws for Naturalization of Foreigners\ ; refusing to pass others to encourage their Migrations hither, and \ raising the Conditions of new Appropriations of Lands.\012HE has \ obstructed the Administration of Justice, by refusing his Assent to \ Laws for establishing Judiciary Powers.\012HE has made Judges \ dependent on his Will alone, for the Tenure of their Offices, and the \ Amount and Payment of their Salaries.\012" IDS_DOITEXT1, "HE has erected a Multitude of new Offices, and sent \ hither Swarms of Officers to harrass our People, and eat out their \ Substance.\012HE has kept among us, in Times of Peace, Standing \ Armies, without the consent of our Legislatures.\012HE has affected to \ render the Military independent of and superior to the Civil Power.\012\ HE has combined with others to subject us to a Jurisdiction \ foreign to our Constitution, and unacknowledged by our Laws; giving \ his Assent to their Acts of pretended Legislation:\012FOR quartering \ large Bodies of Armed Troops among us;\012for protecting them, by a \ mock Trial, from Punishment for any Murders which they should commit \ on the Inhabitants of these States:\012FOR cutting off our Trade with \ all Parts of the World:\012\ FOR imposing Taxes on us without our Consent:\012FOR depriving \ us, in many Cases, of the Benefits of Trial by Jury:\012FOR \ transporting us beyond Seas to be tried for pretended Offences:\012\ FOR abolishing the free System of English Laws in a neighbouring \ Province, establishing therein an arbitrary Government, and enlarging \ its Boundaries, so as to render it at once an Example and fit \ Instrument for introducing the same absolute Rules into these \ Colonies:\012\ FOR taking away our Charters, abolishing our most valuable Laws, \ and altering fundamentally the Forms of our Governments:\012FOR \ suspending our own Legislatures, and declaring themselves invested \ with Power to legislate for us in all Cases whatsoever.\012he has \ abdicated Government here, by declaring us out of his Protection and \
waging War against us.\012he has plundered our Seas, ravaged our \ Coasts, burnt our Towns, and destroyed the Lives of our People.\012\ HE is, at this Time, transporting large Armies of foreign \ Mercenaries to compleat the Works of Death, Desolation, and Tyranny, \ already begun with circumstances of Cruelty and Perfidy, scarcely \ paralleled in the most barbarous Ages, and totally unworthy the Head \ of a civilized Nation.\012HE has constrained our fellow Citizens taken \ Captive on the high Seas to bear Arms against their Country, to become \ the Executioners of their Friends and Brethren, or to fall themselves \ by their Hands.\012\ HE has excited domestic Insurrections amongst us, and has \ endeavoured to bring on the Inhabitants of our Frontiers, the \ merciless Indian Savages, whose known Rule of Warfare, is an \ undistinguished Destruction, of all Ages, Sexes and Conditions.\012IN \ every stage of these Oppressions we have Petitioned for Redress in the \ most humble Terms: Our repeated Petitions have been answered only by \ repeated Injury. A Prince, whose Character is thus marked by every act \ which may define a Tyrant, is unfit to be the Ruler of a free People. \ NOR have we been wanting in Attentions to our Brittish Brethren. \ We have warned them from Time to Time of Attempts by their Legislature \ to extend an unwarrantable Jurisdiction over us. We have reminded them \ of the Circumstances of our Emigration and Settlement here. We have \ appealed to their native Justice and Magnanimity, and we have conjured \ them by the Ties of our common Kindred to disavow these Usurpations, \ which, would inevitably interrupt our Connections and Correspondence. \ They too have been deaf to the Voice of Justice and of Consanguinity. \ We must, therefore, acquiesce in the Necessity, which denounces our \ Separation, and hold them, as we hold the rest of Mankind, Enemies in \ War, in Peace, Friends.\012" IDS_DOITEXT2, "WE, therefore, the Representatives of the UNITED \ STATES OF AMERICA, in GENERAL CONGRESS, Assembled, appealing to the \ Supreme Judge of the World for the Rectitude of our Intentions, do, in \ the Name, and by Authority of the good People of these Colonies, \ solemnly Publish and Declare, That these United Colonies are, and of \ Right ought to be, FREE AND INDEPENDENT STATES; that they are absolved \ from all Allegiance to the British Crown, and that all political \ Connection between them and the State of Great-Britain, is and ought \ to be totally dissolved; and that as FREE AND INDEPENDENT STATES, they \ have full Power to levy War, conclude Peace, contract Alliances, \ establish Commerce, and to do all other Acts and Things which \ INDEPENDENT STATES may of right do. And for the support of this \ Declaration, with a firm Reliance on the Protection of divine \ Providence, we mutually pledge to each other our Lives, our Fortunes, \
and our sacred Honor." END DOIView.h ================================================= ===================== Header file Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= =============== Returns number of elements #define dim(x) (sizeof(x) / sizeof(x[0])) ---------------------------------------------------------------------- Generic defines and data types struct decodeuint { Structure associates UINT Code; messages with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); ; struct decodecmd { Structure associates UINT Code; menu IDs with a LRESULT (*Fxn)(HWND, WORD, HWND, WORD); function ; #define ID_MENU 10 #define ID_ACCEL 11 #define IDM_HOME 100 #define IDM_END 101 #define IDM_EXIT 102 #define IDS_DOITEXT 1000 These IDs must be #define IDS_DOITEXT1 1001 consecutive #define IDS_DOITEXT2 1002 ---------------------------------------------------------------------- Function prototypes LPTSTR WrapString (HDC hdc, LPTSTR psztext, int *pnlen, int nwidth, BOOL *feol); HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int); Window procedures LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM); Message handlers LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoSizeMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoCommandMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoLButtonDownMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoRButtonUpMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoVScrollMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM); Command functions LPARAM DoMainCommandHome (HWND, WORD, HWND, WORD); LPARAM DoMainCommandEnd (HWND, WORD, HWND, WORD); LPARAM DoMainCommandExit (HWND, WORD, HWND, WORD); DOIView.cpp ================================================= ===================== DOIView - Demonstrates window scroll bars Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include "DOIView.h" Program-specific stuff #include <aygshell.h> Extended shell API ---------------------------------------------------------------------- Global data const TCHAR szappname[] = TEXT("DOIView"); HINSTANCE hinst; Program instance handle #define WM_MYMSG (WM_USER + 100) Message dispatch table for MainWindowProc const struct decodeuint MainMessages[] = { WM_CREATE, DoCreateMain, WM_SIZE, DoSizeMain,
WM_LBUTTONDOWN, DoLButtonDownMain, WM_COMMAND, DoCommandMain, WM_VSCROLL, DoVScrollMain, WM_PAINT, DoPaintMain, WM_DESTROY, DoDestroyMain, ; Command Message dispatch for MainWindowProc const struct decodecmd MainCommandItems[] = { IDM_HOME, DoMainCommandHome, IDM_END, DoMainCommandEnd, IDM_EXIT, DoMainCommandExit, ; typedef struct { LPTSTR pszline; int nlen; LINEARRAY, *PLINEARRAY; #define MAXLINES 1000 LINEARRAY latext[maxlines]; int nnumlines = 0; int nfontheight = 1; int nlinesperpage = 1; LPTSTR pszdeclaration; HFONT hfont; int nvpos, nvmax; BOOL ffirst = TRUE; ================================================= ===================== Program entry point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { MSG msg; int rc = 0; HWND hwndmain; HACCEL haccel; Initialize this instance. hwndmain = InitInstance (hinstance, lpcmdline, ncmdshow); if (hwndmain == 0) return 0x10;
Load accelerator table. haccel = LoadAccelerators (hinst, MAKEINTRESOURCE (ID_ACCEL)); Application message loop while (GetMessage (&msg, NULL, 0, 0)) { Translate accelerators if (!TranslateAccelerator (hwndmain, haccel, &msg)) { TranslateMessage (&msg); DispatchMessage (&msg); Instance cleanup return TermInstance (hinstance, msg.wparam); ---------------------------------------------------------------------- InitInstance - Instance initialization HWND InitInstance (HINSTANCE hinstance, LPWSTR lpcmdline, int ncmdshow) { WNDCLASS wc; HWND hwnd; PBYTE pres, pbuff; int nstrlen = 0, i = 0; Save program instance handle in global variable. hinst = hinstance; #if defined(win32_platform_pspc) If Pocket PC, only allow one instance of the application hwnd = FindWindow (szappname, NULL); if (hwnd) { SetForegroundWindow ((HWND)(((DWORD)hWnd) 0x01)); return 0; #endif Load text from multiple string resources into one large buffer pbuff = (PBYTE)LocalAlloc (LPTR, 8); while (pres = (PBYTE)LoadString (hinst, IDS_DOITEXT + i++, NULL, 0)) { Get the length of the string resource int nlen = *(PWORD)(pRes-2) * sizeof (TCHAR); Resize buffer pbuff = (PBYTE)LocalReAlloc (pbuff, nstrlen + 8 + nlen,
LMEM_MOVEABLE LMEM_ZEROINIT); if (pbuff == NULL) return 0; Copy resource into buffer memcpy (pbuff + nstrlen, pres, nlen); nstrlen += nlen; *(TCHAR *)(pbuff + nstrlen) = TEXT ('\0'); pszdeclaration = (LPTSTR)pBuff; Register application main window class. wc.style = 0; Window style wc.lpfnwndproc = MainWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = szappname; Window class name if (RegisterClass (&wc) == 0) return 0; Create main window. hwnd = CreateWindowEx (WS_EX_NODRAG, szappname, TEXT("The Declaration of Independence"), WS_VSCROLL WS_VISIBLE WS_CAPTION WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL); if (!IsWindow (hwnd)) return 0; Fail code if not created. Standard show and update calls ShowWindow (hwnd, ncmdshow); UpdateWindow (hwnd); return hwnd; ---------------------------------------------------------------------- TermInstance - Program cleanup int TermInstance (HINSTANCE hinstance, int ndefrc) { LocalFree (pszdeclaration);
return ndefrc; ================================================= ===================== Message handling procedures for main window ---------------------------------------------------------------------- MainWndProc - Callback function for application window LRESULT CALLBACK MainWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(mainmessages); i++) { if (wmsg == MainMessages[i].Code) return (*MainMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoCreateMain - Process WM_CREATE message for window. LRESULT DoCreateMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { TEXTMETRIC tm; HDC hdc = GetDC (hwnd); LOGFONT lf; HFONT hfontwnd; hfontwnd = (HFONT)GetStockObject (SYSTEM_FONT); GetObject (hfontwnd, sizeof (LOGFONT), &lf); lf.lfheight = -12 * GetDeviceCaps(hdc, LOGPIXELSY)/ 72; lf.lfweight = 0; hfont = CreateFontIndirect (&lf); SendMessage (hwnd, WM_SETFONT, (WPARAM)hFont, 0); Get the height of the default font. hfontwnd = (HFONT)SelectObject (hdc, hfont); GetTextMetrics (hdc, &tm); nfontheight = tm.tmheight + tm.tmexternalleading;
SelectObject (hdc, hfontwnd); ReleaseDC (hwnd, hdc); return 0; ---------------------------------------------------------------------- DoSizeMain - Process WM_SIZE message for window. LRESULT DoSizeMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { RECT rect; HDC hdc = GetDC (hwnd); GetClientRect (hwnd, &rect); int i = 0, nchars, nwidth; LPTSTR pszwndtext = pszdeclaration; SCROLLINFO si; HFONT hfontwnd; BOOL fnewline; hfontwnd = (HFONT)SelectObject (hdc, hfont); Compute the line breaks nwidth = rect.right - rect.left - 10; while (i < MAXLINES){ pszwndtext = WrapString (hdc, pszwndtext, &nchars, nwidth, &fnewline); if (pszwndtext == 0) break; latext[i].pszline = pszwndtext; latext[i].nlen = nchars; i++; if (fnewline) { latext[i].nlen = 0; i++; pszwndtext += nchars; nnumlines = i; nlinesperpage = (rect.bottom - rect.top)/nfontheight; Compute lines per window and total lenght si.cbsize = sizeof (si); si.nmin = 0; si.nmax = nnumlines;
si.npage = nlinesperpage; si.npos = nvpos; si.fmask = SIF_ALL; SetScrollInfo (hwnd, SB_VERT, &si, TRUE); Clean up SelectObject (hdc, hfontwnd); ReleaseDC (hwnd, hdc); InvalidateRect (hwnd, NULL, TRUE); return 0; ---------------------------------------------------------------------- DoCommandMain - Process WM_COMMAND message for window. LRESULT DoCommandMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { WORD iditem, wnotifycode; HWND hwndctl; int i; Parse the parameters. iditem = (WORD) LOWORD (wparam); wnotifycode = (WORD) HIWORD(wParam); hwndctl = (HWND) lparam; Call routine to handle control message. for (i = 0; i < dim(maincommanditems); i++) { if (iditem == MainCommandItems[i].Code) return (*MainCommandItems[i].Fxn)(hWnd, iditem, hwndctl, wnotifycode); return 0; ---------------------------------------------------------------------- DoLButtonDownMain - Process WM_LBUTTONDOWN message for window. LRESULT DoLButtonDownMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { HMENU hmenumain, hmenu; POINT pt; int rc; Display the menu at the point of the tap
pt.x = LOWORD (lparam); pt.y = HIWORD (lparam); SHRGINFO sri; sri.cbsize = sizeof (sri); sri.dwflags = 1; sri.hwndclient = hwnd; sri.ptdown = pt; See if tap and hold rc = SHRecognizeGesture (&sri); if (rc == 0) return 0; Display the menu at the point of the tap First, convert to desktop coordinates MapWindowPoints (hwnd, HWND_DESKTOP, &pt, 1); pt.x += 5; hmenumain = LoadMenu (hinst, MAKEINTRESOURCE (ID_MENU)); hmenu = GetSubMenu (hmenumain, 0); TPMPARAMS tpm; tpm.cbsize = sizeof (tpm); GetClientRect (hwnd, &tpm.rcexclude); TrackPopupMenuEx (hmenu, TPM_LEFTALIGN TPM_TOPALIGN, pt.x, pt.y, hwnd, &tpm); DestroyMenu (hmenumain); DestroyMenu (hmenu); return 0; ---------------------------------------------------------------------- DoVScrollMain - Process WM_VSCROLL message for window. LRESULT DoVScrollMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { RECT rect; SCROLLINFO si; int soldpos = nvpos; GetClientRect (hwnd, &rect); switch (LOWORD (wparam)) { case SB_LINEUP: nvpos -= 1; break;
case SB_LINEDOWN: nvpos += 1; break; case SB_PAGEUP: nvpos -= nlinesperpage; break; case SB_PAGEDOWN: nvpos += nlinesperpage; break; case SB_THUMBTRACK: case SB_THUMBPOSITION: nvpos = HIWORD (wparam); break; Check range. if (nvpos < 0) nvpos = 0; if (nvpos > nnumlines-1) nvpos = nnumlines-1; If scroll position changed, update scrollbar and force redraw of window. if (nvpos!= soldpos) { si.cbsize = sizeof (si); si.npos = nvpos; si.fmask = SIF_POS; SetScrollInfo (hwnd, SB_VERT, &si, TRUE); InvalidateRect (hwnd, NULL, TRUE); return 0; ---------------------------------------------------------------------- DoPaintMain - Process WM_PAINT message for window. LRESULT DoPaintMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PAINTSTRUCT ps; HFONT hfontwnd; RECT rect; HDC hdc;
int i, y = 5; hdc = BeginPaint (hwnd, &ps); GetClientRect (hwnd, &rect); hfontwnd = (HFONT)SelectObject (hdc, hfont); Draw the text for (i = nvpos; i < nnumlines; i++) { if (y > rect.bottom - nfontheight - 10) break; if (latext[i].nlen) ExtTextOut (hdc, 5, y, TRANSPARENT, NULL, latext[i].pszline, latext[i].nlen, NULL); y += nfontheight; SelectObject (hdc, hfontwnd); EndPaint (hwnd, &ps); return 0; ---------------------------------------------------------------------- DoDestroyMain - Process WM_DESTROY message for window. LRESULT DoDestroyMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PostQuitMessage (0); return 0; ================================================= ===================== Command handler routines ---------------------------------------------------------------------- DoMainCommandHome - Process Program Home command. LPARAM DoMainCommandHome (HWND hwnd, WORD iditem, HWND hwndctl, WORD wnotifycode) { SCROLLINFO si; if (nvpos!= 0) { nvpos = 0; si.cbsize = sizeof (si); si.npos = nvpos;
si.fmask = SIF_POS; SetScrollInfo (hwnd, SB_VERT, &si, TRUE); InvalidateRect (hwnd, NULL, TRUE); return 0; ---------------------------------------------------------------------- DoMainCommandEnd - Process End command. LPARAM DoMainCommandEnd (HWND hwnd, WORD iditem, HWND hwndctl, WORD wnotifycode) { SCROLLINFO si; int nendpos = nnumlines - nlinesperpage + 1; if (nvpos!= nendpos) { nvpos = nendpos; si.cbsize = sizeof (si); si.npos = nvpos; si.fmask = SIF_POS; SetScrollInfo (hwnd, SB_VERT, &si, TRUE); InvalidateRect (hwnd, NULL, TRUE); return 0; ---------------------------------------------------------------------- DoMainCommandExit - Process Program Exit command. LPARAM DoMainCommandExit (HWND hwnd, WORD iditem, HWND hwndctl, WORD wnotifycode) { SendMessage (hwnd, WM_CLOSE, 0, 0); return 0; ---------------------------------------------------------------------- WrapString - Determine a length that will fit with a width LPTSTR WrapString (HDC hdc, LPTSTR psztext, int *pnlen, int nwidth, BOOL *feol) { LPTSTR pszstr, pszstart; SIZE Size;
*feol = FALSE; *pnlen = 0; Skip to first non-space char for (; (*psztext!=text('\0')) && (*psztext<=text (' ')); psztext++); pszstart = psztext; if (*psztext == 0) return 0; while (1) { pszstr = psztext; Find end of the next word for (; (*psztext!=text('\0')) && *psztext>text (' ');psztext++); Get length of the string GetTextExtentPoint (hdc, pszstart, psztext - pszstart, &Size); if (Size.cx > nwidth) break; if ((*psztext == TEXT ('\0')) (*psztext == TEXT ('\r')) (*psztext == TEXT ('\n'))) { *feol = TRUE; pszstr = psztext; break; slip past space psztext++; *pnlen = pszstr - pszstart; return pszstart; 程序启动的时候, 会将字符串资源读到一个大的缓冲区中 通过传递一个 NULL 缓冲区给 LoadString 函数来访问字符串资源, 可以减少对内存的影响 这将使 LoadString 返回一个指向资源的指针 注意, 在这种情况下, 字符串并不是以零结尾的, 所以 DOIView 从字符串之前的一个字里, 读取字符的个数 因为字符串是 Unicode, 字符串长度应该是再乘以 TCHAR 的大小, 由此得到字符串需要的缓冲区的尺寸 DOIView 的主窗口处理了一些额外消息 WM_SIZE 消息处理例程中通过调用 WrapString 重新格式化了文本 该例程调用了 GetTextExtentPoint 来计算每行的长度 如果长度小于窗口宽度, 例程就给该行增加一个单词, 并重新计算长度 这会一直持续到该行被加的单词长度达到窗口宽度时为止 WM_PAINT 消息处理例程则从当前滚动位置所定义的用来显示的文本行开始绘制
WM_VSCROLL 例程处理来自垂直滚动条的消息 当通知是 SB_PAGEUP 或者 SB_PAGEDOWN 的时候, 该例程会在当前滚动位置上减少或者增加要显示的行数 WM_LBUTTONDOWN 例程从菜单资源中装载一个菜单 该菜单有三个命令 :Home 表示滚动到文档起始位置 ;End 表示滚动到文档结尾 ;Exit 表示退出程序 DOIView 还响应加速键 : Ctrl-H 用于 Home,Ctrl-E 用于 End. 从基础的子窗口到控件以及资源和菜单, 本章函盖了大量的基础内容 我的目的不是教这些主题里的每个东西 相反, 我试图介绍每个程序元素, 提供一些示例, 并指出 Windows CE 和 Windows 桌面版本处理这些元素的细微差别 虽然 Windows 控件很有用并且相当方便, 但下一章节会涉及一些通用控件 这些控件是 Windows CE 也支持的功能更强大, 也更复杂的控件集 Windows CE 程序设计 (3rd 版 )-- 第 5 章公共控件和 Windows CE -- 概述翻译 tellmenow 概述随着微软 Windows 作为一个操作系统日渐成熟,Windows 提供的基本控件也是愈发不足以满足用户对复杂界面的需要 微软开发了一系列称为公共控件的附加控件, 用于其内部应用 随后又将这些公共控件包容到 DLL 中提供给开发者使用 从 Windows 95 和 NT3.5 开始, 公共控件库都是和操作系统捆绑到一起的 ( 即使如此, 当公共控件库增强的时候, 也不能阻止微软发布过渡版本的 DLL ) 伴随公共控件 DLL 的每次发布, 新控件和新特性被加到旧的控件中 作为一个群体, 公共控件同标准 Windows 控件比, 成熟度会低一些 因此在 Windows 的各个版本里, 公共控件在实现方面也表现出极大的差异 这些差异并不仅仅存在于 Windows CE 和 Windows 的其它版本之间, 还存在于 Windows Me,Windows 2000 和 XP 之间 Windows CE 中公共控件的功能更贴近于 Windows 98 中发布的公共控件, 尽管它还不是完全支持 Windows 98 中的特性 本章的目标并不是想深入地讲解所有的公共控件 这样会花费整本书地内容 相反, 我讲解 Windows CE 程序员编写 Windows CE 程序时最常需要的控件和特性 我将从命令条和菜单条控件开始, 随后讲一下日历和时间日期选择控件 最后, 我将用列表视图控件作为结尾 到本章结束地时候, 您可能不会里里外外地了解每个公共控件, 但您将能够理解公共控件大体上是如何工作的 并且您将具备浏览文档和理解未提到的公共控件的背景知识 5.1 公共控件编程翻译 tellmenow 因为公共控件同操作系统核心是分离的, 所以在使用任何一个公共控件前必须要初始化包含公共控件的 DLL 在所有 Windows 版本里, 也包括 Windows CE, 您可以调用 void InitCommonControls(void) 来装载动态库并注册许多公共控件类 该调用并不初始化日历控件 时间选择控件 up/down 控件 IP 地址控件以及其它更新一些的公共控件 要初始化这些控件, 使用函数 BOOL InitCommonControlsEx(LPINITCOMMONCONTROLSEX lpinitctrls); 该函数允许应用程序只装载和初始化选择的公共控件 该函数在 Windows CE 下很容易获得, 因为只装载需要的控件可以减少对内存的影响 该函数唯一的参数是一个含有两个域的结构, 该
结构有一个尺寸域和一个包含标志集的域, 标志集用来指出哪些公共控件需要被注册 表 5-1 给出了可以使用的标志及对应的控件 表 5-1: 公共控件对应的标志 Flag Control Classes Initialized ICC_BAR_CLASSES Toolbar Status bar Trackbar Command bar ICC_COOL_CLASSES Rebar ICC_DATE_CLASSES Date and time picker Month calendar control ICC_LISTVIEW_CLASSES List view Header control ICC_PROGRESS_CLASS Progress bar control ICC_TAB_CLASSES Tab control ICC_TREEVIEW_CLASSES Tree view control ICC_UPDOWN_CLASS Up-Down control ICC_TOOLTIP_CLASSES Tool tip control ICC_CAPEDIT_CLASS Cap edit control 一旦公共控件 DLL 被初始化, 这些公共控件就可以像其它任何控件一样对待了 每个控件都有一个可定制风格标志集, 用来配置控件的外观和行为 针对每个控件的消息会被发出, 用来配置和操纵控件并让控件执行某些动作 标准 Windows 控件和公共控件之间的一个主要差别是事件通知或服务请求是通过 WM_NOTIFY 消息来发出, 而标准控件则是通过 WM_COMMAND 消
息发出的 同通过 WM_COMMAND 消息发出的通知相比, 采用这种技术可以使通知能够包含更多的信息 另外, 这种技术允许为每个使用该通知的控件进行扩展和改编 WM_NOTIFY 消息 WM_NOTIFY 消息在 lparam 参数中携带着指向 NMHDR 结构的指针,NMHDR 定义如下 : typedef struct tagnmhdr { HWND hwndfrom; UINT idfrom; UINT code; NMHDR; hwndfrom 是发送通知消息的窗口句柄 对属性页来说, 就是属性页窗口 如果是控件发送通知的话,idFrom 就是控件 ID 最后一个 code 域包含的是通知码 同 WM_COMMAND 消息相比, 虽然这个基本结构没有包含任何更多的信息, 但它几乎总是可以扩展的, 可以使用附加域来扩展它 通知码指出有什么样的附加域附加到了该通知结构里 公共控件编程中另一个不同点是发给公共控件的大部分与控件相关的消息都有预定义的宏, 用这些宏来发送消息, 看上去像是应用程序在调用函数 所以不用像下面的语句那样使用 LVM_INSERTITEM 消息来给列表控件插入一个项, 如 nindex = (int) SendMessage (hwndlv, LVM_INSERTITEM, 0, (LPARAM)&lvi); 而是可以很容易地使用 nindex = ListView_InsertItem (hwndlv, &lvi) 即可 这两行语句没有功能上的差别 用宏地优势是清晰 宏和其它公共控件编程中需要的定义们一起都位于 CommCtrl.h 中 用这些宏的一个问题是编译器不能对参数执行类型检查, 而假如宏是真正的函数的话是本应该执行的 这个问题也存在于 SendMessage 技术中, 在 SendMessage 这种方式中参数必须是 WPARAM 和 LPARAM 类型, 但消息缺乏类型检查也是比较常见的 总的来说, 宏例程还是提供了更好的可读性 宏系统的一个例外是在命令条控件和命令带控件中进行宏调用的时候 在这些控件中, 除了有大量的用宏包装的消息外, 实际上还有许多真的函数 通常, 我所说的消息是真正的消息, 而不是它们对应的宏 这将有助于将消息或者宏同真正的函数区分开来 Windows CE 程序设计 (3rd 版 )--5.2 公共控件 ( 部分 ) 翻译 tellmenow Windows CE 的基本目标定位 -- 小型个人生产力工具 -- 在驱动着公共控件的需求 日程和任务管理应用程序中频繁用到时间和日期的需求导致在控件中包括了日期和时间选择控件以及日历控件 个人生产力工具的小屏幕促成了节省空间的命令条 命令条控件和用于 IE3.0 的 rebar 控件结合产生了命令带控件 命令带控件为位于 Windows CE 应用程序顶部的菜单 按钮和其它控件提供了更多的空间 最后是 Pocket PC 开发者所熟悉的菜单控件 在 Windows CE.NET 4.2 中, 它被加到了 Windows CE 公共控件中 我在讨论完命令条控件和命令带控件后, 会谈到菜单控件 命令条简单地讲, 命令条控件将菜单和工具条组合到了一起 这个组合是很有价值的, 因为菜单和工具条组合到一行中可以节省空间受限的 Windows CE 显示器的屏幕空间 对程序员来说, 命令条就像一个带有很多帮助函数的工具条, 使公共控件编程变的轻而易举 当您使用命令条的时候,
除了可以使用命令条函数外, 您还可以使用许多工具条消息 图 5-1 中展示了一个带有命令条的窗口 图 5-1( 略 ): 带有命令条的窗口 创建命令条您可以通过几步操作来创建命令条, 每步都由一个特别的函数来定义 先创建命令条, 再加入菜单 按钮 其它控件以及工具提示, 最后, 添加关闭和帮助按钮到命令条的右侧 通过调用 CommandBar_Create 函数来开始创建命令条, 该函数原型如下 : HWND CommandBar_Create (HINSTANCE hinst, HWND hwndparent, int idcmdbar); 函数需要程序的实例句柄, 父窗口句柄以及控件的 ID 如果创建成功, 函数返回新创建的命令条控件的句柄 对应用程序来说, 光秃秃的命令条并没有多少用处, 还需要加入菜单和一些按钮来使它变的有用 命令条菜单您可以通过调用下面两个函数之一来给命令条添加菜单 第一个函数是 BOOL CommandBar_InsertMenubar (HWND hwndcb, HINSTANCE hinst, WORD idmenu, int ibutton); 头两个参数是命令条句柄和应用程序的实例句柄 idmenu 是要被装载到命令条的菜单的资源 ID 最后一个参数是紧靠菜单左边的按钮的索引 因为 Windows CE 规范中指定菜单应该再命令条的左端, 所以该参数应该设置为 0, 表示所有按钮都在菜单右边 CommandBar_InsertMenubar 函数的缺点是它要求从资源中加载菜单 您不能在运行时配置菜单 当然, 通过装载一个虚拟菜单, 并使用不同的菜单函数来操作菜单的内容, 是可能达到这个目的的, 但这里有一个更简单的方法 : BOOL CommandBar_InsertMenubarEx (HWND hwndcb, HINSTANCE hinst, LPTSTR pszmenu, int ibutton); 函数 CommandBar_InsertMenubarEx 中, 除了第三个参数 pszmenu 外, 其它参数都和 CommandBar_InsertMenubar 中的类似 该参数要么是菜单资源的名字, 要么是程序先前创建的菜单的句柄 如果 pszmenu 是菜单句柄, 那么 hinst 参数应该是 NULL 一旦菜单被装载进命令条, 就可以在任何时候通过如下函数来被检索菜单句柄 : HMENU CommandBar_GetMenu (HWND hwndcb, int ibutton); 第二个参数 ibutton 是紧靠菜单左侧的按钮的索引 这种机制提供了在命令条上识别多个菜单的能力 然而, 根据给出的 WINDOWS CE 设计规范, 用户在命令条上只能看到一个菜单 有了菜单句柄, 您就可以有许多可用的菜单函数来操纵菜单的结构了 如果应用程序要修改命令条上的菜单, 应用程序必须调用 BOOL CommandBar_DrawMenuBar(HWND hwndcb,int ibutton); 它将强迫重绘命令条上的菜单 这里的参数又一次使用了命令条的句柄和菜单左侧按钮的索引 在 WINDOWS CE 中, 您必须使用 CommandBar_DrawMenuBar, 而不是用在其它的 WINDOWS 版本中重绘菜单的标准函数 DrawMenuBar 命令条按钮给命令条增加按钮的过程需要两步, 这和给工具条增加按钮的过程类似 首先必须给命令条增加用于按钮的位图图片 其次增加按钮, 每个按钮对应先前加入的位图列表里的一个图片
命令条在一个内部图片列表中维护按钮的位图列表 位图可以一次一个的增加到图象列表中或者将一组图象包含在一个长而且狭窄的位图中 例如, 对一个包含了 4 个 16*16 像素图象的位图来说, 其尺寸将是 64*16 像素 图 5-2 显示了给位图图象的布局 图 5-2: 包含 4 个 16*16 像素图象的位图装载一个位图可以通过下面的函数来完成 int CommandBar_AddBitmap (HWND hwndcb, HINSTANCE hinst, int idbitmap, int inumimage, int iimagewidth, int iimageheight); 头两个参数和以往的命令条函数一样, 是命令条句柄和可执行程序的实例句柄 第三个参数, idbitmap, 是位图图象的资源 ID 第四个参数 inumimages 是加载的位图中图象的数量 只要需要, 可以多次调用 CommandBar_AddBitmap 将多张位图加载到同一个命令条中 最后两个参数是图象的尺寸, 都设置为 16 有两个预定义的位图提供了许多通常用在命令条和工具条中的图象 通过将 CommandBar_AddBitmap 中的 hinst 设置为 HINST_COMMCTRL, 将 idbitmap 设置为 IDB_STD_SMALL_COLOR 或 IDB_VIEW_SMALL_COLOR, 您可以装载这两个位图 图 5-3 展示了这两个位图中包含的图象 第一行的按钮包含了来自标准位图的图象, 而第二行按钮包含了标准视窗中的位图 图 5-3: 由公共控件 DLL 提供的两个标准位图中的图象 这些图象的索引值定义在 CommCtrl.h 中, 所以您不需要知道它们在位图中的确切顺序 这些常量定义如下 : 访问标准位图的常量定义 STD_CUT Edit/Cut button image STD_COPY Edit/Copy button image STD_PASTE Edit/Paste button image STD_UNDO Edit/Undo button image STD_REDOW Edit/Redo button image STD_DELETE Edit/Delete button image STD_FILENEW File/New button image STD_FILEOPEN File/Open button image STD_FILESAVE File/Save button image STD_PRINTPRE Print preview button image STD_PROPERTIES Properties button image STD_HELP Help button (Use Commandbar_Addadornments function to add a help button to the command bar.) STD_FIND Find button image STD_REPLACE Replace button image STD_PRINT Print button image 访问标准视窗中位图的常量定义 VIEW_LARGEICONS View/Large Icons button image VIEW_SMALLICONS View/Small Icons button image VIEW_LIST View/List button image VIEW_DETAILS View/Details button image
VIEW_SORTNAME VIEW_SORTSIZE VIEW_SORTDATE VIEW_SORTTYPE VIEW_PARENTFOLDER VIEW_NETCONNECT VIEW_NETDISCONNECT VIEW_NEWFOLDER Sort by name button image Sort by size button image Sort by date button image Sort by type button image Go to Parent folder button image Connect network drive button image Disconnect network drive button image Create new folder button image 加载到命令条中的图象是按它们在图象列表中的索引进行引用的 例如, 如果加载的位图包含 5 个图象, 要引用位图中第四个图象, 则其基于 0 的索引值是 3 如果通过多次调用 CommandBar_AddBitmap 将多个位图图象集加到命令条中的话, 将前面的图象数加上图象在当前列表中的索引, 即为当前图象的引用值 例如, 通过两次调用 CommandBar_AddBitmap 加入了两个图象集, 其中第一次加入的是 5 个图象, 第二次加入的是 4 个图象, 那么要引用第二个图象集中的第三个图象, 需要用第一个图象里的图象总数 (5) 加上对第 2 个位图的索引 (2), 得到引用为 7 一旦加载了位图, 就可以通过下面两个函数之一来加载按钮 第一个函数是 : BOOL CommandBar_AddButtons ( HWND hwndcb, UINT unumbuttons, LPTBBUTTON lpbuttons); CommandBar_AddButtons 可以一次就给命令条增加一系列按钮 该函数传入一个按钮数量和一个指向 TBBUTTON 结构数组的指针 数组中的每个元素描述一个按钮 TBBUTTON 结构定义如下 : typedef struct { int ibitmap; int idcommand; BYTE fsstate; BYTE fsstyle; DWORD dwdata; int istring; TBBUTTON; ibitmap 设置为按钮使用的位图图象 也就是我刚解释的图象列表中基于 0 的索引 第二个参数是按钮的命令 ID 当用户点击按钮的时候, 通过 WM_COMMAND 消息, 该 ID 被发送到父窗口 fsstate 域则定义了按钮的初始状态 该域所允许的值如下 : TBSTATE_ENABLED 按钮有效 如果没有指定该标志, 按钮将失效并变灰 TBSTATE_HIDDEN 按钮在命令条上不可见 TBSTATE_PRESSED 按钮显示为下压状态 TBSTATE_CHECKED 按钮初始被选中 只有当按钮是 TBSTYLE_CHECKED 风格时该状态才可用 TBSTATE_INDETERMINATE 按钮变灰 最后一个标志 TBSTATE_WRAP, 尽管其在文档中有定义, 但在命令条中它是无效的 该标志用在工具条超过一行而折行时
fsstyle 指定了按钮的初始风格, 用来表明按钮如何响应动作 按钮可以定义为标准下压按钮, 复选框按钮, 下拉列表框, 以及类似单选框的复选框, 此时只允许一组中的一个按钮被选择 对 fsstyle 来说可能的标志如下 : TBSTYLE_BUTTON 标准下压按钮 TBSTYLE_CHECK 复选框, 每次用户点该按钮时, 在选择和取消选择之间切换 TBSTYLE_GROUP 定义一组按钮的开始按钮 TBSTYLE_CHECKGROUP 表示是一组复选框中的一个, 并且行为和单选框类似, 每次只能有一个按钮被选择 TBSTYLE_DROPDOWN 下拉列表框 TBSTYLE_AUTOSIZE 按钮尺寸取决于按钮的文本 TBSTYLE_SEP 定义了一个分隔条, 在按钮之间插入一小块空间 TBBUTTON 结构中的 dwdata 域里是应用程序定义的值 通过使用 TB_SETBUTTONINFO 和 TB_GETBUTTONINFO 消息, 应用程序可以设置和查询该值 istring 则定义了在包含按钮文字的命令条字符数组中的索引 该域也可以添充为指向按钮文本的字符串的指针 给命令条增加按钮的另一个函数如下 : BOOL CommanBar_InsertButton (HWND hwndcb, int ibutton, LPTBBUTTON lpbutton); 该函数将一个按钮插到命令条中 ibutton 所对应的按钮的左边 除了指向单个 TBBUTTON 结构的 lpbutton 外, 该函数的参数同 CommandBar_AddButtons 类似 ibutton 给出了新按钮在命令条上的位置 使用命令条按钮除了下拉按钮外, 当用户按一个命令条按钮时, 命令条都会给其父窗口发送一个 WM_COMMAND 消息 所以处理命令条上的按钮点击就像处理菜单命令一样 实际上, 因为命令条上许多按钮都有等价的菜单命令, 所以习惯上为按钮和同功能的菜单使用相同的命令 ID 号, 这样可以减少对命令条按钮进行特定的处理的需求 命令条会维护单个复选框和一组复选框按钮的选择状态 当按钮被加到命令条中以后, 可以使用下面两个消息来查询或者设置它们的状态 :TB_ISBUTTONCHECKED 和 TB_CHECKBUTTON ( 前缀 TB_ 暗示了命令条和工具条控件之间的密切关系 ) 把要查询的按钮的 ID 放到参数 wparam 中, 将 TB_ISBUTTONCHECKED 消息按下面的方式发送出去 : fchecked = SendMessage (hwndcb, TB_ISBUTTONCHECKED, wid, 0); hwndcb 是包含按钮的命令条的句柄 该函数如果返回值非零, 表示按钮被选择了 要将按钮设置成选择状态, 可以给命令条发送一个 TB_CHECKBUTTON 消息, 方法如下 : SendMessage (hwndcb, TB_CHECKBUTTON, wid,true); 要取消选择, 只要将 TRUE 换成 FALSE 即可 使按钮失效 Windows CE 允许您很容易的修改命令条或者工具条上失效按钮的外观 命令条和工具条维护两个图象列表 : 先前讲述过的标准图象列表和一个失效图象列表, 该列表用来存储用于失效按钮的图象 要使用该特性, 您需要为失效按钮创建和装载第 2 个图象列表 而最容易的方式就是用谈论 CommandBar_AddBitmap 时描述的技术, 为按钮的普通状态创建一个图象列表 ( 可以使
用 TB_LOADIMAGES 消息来装载工具条中的图象列表 ) 一旦完成了图象列表, 只要简单的复制原始图象列表, 并修改图象列表中的位图, 为原始位图创建一个失效副本即可 接下来将新的图象列表装载回命令条或者工具条即可 下面的代码片段展示了如何完成这些工作的 : HBITMAP hbmp, hmask; HIMAGELIST hildisabled, hilenabled; Load the bitmap and mask to be used in the disabled image list. hbmp = LoadBitmap (hinst, TEXT ("DisCross")); hmask = LoadBitmap (hinst, TEXT ("DisMask")); Get the standard image list and copy it. hilenabled = (HIMAGELIST)SendMessage (hwndcb, TB_GETIMAGELIST, 0, 0); hildisabled = ImageList_Duplicate (hilenabled); Replace one bitmap in the disabled list. ImageList_Replace (hildisabled, VIEW_LIST, hbmp, hmask); Set the disabled image list. SendMessage (hwndcb, TB_SETDISABLEDIMAGELIST, 0, (LPARAM) hildisabled); 代码中首先装载了一个位图和一个掩码位图, 它们将用于替换失效图象列表中的位图 通过给命令条发送 TB_GETIMAGELIST 消息, 可以获得当前的图象列表, 之后用 ImageList_Duplicate 来复制一个图象列表 最后用之前装载的位图来替换图象列表中的相应图象即可 本例仅替换了一个位图, 但在实际应用中可以替换许多位图 如果所以有的位图都被替换, 那么从头创建一个失效的图象列表可能比复制一个标准图象列表再替换其中的一些位图会更容易一些 一旦新的图象列表被创建完成, 您可以通过发送 TB_SETDISABLEDIMAGELIST 消息将列表装载进命令条中 上面的代码对 Windows CE 下的命令条一样有效 下拉按钮同命令条上的标准按钮相比, 下拉按钮显得更复杂一些 对用户来说, 它就是一个按钮, 当被按下的时候, 会显示一个列表项来让用户选择 对程序员来说, 下拉按钮实际上就是按钮和菜单的组合, 当用户点击按钮的时候会显示一个菜单 不幸地是, 命令条对下拉按钮支持有限, 只提供了修改按钮外观的功能以表示是一个下拉按钮以及当用户点击按钮时会发送特定通知的功能 另外, 是由应用程序决定如何显示菜单的 通过发送带 TBN_DROPDOWN 通知码的 WM_NOTIFY 消息, 用户点击下拉按钮的通知将会发送到命令条的父窗口中 当父窗口接收到 TBN_DROPDOWN 通知后, 它必须立即在通知消息中标识的下拉按钮下创建一个弹出菜单 菜单由父窗口使用适当的选项来填充 当有菜单项被选择时, 菜单将发送 WM_COMMAND 消息, 表示菜单项被选择了并且菜单即将消失 为了更容易理解如何处理下拉按钮通知, 可以浏览以下处理 TBN_DROPDOWN 通知的过程 : LRESULT DoNotifyMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { LPNMHDR pnotifyheader; LPNMTOOLBAR pnotifytoolbar;
RECT rect; TPMPARAMS tpm; HMENU hmenu; Get pointer to notify message header. pnotifyheader = (LPNMHDR)lParam; if (pnotifyheader->code == TBN_DROPDOWN) { Get pointer to toolbar notify structure. pnotifytoolbar = (LPNMTOOLBAR)lParam; Get the rectangle of the drop-down button. SendMessage (pnotifyheader->hwndfrom, TB_GETRECT, pnotifytoolbar->iitem, (LPARAM)&rect); Convert rect to screen coordinates. The rect is considered here to be an array of 2 POINT structures. MapWindowPoints (pnotifyheader->hwndfrom, HWND_DESKTOP, (LPPOINT)&rect, 2); Prevent the menu from covering the button. tpm.cbsize = sizeof (tpm); CopyRect (&tpm.rcexclude, &rect); Load the menu resource to display under the button. hmenu = GetSubMenu (LoadMenu (hinst, TEXT ("popmenu")),0); Display the menu. This function returns after the user makes a selection or dismisses the menu. TrackPopupMenuEx (hmenu, TPM_LEFTALIGN TPM_VERTICAL, rect.left, rect.bottom, hwnd, &tpm); return 0; 当代码判断出是 TBN_DROPDOWN 通知后, 首先要做的就是获取下拉按钮的矩形 获取该矩形是为了使下拉菜单能被立即定位到按钮下方 要达到该目的, 程序会发送 TB_GETRECT 消息给命令条, 其中参数 wparam 是下拉按钮的 ID, 参数 lparam 是指向矩形结构的指针 因为返回的矩形是基于父窗口的坐标, 而弹出菜单是屏幕坐标, 所以必须进行坐标转换 这可以使用以下函数来完成 : MapWindowPoints (HWND hwndfrom, HWND hwndto, LPPOINT lppoints, UINT cpoints); 第一个参数是原始坐标的窗口句柄 第二个参数是将要转换到的坐标所在的窗口句柄 第三个参
数是指向将被转换的坐标点数组的指针 最后一个参数是数组中点的数量 在我所展示的程序里, 窗口句柄分别是命令条句柄和桌面窗口句柄 一旦矩形被转换成桌面坐标, 就可以创建弹出菜单或者和上下文相关的菜单了 要这样做, 您首先需要从资源中装载菜单并调用 TrackPopupMenuEx 来显示菜单 如果您能回想起前面章节中对 TrackPopupMenuEx 的讨论, 那么就会知道 TPMPARAMS 结构包含一个不会被菜单显示所覆盖的矩形 对次, 矩形被设置成下拉按钮的大小, 这样按钮就不会弹出菜单所覆盖了 fuflags 中则包含了许多值, 用来定义菜单的位置 对下拉按钮来说, 只需要 TPM_VERTICAL 标志 如果 TPM_VERTICAL 被设置, 菜单会尽可能的为非覆盖矩形保留更多的水平区域 TrackPopupMenuEx 函数会一直等到菜单项被选择或者当用户在屏幕的其它地方点击后菜单消失时才会返回 ( 待续 ) Windows CE 程序设计 (3rd 版 )--5.2 公共控件 ( 二 ) / / / 2006-03-05 由于最近事情比较多, 不能像以前一样, 翻译一整节再发布了, 所以决定采用分散翻译, 分散发布的方式 每次会翻译一部分就发布, 等该节全部翻译完, 再汇总成一个完整的章节 / / / 翻译 :tellmenow 命令条上的组合框同下拉列表按钮相比, 命令条中的组合框更容易实现 通过调用下面的函数, 您可以增加一个组合框 : HWND CommandBar_InsertComboBox (HWND hwndcb, HINSTANCE hinst,int iwidth, UINT dwstyle,word idcombobox,int ibutton); 该函数在命令条上 ibutton 参数所指示的按钮左边插入一个组合框 组合框的宽度在 iwidth 参数中指定, 单位是像素 dwstyle 指定了组合框的风格 允许的风格包括任何有效的 Windows CE 组合框风格和窗口风格 当创建组合框的时候, 该函数自动加 WS_CHILD 和 WS_VISIBLE 标志 idcombobox 参数是组合框的 ID, 当发送 WM_COMMAND 消息来通知组合框父窗口事件的时候会使用该 ID 经验丰富的 Windows 程序员如果知道 CommandBAr_InsertComboBox 函数已经解决了在将控件加到标准 Windows 工具条时会发生的所有问题时, 应该是很高兴的 要在命令条中创建一个完全功能的组合框, 所有需要做的仅仅是调用这个函数即可 一旦创建了组合框, 您就可以按照对待其它独立组合框一样的方式, 对命令条上的组合框进行编程了 因为组合框是命令条的子窗口, 所以您必须将命令条的句柄以及组合框的 ID 传给 GetDlgItem 函数来查询组合框的窗口句柄, 如下列代码所示 : hwndcombobox = GetDlgItem (GetDlgItem (hwnd, IDC_CMDBAR),IDC_COMBO)); 由于来自组合框的 WM_COMMAND 消息被直接发送到了命令条的父窗口上, 所以处理组合框事件同处理由应用程序顶层窗口创建的作为子窗口的组合框是一样的
命令条的工具提示 (Tooltips) 工具提示实际上是小窗口, 当输入笔在控件上压触的时候, 会在其中显示命令条按钮的描述文字 命令条使用自己特殊的方式来实现工具提示 您可以使用以下函数给命令条增加工具提示 : BOOL CommandBar_AddToolTips (HWND hwndcb, UINT unumtooltips, LPTSTR lptooltips); 参数 lptooltips 指向字符串指针数组 unumtooltips 是字符串指针数组中元素的个数 CommandBar_AddToolTips 并不会将字符串复制到自己的存储区中, 而是保存字符串数组的位置 这意味着在命令条被销毁之前, 包含字符串数组的内存区不应该被释放 数组里的每个字符串成为命令条上控件或者分隔条的提示文字, 要注意的是, 这并不包括命令条上面的菜单 数组里的第一个字符串成为第一个控件或者分隔条的工具提示, 第二个字符串成为第二个控件或者分隔条的工具提示, 依次类推 所以即使组合框和分隔条不显示工具提示, 它们也必须要在字符串数组中占有条目, 以保证所有的文本同相应的按钮匹配 Windows CE 程序设计 (3rd 版 )--5.2 公共控件 ( 三 ) 翻译 :tellmenow 其它命令条函数有许多其它函数用于帮助管理命令条 CommandBar_Height 函数返回命令条的高度, 并且用于所有使用命令条的示例程序中 同样地, 不论什么时候使用命令条, 都会用到 CommandBar_AddAdornments 函数 该函数原型如下 : BOOL CommandBar_AddAdornments (HWND hwndcb, DWORD dwflags, DWORD dwreserved); 使用该函数, 可以在命令条最右边增加 [ 关闭 ] 按钮 [ 帮助 ] 按钮和 [OK] 按钮 通过将参数 dwflags 设置成 CMDBAR_HELP 来增加 [ 帮助 ] 按钮, 设置成 CMDBAR_OK 来增加 [OK] 按钮 [ 帮助 ] 按钮在处理上不同于命令条上的其它按钮 当 [ 帮助 ] 按钮被压下, 命令条发送 WM_HELP 消息给命令条的拥有者, 而不是通常标准的 WM_COMMAND 消息 [OK] 按钮则是按惯常的方式处理 当按下时, 会发送一个带控件标识 IDOK 的 WM_COMMAND 消息 必须当所有其它控件添加以后, 才可以调用 CommandBar_AddAdornments 函数 如果顶层窗口是可调整大小的, 那在处理 WM_SIZE 消息时, 必须给命令条发送一个 TB_AUTOSIZE 消息并调用 BOOL CommandBar_AlignAdornments (HWND hwndcb) 函数, 以此来通知命令条调整大小 该函数唯一的参数就是命令条句柄 通过调用 BOOL CommandBar_Show (HWND hwndcb, BOOL fshow) 函数, 可以隐藏或者显示命令条 fshow 设置成 TRUE 则显示命令条, 设置成 FALSE 则隐藏命令条 可以使用 BOOL CommandBar_IsVisible (HWND hwndcb) 函数来查询命令条是否可视 可以使用 void CommandBar_Destory (HWND hwndcb) 函数来销毁命令条 虽然当父窗口被销毁的时会自动销毁命令条, 但是有时手工销毁命令条会更方便一些 尤其是当应用程序的不同模式需要新的命令条的时候更是经常这么做 当然, 您可以先创建多个命令条, 将其中一个显示并隐藏其它的, 随后通过每次只显示一个的方式来进行切换 但在 Windows CE 下这不是一个好的编程习惯, 因为所有隐藏的命令条会占用宝贵的 RAM 恰当的方式是在空闲的时候销毁和创建一个命令条 可以很快的创建一个命令条, 快到用户不会察觉到有任何延迟
Windows CE 程序设计 (3rd 版 )--5.2 公共控件 ( 四 ) 翻译 :tellmenow 命令条设计指导因为命令条是 Windows CE 应用程序中的一个主要元素, 所以微软对如何使用它们给出了一个相当强的规则集合 这些规则中有许多规则同其它版本的 Windows 是类似的, 例如对主菜单项的顺序的建议和对工具条提示的使用等 大部分规则已经成为 Windows 程序员的第二天性了 菜单应该是命令条上最左边的项 主菜单项按从左到右的顺序依次为 : 文件 (File) 视图 (View) 插入 (Insert) 格式(Format) 工具(Tools) 和窗口 (Windows) 当然, 大部分应用都具有这些菜单项, 这些菜单项的顺序应该遵循建议的顺序 对按钮来说, 按从左到右的顺序是 : 用于文件操作的有新建 (New) 打开(Open) 保存(Save) 和打印 (Print);, 用于字体风格的是粗体 斜体和下划线 CmdBar 示例程序 CmdBar 示例演示了命令条的基本操作 在启动的时候, 创建了一个只有菜单和关闭按钮的工具条 从 [ 视图 (view)] 菜单里选择不同的项来创建不同的命令条, 由此展示了命令条控件的性能 源代码如清单 5-1 所示 清单 5-1:CmdBar 程序 CmdBar.rc ================================================= ===================== Resource file Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include "windows.h" #include "CmdBar.h" Program-specific stuff ---------------------------------------------------------------------- Icons and bitmaps ID_ICON ICON "cmdbar.ico" Program icon DisCross BITMAP "cross.bmp" Disabled button image DisMask BITMAP "mask.bmp" Disabled button image mask SortDropBtn BITMAP "sortdrop.bmp" Sort drop-down button image ---------------------------------------------------------------------- Menu ID_MENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN
END MENUITEM "E&xit", IDM_EXIT POPUP "&View" BEGIN MENUITEM "&Standard", MENUITEM "&View", MENUITEM "&Combination", END POPUP "&Help" BEGIN MENUITEM "&About...", END END IDM_STDBAR IDM_VIEWBAR IDM_COMBOBAR IDM_ABOUT popmenu MENU DISCARDABLE BEGIN POPUP "&Sort" BEGIN MENUITEM "&Name", MENUITEM "&Type", MENUITEM "&Size", MENUITEM "&Date", END END IDC_SNAME IDC_STYPE IDC_SSIZE IDC_SDATE ---------------------------------------------------------------------- About box dialog template aboutbox DIALOG discardable 10, 10, 160, 45 STYLE WS_POPUP WS_VISIBLE WS_CAPTION WS_SYSMENU DS_CENTER DS_MODALFRAME CAPTION "About" BEGIN ICON ID_ICON, -1, 5, 5, 10, 10 LTEXT "CmdBar - Written for the book Programming Windows \ CE Copyright 2003 Douglas Boling" -1, 40, 5, 110, 35 END CmdBar.h ================================================= ===================== Header file
Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== Returns number of elements #define dim(x) (sizeof(x) / sizeof(x[0])) ---------------------------------------------------------------------- Generic defines and data types struct decodeuint { Structure associates UINT Code; messages with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); ; struct decodecmd { Structure associates UINT Code; menu IDs with a LRESULT (*Fxn)(HWND, WORD, HWND, WORD); function. ; ---------------------------------------------------------------------- Generic defines used by application #define IDC_CMDBAR 1 Command band ID #define ID_ICON 10 Icon resource ID #define ID_MENU 11 Main menu resource ID #define IDC_COMBO 12 Combo box on cmd bar ID Menu item IDs #define IDM_EXIT 101 File menu #define IDM_STDBAR 111 View menu #define IDM_VIEWBAR 112 #define IDM_COMBOBAR 113 #define IDM_ABOUT 120 Help menu Command bar button IDs #define IDC_NEW 201 #define IDC_OPEN 202 #define IDC_SAVE 203 #define IDC_CUT 204 #define IDC_COPY 205 #define IDC_PASTE 206 #define IDC_PROP 207 #define IDC_LICON 301 #define IDC_SICON 302
#define IDC_LIST 303 #define IDC_RPT 304 #define IDC_SNAME 305 #define IDC_STYPE 306 #define IDC_SSIZE 307 #define IDC_SDATE 308 #define IDC_DPSORT 350 #define STD_BMPS (STD_PRINT+1) Number of bmps in std imglist #define VIEW_BMPS (VIEW_NEWFOLDER+1) Number of bmps in view imglist ---------------------------------------------------------------------- Function prototypes HWND InitInstance (HINSTANCE, LPWSTR, int); int TermInstance (HINSTANCE, int); Window procedures LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM); Message handlers LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoSizeMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoCommandMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoNotifyMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM); Command functions LPARAM DoMainCommandExit (HWND, WORD, HWND, WORD); LPARAM DoMainCommandVStd (HWND, WORD, HWND, WORD); LPARAM DoMainCommandVView (HWND, WORD, HWND, WORD); LPARAM DoMainCommandVCombo (HWND, WORD, HWND, WORD); LPARAM DoMainCommandAbout (HWND, WORD, HWND, WORD); Dialog procedures BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM); CmdBar.cpp ================================================= ===================== CmdBar - Command bar demonstration Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling =================================================
===================== #include <windows.h> For all that Windows stuff #include <commctrl.h> Command bar includes #include "CmdBar.h" Program-specific stuff ---------------------------------------------------------------------- Global data const TCHAR szappname[] = TEXT ("CmdBar"); HINSTANCE hinst; Program instance handle Message dispatch table for MainWindowProc const struct decodeuint MainMessages[] = { WM_CREATE, DoCreateMain, WM_SIZE, DoSizeMain, WM_COMMAND, DoCommandMain, WM_NOTIFY, DoNotifyMain, WM_DESTROY, DoDestroyMain, ; Command Message dispatch for MainWindowProc const struct decodecmd MainCommandItems[] = { IDM_EXIT, DoMainCommandExit, IDM_STDBAR, DoMainCommandVStd, IDM_VIEWBAR, DoMainCommandVView, IDM_COMBOBAR, DoMainCommandVCombo, IDM_ABOUT, DoMainCommandAbout, ; Standard file bar button structure const TBBUTTON tbcbstdbtns[] = { BitmapIndex Command State Style UserData String {0, 0, 0, TBSTYLE_SEP, 0, 0, {STD_FILENEW, IDC_NEW, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {STD_FILEOPEN, IDC_OPEN, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {STD_FILESAVE, IDC_SAVE, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {0, 0, 0, TBSTYLE_SEP, 0, 0, {STD_CUT, IDC_CUT, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {STD_COPY, IDC_COPY, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {STD_PASTE, IDC_PASTE, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0,
{0, 0, 0, TBSTYLE_SEP, 0, 0, {STD_PROPERTIES, IDC_PROP, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0 ; Standard view bar button structure const TBBUTTON tbcbviewbtns[] = { BitmapIndex Command State Style UserData String {0, 0, 0, TBSTYLE_SEP, 0, 0, {VIEW_LARGEICONS, IDC_LICON, TBSTATE_ENABLED TBSTATE_CHECKED, TBSTYLE_CHECKGROUP, 0, 0, {VIEW_SMALLICONS, IDC_SICON, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0, {VIEW_LIST, IDC_LIST, 0, TBSTYLE_CHECKGROUP, 0, 0, {VIEW_DETAILS, IDC_RPT, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0, {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, {VIEW_SORTNAME, IDC_SNAME, TBSTATE_ENABLED TBSTATE_CHECKED, TBSTYLE_CHECKGROUP, 0, 0, {VIEW_SORTTYPE, IDC_STYPE, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0, {VIEW_SORTSIZE, IDC_SSIZE, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0, {VIEW_SORTDATE, IDC_SDATE, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0, {0, 0, 0, TBSTYLE_SEP, 0, 0, ; Tooltip string list for view bar const TCHAR *pviewtips[] = {TEXT (""), TEXT ("Large"), TEXT ("Small"), TEXT ("List"), TEXT ("Details"), TEXT (""), TEXT ("Sort by Name"), TEXT ("Sort by Type"), TEXT ("Sort by Size"), TEXT ("Sort by Date"), ; Combination standard and view bar button structure const TBBUTTON tbcbcmbobtns[] = { BitmapIndex Command State Style UserData String {0, 0, 0, TBSTYLE_SEP, 0, 0, {STD_FILENEW, IDC_NEW, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {STD_FILEOPEN, IDC_OPEN, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {STD_PROPERTIES, IDC_PROP, TBSTATE_ENABLED,
; TBSTYLE_BUTTON, 0, 0, {0, 0, 0, TBSTYLE_SEP, 0, 0, {STD_CUT, IDC_CUT, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {STD_COPY, IDC_COPY, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {STD_PASTE, IDC_PASTE, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {0, 0, 0, TBSTYLE_SEP, 0, 0, {STD_BMPS + VIEW_LARGEICONS, IDC_LICON, TBSTATE_ENABLED TBSTATE_CHECKED, TBSTYLE_CHECKGROUP, 0, 0, {STD_BMPS + VIEW_SMALLICONS, IDC_SICON, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0, {STD_BMPS + VIEW_LIST, IDC_LIST, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0, {STD_BMPS + VIEW_DETAILS, IDC_RPT, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0, {0, 0, 0, TBSTYLE_SEP, 0, 0, {STD_BMPS + VIEW_BMPS, IDC_DPSORT,TBSTATE_ENABLED, TBSTYLE_DROPDOWN, 0, 0 ================================================= ===================== Program entry point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { HWND hwndmain; MSG msg; int rc = 0; Initialize application. hwndmain = InitInstance (hinstance, lpcmdline, ncmdshow); if (hwndmain == 0) return 0x10; Application message loop while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage (&msg); DispatchMessage (&msg); Instance cleanup return TermInstance (hinstance, msg.wparam); ---------------------------------------------------------------------- InitInstance - Instance initialization HWND InitInstance (HINSTANCE hinstance, LPWSTR lpcmdline, int ncmdshow){ HWND hwnd; DWORD dwstyle = WS_VISIBLE; int x = CW_USEDEFAULT, y = CW_USEDEFAULT; int cx = CW_USEDEFAULT, cy = CW_USEDEFAULT; WNDCLASS wc; INITCOMMONCONTROLSEX icex; #if defined(win32_platform_pspc) If Pocket PC, allow only one instance of the application. hwnd = FindWindow (szappname, NULL); if (hwnd) { SetForegroundWindow ((HWND)(((DWORD)hWnd) 0x01)); return 0; #endif Register application main window class. wc.style = 0; Window style wc.lpfnwndproc = MainWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = szappname; Window class name if (RegisterClass (&wc) == 0) return 0; Load the command bar common control class. icex.dwsize = sizeof (INITCOMMONCONTROLSEX); icex.dwicc = ICC_BAR_CLASSES; InitCommonControlsEx (&icex);
#ifndef WIN32_PLATFORM_PSPC dwstyle = WS_CAPTION WS_SIZEBOX WS_MAXIMIZEBOX WS_MINIMIZEBOX; x = y = 10; cx = GetSystemMetrics (SM_CXSCREEN) - 30; cy = GetSystemMetrics (SM_CYSCREEN) - 50 #endif Save program instance handle in global variable. hinst = hinstance; Create main window. hwnd = CreateWindow (szappname, TEXT ("CmdBar Demo"), dwstyle, x, y, cx, cy, NULL, NULL, hinstance, NULL); Return fail code if window not created. if (!IsWindow (hwnd)) return 0; Standard show and update calls ShowWindow (hwnd, ncmdshow); UpdateWindow (hwnd); return hwnd; ---------------------------------------------------------------------- TermInstance - Program cleanup int TermInstance (HINSTANCE hinstance, int ndefrc) { return ndefrc; ================================================= ===================== Message handling procedures for MainWindow ---------------------------------------------------------------------- MainWndProc - Callback function for application window LRESULT CALLBACK MainWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { int i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(mainmessages); i++) { if (wmsg == MainMessages[i].Code) return (*MainMessages[i].Fxn)(hWnd, wmsg, wparam, lparam);
return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoCreateMain - Process WM_CREATE message for window. LRESULT DoCreateMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { HWND hwndcb; Create a minimal command bar that has only a menu and an exit button. hwndcb = CommandBar_Create (hinst, hwnd, IDC_CMDBAR); Insert the menu. CommandBar_InsertMenubar (hwndcb, hinst, ID_MENU, 0); Add exit button to command bar. CommandBar_AddAdornments (hwndcb, 0, 0); return 0; ---------------------------------------------------------------------- DoSizeMain - Process WM_SIZE message for window. LRESULT DoSizeMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { #ifndef WIN32_PLATFORM_PSPC HWND hwndcb = GetDlgItem (hwnd, IDC_CMDBAR); Tell the command bar to resize itself and reposition Close button. SendMessage(hwndCB, TB_AUTOSIZE, 0L, 0L); CommandBar_AlignAdornments(hwndCB); #endif WIN32_PLATFORM_PSPC return 0; ---------------------------------------------------------------------- DoCommandMain - Process WM_COMMAND message for window. LRESULT DoCommandMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { WORD iditem, wnotifycode; HWND hwndctl; INT i; Parse the parameters.
iditem = (WORD) LOWORD (wparam); wnotifycode = (WORD) HIWORD (wparam); hwndctl = (HWND) lparam; Call routine to handle control message. for (i = 0; i < dim(maincommanditems); i++) { if (iditem == MainCommandItems[i].Code) return (*MainCommandItems[i].Fxn)(hWnd, iditem, hwndctl, wnotifycode); return 0; ---------------------------------------------------------------------- DoNotifyMain - Process WM_NOTIFY message for window. LRESULT DoNotifyMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { LPNMHDR pnotifyheader; LPNMTOOLBAR pnotifytoolbar; RECT rect; TPMPARAMS tpm; HMENU hmenu; Get pointer to notify message header. pnotifyheader = (LPNMHDR)lParam; if (pnotifyheader->code == TBN_DROPDOWN) { Get pointer to toolbar notify structure. pnotifytoolbar = (LPNMTOOLBAR)lParam; if (pnotifytoolbar->iitem == IDC_DPSORT) { Get the rectangle of the drop-down button. SendMessage (pnotifyheader->hwndfrom, TB_GETRECT, pnotifytoolbar->iitem, (LPARAM)&rect); Convert rect to screen coordinates. The rect is considered here to be an array of 2 POINT structures. MapWindowPoints (pnotifyheader->hwndfrom, HWND_DESKTOP, (LPPOINT)&rect, 2); Prevent the menu from covering the button. tpm.cbsize = sizeof (tpm); CopyRect (&tpm.rcexclude, &rect);
hmenu = GetSubMenu (LoadMenu (hinst, TEXT ("popmenu")),0); TrackPopupMenuEx (hmenu, TPM_LEFTALIGN TPM_VERTICAL, rect.left, rect.bottom, hwnd, &tpm); return 0; ---------------------------------------------------------------------- DoDestroyMain - Process WM_DESTROY message for window. LRESULT DoDestroyMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PostQuitMessage (0); return 0; ================================================= ===================== Command handler routines ---------------------------------------------------------------------- DoMainCommandExit - Process Program Exit command. LPARAM DoMainCommandExit (HWND hwnd, WORD iditem, HWND hwndctl, WORD wnotifycode) { SendMessage (hwnd, WM_CLOSE, 0, 0); return 0; ---------------------------------------------------------------------- DoMainCommandViewStd - Displays a standard edit-centric command bar LPARAM DoMainCommandVStd (HWND hwnd, WORD iditem, HWND hwndctl, WORD wnotifycode) { HWND hwndcb; If a command bar exists, kill it. if (hwndcb = GetDlgItem (hwnd, IDC_CMDBAR)) CommandBar_Destroy (hwndcb); Create a command bar. hwndcb = CommandBar_Create (hinst, hwnd, IDC_CMDBAR); Insert a menu. CommandBar_InsertMenubar (hwndcb, hinst, ID_MENU, 0); Insert buttons.
CommandBar_AddBitmap (hwndcb, HINST_COMMCTRL, IDB_STD_SMALL_COLOR, STD_BMPS, 0, 0); CommandBar_AddButtons (hwndcb, dim(tbcbstdbtns), tbcbstdbtns); Add exit button to command bar. CommandBar_AddAdornments (hwndcb, 0, 0); return 0; ---------------------------------------------------------------------- DoMainCommandVView - Displays a standard edit-centric command bar LPARAM DoMainCommandVView (HWND hwnd, WORD iditem, HWND hwndctl, WORD wnotifycode) { INT i; HWND hwndcb; TCHAR sztmp[64]; HBITMAP hbmp, hmask; HIMAGELIST hildisabled, hilenabled; If a command bar exists, kill it. if (hwndcb = GetDlgItem (hwnd, IDC_CMDBAR)) CommandBar_Destroy (hwndcb); Create a command bar. hwndcb = CommandBar_Create (hinst, hwnd, IDC_CMDBAR); Insert a menu. CommandBar_InsertMenubar (hwndcb, hinst, ID_MENU, 0); Insert buttons, first add a bitmap and then the buttons. CommandBar_AddBitmap (hwndcb, HINST_COMMCTRL, IDB_VIEW_SMALL_COLOR, VIEW_BMPS, 0, 0); Load bitmaps for disabled image. hbmp = LoadBitmap (hinst, TEXT ("DisCross")); hmask = LoadBitmap (hinst, TEXT ("DisMask")); Get the current image list and copy. hilenabled = (HIMAGELIST)SendMessage (hwndcb, TB_GETIMAGELIST, 0, 0); hildisabled = ImageList_Duplicate (hilenabled); Replace a button image with the disabled image. ImageList_Replace (hildisabled, VIEW_LIST, hbmp, hmask);
Set disabled image list. SendMessage (hwndcb, TB_SETDISABLEDIMAGELIST, 0, (LPARAM)hilDisabled); Add buttons to the command bar. CommandBar_AddButtons (hwndcb, dim(tbcbviewbtns), tbcbviewbtns); Add tooltips to the command bar. CommandBar_AddToolTips (hwndcb, dim(pviewtips), pviewtips); Add a combo box between the view icons and the sort icons. CommandBar_InsertComboBox (hwndcb, hinst, 75, CBS_DROPDOWNLIST WS_VSCROLL, IDC_COMBO, 6); Fill in combo box. for (i = 0; i < 10; i++) { wsprintf (sztmp, TEXT ("Item %d"), i); SendDlgItemMessage (hwndcb, IDC_COMBO, CB_INSERTSTRING, -1, (LPARAM)szTmp); SendDlgItemMessage (hwndcb, IDC_COMBO, CB_SETCURSEL, 0, 0); Add exit button to command bar. CommandBar_AddAdornments (hwndcb, 0, 0); return 0; ---------------------------------------------------------------------- DoMainCommandVCombo - Displays a combination of file and edit buttons LPARAM DoMainCommandVCombo (HWND hwnd, WORD iditem, HWND hwndctl, WORD wnotifycode) { HWND hwndcb; If a command bar exists, kill it. if (hwndcb = GetDlgItem (hwnd, IDC_CMDBAR)) CommandBar_Destroy (hwndcb); Create a command bar. hwndcb = CommandBar_Create (hinst, hwnd, IDC_CMDBAR); Insert a menu. CommandBar_InsertMenubar (hwndcb, hinst, ID_MENU, 0); Add two bitmap lists plus custom bmp for drop-down button. CommandBar_AddBitmap (hwndcb, HINST_COMMCTRL,
IDB_STD_SMALL_COLOR, STD_BMPS, 0, 0); CommandBar_AddBitmap (hwndcb, HINST_COMMCTRL, IDB_VIEW_SMALL_COLOR, VIEW_BMPS, 0, 0); CommandBar_AddBitmap (hwndcb, NULL, (int)loadbitmap (hinst, TEXT ("SortDropBtn")), 1, 0, 0); CommandBar_AddButtons (hwndcb, dim(tbcbcmbobtns), tbcbcmbobtns); Add exit button to command bar. CommandBar_AddAdornments (hwndcb, 0, 0); return 0; ---------------------------------------------------------------------- DoMainCommandAbout - Process the Help About menu command. LPARAM DoMainCommandAbout(HWND hwnd, WORD iditem, HWND hwndctl, WORD wnotifycode) { Use DialogBox to create modal dialog box. DialogBox (hinst, TEXT ("aboutbox"), hwnd, AboutDlgProc); return 0; ================================================= ===================== About Dialog procedure BOOL CALLBACK AboutDlgProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { switch (wmsg) { case WM_COMMAND: switch (LOWORD (wparam)) { case IDOK: case IDCANCEL: EndDialog (hwnd, 0); return TRUE; break; return FALSE;
CmdBar 所创建的每个命令条都演示了命令条控件的不同能力 在例程 DoMainCommandVStd 中创建了第一个命令条, 带有一个菜单和一组按钮 按钮结构的定义 位于 CmdBar.cpp 文件顶部位置的 tbcbstdbtns 数组里 在例程 DoMainCommandVView 中创建了第二个命令条, 包含了由组合框分隔开的两组复选框按钮 该命令条也演示了用于失效按钮的分隔图的用法 命令条上第三个按钮 -- 列表视图按钮 -- 是失效的 用于按钮的失效图像是一个看上去像 X 的位图, 该位图位于失效按钮图象列表里 在例程 DoMainCommandVCombo 中创建了第三个命令条 该命令条为下拉按钮提供了标准位图以及定制位图 演示了如何在包含多个位图的图象列表中引用图象的技术 下拉按钮使用了 DoNotifyMain 例程, 当收到 TBN_DROPDOWN 通知后, 该例程会装载和显示一个弹出式菜单 当 CmdBar 最终被编译成 Windows CE 的嵌入式版本时, 受 CreateWindow 中的风格标志影响, 使其看上去有点不同 主窗口有标题栏, 但不会占据整个屏幕 您可以通过拖拉窗口的边缘来调整窗口, 通过拖动标题栏来移动窗口 通过借鉴 WM_SIZE 消息处理函数里的一些代码, 该程序演示了命令条调整自身的能力 Windows CE 程序设计 (3rd 版 )--5.2 公共控件 ( 五 ) 翻译 :tellmenow 命令带 (Command Bands) 命令带控件是一种 rebar 控件, 默认会在控件的每个带区中包含一个命令条 rebar 控件是一种控件容器, 用户可以在应用程序的窗口上拖动它 命令带只不过是在 rebar 中的命令条, 所以在学习如何对命令带控件进行编程时, 知道如何对命令条进行编程就成为最多学习的了 命令带控件上每个单独的带区都有一个 小夹子, 用于将该带区拖动到新的位置 命令带可以最小化, 此时只显示 小夹子 和一个图标 ; 也可以最大化, 此时可以覆盖同一行上的其它带区 ; 也可以设置为 还原 (Restore) 状态, 此时和同一行上的其它带区共享屏幕空间 甚至可以将命令带移动到一个新行, 来创建一个多行命令带 图 5-4 中的窗口顶部, 显示了一个两行命令带控件 图 5-4 略命令带的标准用途是将菜单 按钮等命令条上的元素分隔成独立的命令带 这可以让用户按自己的喜好来重新安排这些元素 也可以将独立的命令带显露或者叠在一起, 好为菜单 按钮等控件提供更大的空间 创建命令带控件创建命令带是很简单的, 当然, 同创建命令条控件比还是有点复杂 通过调用以下函数来创建命令带控件 : HWND CommandBands_Create (HINSTANCE hinst, HWND hwndparent, UINT wid, DWORD dwstyles, HIMAGELIST himl); dwstyles 参数接受许多标志, 用来定义命令带控件的外观和操作行为 这些风格同 rebar 的很类似, 毕竟命令带控件和 rebar 控件关系密切 RBS_AUTOSIZE 如果控件尺寸或者位置发生变化, 命令带自动调整布局 RBS_BANDBORDERS 每个带上绘制线条来分隔相邻的带
------------------------------------------------------------------------------------------- ------------------------- 用于命令带控件的图象列表 前面我提到过图象列表 命令条和工具条都在内部使用图象列表, 用于管理用在按钮上的图象 可以使用标准的图象列表控件来管理图象列表 该控件基本上就是一个帮助控件, 用来协助应用程序管理一系列尺寸相同的图象 Windows CE 下的图象列表控件同 Windows2000 及 Windows Me 下的是一致的, 除非是不支持光标的 WindowsCE 版本 对于命令带控件来说, 只需要创建图象列表, 并加入代表最小化状态时的单独命令带的图象集即可 下面展示了一小段相关的代码 : himl = ImageList_Create (16,16,ILC_COLOR,2,0); hbmp = LoadBitmap (hinst, TEXT("CmdBarBmps")); DeleteObject (hbmp); 函数 ImageList_Create 头两个参数是要装载的图象的尺寸, 第三个是图象的格式 (ILC_COLOR 是默认的 ), 第四个是图象列表里的初始图象数量, 最后一个是要添加的图象个数 通过装载一个包含两个图象的双倍宽度的位图以及调用 ImageList_Add, 可以将两个图象加进来 位图被装进图象列表后, 应该删除位图 RBS_FIXEDORDER 允许移动命令带, 但顺序保持不变 RBS_SMARTLABELS 当最小化时, 用图标来代表命令带 当命令带还原或者最大化时, 会显示标签文本 RBS_VARHEIGHT 控件中的每行会根据该行带区的高度调整到最小尺寸高度 当没有该标志的时候, 每行的高度按控件中最高的带来计算 CCS_VERT 创建一个垂直命令带控件 RBS_VERTICALGRIPPER 为垂直命令条显示一个用于移动的 小夹子 该标志会被忽略, 除非设置了 CCS_VERT 标志这些风格当中,RBS_SMARTLABELS 和 RBS_VARHEIGHT 是使用最多的两个标志 RBS_SMARTLABELS 标志使用户可以为命令带控件选择有吸引力的外观而不需要应用程序做什么工作 如果在带区使用控件而不是在默认的命令条上, 那么 RBS_VARHEIGHT 标志就是很重要的 CCS_VERT 风格标志会创建一个垂直的命令带控件, 但因为 WindowsCE 不支持垂直菜单, 所以带有菜单的命令带在垂直带中是不会正确显示的 不过您可以当控件是垂直方向的时候, 隐藏一个特殊的命令带 Windows CE 程序设计 (3rd 版 )--5.2 公共控件 ( 六 ) 增加带区翻译 :tellmenow REBARBANDINFO 结构用来描述控件中的每个带区, 通过给 CommamndBands_AddBands 函数传递一个 REBARBANDINFO 结构数组, 可以给应用程序添加带区 CommandBands_AddBands 函数原型如下 : BOOL CommandBands_AddBands (HWND hwndcmdbands, HINSTANCE hinst, UINT cbands, LPREBARBANDINFO prbbi); 再调用该函数之前, 您必须先把准备加到控件里的每个带区的信息填写到 REBARBANDINFO 结构中 该结构定义如下 :
typedef struct tagrebarbandinfo{ UINT cbsize; UINT fmask; UINT fstyle; COLORREF clrfore; COLORREF clrback; LPTSTR lptext; UINT cch; int iimage; HWND hwndchild; UINT cxminchild; UINT cyminchild; UINT cyminchild; UINT cx; HBITMAP hbmback; UINT wid; UINT cychild; UINT cymaxchild; UINT cyintegral; UINT cxideal; LPARAM lparam; REBARBANDINFO; 幸运地是, 虽然该结构看上去很宏伟壮观, 但其中的许多域可以被忽略掉, 因为对未初始化的域有默认值 和通常的 Windows 结构一样, 做为一种容错安全措施,cbSize 需要填充为结构的尺寸 fmask 域则填充了许多标志, 用来指出结构中其它域中哪些域填写的是有效信息 在讨论到每个域的时候, 我会描述这些标志的 如果 fmask 域中指定了 RBBIM_STYLE 标志, 那 fstyle 域就必须使用带区的风格标志来填充 适用的标志如下 : RBBS_BREAK 带区从新的一行开始 RBBS_FIXEDSIZE 带区不能被调整大小 当指定了该标志, 带区的 小把手 将不显示 RBBS_HIDDEN 当命令带被创建后, 带区不可视 RBBS_GRIPPERALWAYS 带区有可调尺寸的小把手, 即使命令带只有一个带区 RBBS_NOGRIPPER 带区没有可调尺寸的小把手, 因此用户不能移动带区 RBBS_NOVERT 如果使用 CCS_VERT 风格, 命令带控件垂直显示的话, 带区不会显示出来 RBBS_CHILDEDGE 在带区的顶部和底部绘制边线 RBBS_FIXEDBMP 当带区改变大小的时候, 带区的背景位图不移动 基本上这些标志是自解释型的 虽然命令带通常显示在窗口顶部, 但是他们可以创建成垂直命令带并显示在窗口左边 在这种情况下,RBBS_NOVERT 风格允许程序员来指定当命令带在垂直方向时, 带区不显示 包含菜单或略宽一些控件的带区是很适合这种标志的, 因为它们在垂直带区上不能被正确显示 clrfore 和 clrback 域中填写的是颜色信息, 在应用程序绘制带区的时候, 会用这些颜色绘制前景色和背景色 需要注意的是, 只有在掩码域中设置了 RBBIM_COLORS 标志时这两个域才有用 对这两个域以及用来为带区指定背景位图的 hbmback 域来说, 只有在带区包含透明命令
条的时候才有用 否则命令条会占据带区的大部分空间, 并遮盖任何背景位图或者颜色 在 配置单独带区 一节中, 我将解释如何使命令条透明 lptext 域指定了用来标记单个带区的可选文本 这些文字将直接显示在带区条的左边 iimage 域用于指定一个显示在带区条左边的位图, 使用包含在图像列表控件中的图象列表的索引来填充 iimage 域 当和命令带控件的 RBS_SMARTLABELS 风格配套使用的时候, 文字和位图显地更加重要 指定了 RBS_SMARTLABELS 风格以后, 如果带区被还原或者最大化, 文字就会显示出来 ; 如果带区被最小化, 位图就显示出来 该技术被 H/PC Explorer 用在它的命令带控件上 wid 域中填充的是带区的 ID 值 带区 ID 是很重要的, 如果您计划在创建带区后配置它们或者想查询它们的状态的话 即使您不打算在程序里使用带区 ID, 保持每个带区 ID 唯一也是很重要的, 因为控件自身使用该 ID 来管理带区 只有在 fmask 域中设置了 RBBIM_ID 标志时才会检查 wid 域 如果带区中默认的命令条控件需要被另一个控件替代的话,hwndChild 域就是很有用的了 为了替换命令条控件, 必须首先创建新控件, 再把该控件的窗口句柄放到 hwndchild 域中 只有在 fmask 域中设置了 RBBIM_CHILD 标志时才会检查 hwndchild 域 cxminchild 和 cyminchild 域中定义了带区可以缩小到的最小尺寸 当使用默认命令条以外的控件时, 这两个域对定义带区的高度和最小宽度很有用 只有在 fmask 域中设置了 RBBIM_CHILDSIZE 标志时才会检查这两个域 当带区被用户最大化的时候, 会使用 cxideal 域 如果这个域没有初始化, 命令带最大化时会伸展到整个控件宽度 通过设置 cxideal, 应用程序可以限制带区最大化的宽度, 这对于带区上的控件只占整个带区宽度一部分的情况来说是很方便的 只有在 fmask 域中设置了 RBBIM_IDEALSIZE 标志时才会检查这个域 lparam 域提供了一个空间来存储由应用程序定义的关于带区信息的值 只有在 fmask 域中设置了 RBBIM_LPARAM 标志时才会检查这个域 REBARBANDINFO 中的其它域多用于更灵活的 rebar 控件, 而不仅仅是命令带控件 下面的代码创建了一个命令带控件, 初始化了一个带三个 REBARBANINFO 结构的数组并把带区加到控件里 Create a command bands control. hwndcb = CommandBands_Create (hinst, hwnd, IDC_CMDBAND, RBS_SMARTLABELS RBS_VARHEIGHT, himl); Initialize common REBARBANDINFO structure fields. for (i = 0; i < dim(rbi); i++) { rbi[i].cbsize = sizeof (REBARBANDINFO); rbi[i].fmask = RBBIM_ID RBBIM_IMAGE RBBIM_SIZE RBBIM_STYLE; rbi[i].fstyle = RBBS_FIXEDBMP; rbi[i].wid = IDB_CMDBAND+i; Initialize REBARBANDINFO structure for each band. 1. Menu band. rbi[0].fstyle = RBBS_NOGRIPPER;
rbi[0].cx = 130; rbi[0].iimage = 0; 2. Standard button band. rbi[1].fmask = RBBIM_TEXT; rbi[1].cx = 200; rbi[1].iimage = 1; rbi[1].lptext = TEXT ("Std Btns"); 3. Edit control band. hwndchild = CreateWindow (TEXT ("edit"), TEXT ("edit ctl"), WS_VISIBLE WS_CHILD WS_BORDER, 0, 0, 10, 5, hwnd, (HMENU)IDC_EDITCTL, hinst, NULL); rbi[2].fmask = RBBIM_TEXT RBBIM_STYLE RBBIM_CHILDSIZE RBBIM_CHILD; rbi[2].fstyle = RBBS_CHILDEDGE; rbi[2].hwndchild = hwndchild; rbi[2].cxminchild = 0; rbi[2].cyminchild = 25; rbi[2].cychild = 55; rbi[2].cx = 130; rbi[2].iimage = 2; rbi[2].lptext = TEXT ("Edit field"); Add bands. CommandBands_AddBands (hwndcb, hinst, 3, rbi); 上面的代码中创建的命令带控件有三个带区, 一个包含一个菜单, 一个包含一组按钮, 一个则使用编辑控件代替了命令条 使用 RBS_SMARTLABELS 和 RBS_VARHEIGHT 风格创建了控件 当命令条最小化的时候, 智能标签显示一个图标 ; 当不在最小化状态时, 显示文字图标 RBS_VARHEIGHT 风格则允许控件上的每行有不同的高度 在一个循环里初始化了 REBARBANINFO 结构中共用的域 接下来, 结构中剩余的域根就据控件中的每个带区进行定制 包含编辑控件的第三个带区初始化是最复杂的 该带区需要更多的初始化, 因为编辑控件需要适当的调整尺寸来匹配其它带区中命令条控件的标准高度 使用图像列表索引来初始化每个带区中的 iimage 域 该图像列表创建并传递进了 CommandBands_Create 函数 用在第二和第三个带区中的文本域被标记所填充 包含菜单的第一个带区中并不包含文本标记, 因为菜单不需要标记 可以对第一个带区使用 RBBS_NOGRIPPER 风格, 这样它就不能在控件上移动了 这可以使菜单带区固定显示在控件中适当的位置上 既然我们已经创建了带区, 那是时候来看看如何初始化它们了 ( 待续 ) Windows CE 程序设计 (3rd 版 )--5.2 公共控件 ( 七 )
配置单独带区译 :tellmenow 翻 进行到这里时, 命令带控件已经创建, 单独单区已经加到控件中了 接下来我们有更多的任务要做, 就是去配置每个带区中单独的命令条控件 ( 实际上, 配置命令条控件比起前面讲述的命令条要略微复杂一些 ) 可以使用下面的函数来获取带区中的命令条句柄 : HWND CommandBands_GetCommandBar (HWND hwndcmdbands, UINT uband); ubnad 是包含该命令条的带区的基于 0 的索引 当命令带控件被初始化时调用该函数的话, 索引值直接同带区加到控件的顺序相关联 然而, 一旦用户有机会拖拽带区到一个新的顺序, 那您的应用程序必须通过发送 RB_IDTOINDEX 消息给命令带控件, 以获取索引值, 如下所示 : nindex = SendMessage (hwndcmdbands, RB_INTOINDEX, ID_BAND,0); 这个消息对管理带区是很重要的, 因为许多函数和消息都需要使用带区索引来识别带区 问题在于索引值是不固定的, 因为用户移动带区导致索引值变化 不要期望索引值是连贯的 作为一个规则, 在没有用 RB_IDTOINDEX 查询索引值之前, 不要盲目使用索引值 一旦您获得命令条窗口句柄, 使用标准的命令条控件函数和消息, 就可以很简单地把菜单或者按钮加到命令条中了 大部分情况下, 在第一个命令条中只加入菜单, 在第二个中只加入按钮, 将其它控件加到第三个及后续命令条中 下面的代码完成了前面提到的创建过程 首先初始化了头两个带区中的命令条控件 因为第三个带区有编辑控件, 所以不需要初始化该带区 最后一行代码是调用 CommandBands_AddAdornments 函数将关闭按钮加到控件中 Add menu to first band. hwndband = CommandBands_GetCommandBar (hwndcb, 0); CommandBar_InsertMenubar (hwndband, hinst, ID_MENU, 0); Add standard buttons to second band. hwndband = CommandBands_GetCommandBar (hwndcb, 1); CommandBar_AddBitmap (hwndband, HINST_COMMCTRL, IDB_STD_SMALL_COLOR, 15, 0, 0); CommandBar_AddButtons (hwndband, dim(tbcbstdbtns), tbcbstdbtns); Add exit button to command band. CommandBands_AddAdornments (hwndcb, hinst, 0, NULL); Windows CE 程序设计 (3rd 版 )--5.2 公共控件 ( 八 ) 保存带区布局 翻译 :tellmenow 命令带控件的可配置能力给程序员带来一个问题 用户重新排列带区后, 希望定制的布局在应用 程序下一次启动的时候能够恢复 通过使用下面的函数, 可以轻易的达到目的 : BOOL CommandBans_GetRestoreInformation (HWND hwndcmdbands, UINT
uband, LPCOMMANDBANDSRESTOREINFO pcbr); 该函数将单个带区的信息保存到 COMMANDBANDSRESTOREINFO 结构中 该函数有两个参数, 一个是命令带控件句柄, 一个是将要查询的带区的索引值 下面的代码片段展示了如何查询命令带控件中每个带区的信息 Get the handle of the command bands control. hwndcb = GetDlgItem (hwnd, IDC_CMDBAND); Get information for each band. for (i = 0; i < NUMBANDS; i++) { Get band index from ID value. nband = SendMessage (hwndcb, RB_IDTOINDEX, IDB_CMDBAND+i, 0); Initialize the size field, and get the restore information. cbr[i].cbsize = sizeof (COMMANDBANDSRESTOREINFO); CommandBands_GetRestoreInformation (hwndcb, nband, &cbr[i]); 上面的代码使用 RB_IDTOINDEX 消息, 将带区 ID 转化成带区索引, 以用于 CommandBands_GetRestoreInformation 结构中的数据通常存储在系统注册表中 我将在第 8 章 文件和注册表 中讲解如何读写注册表数据 当应用程序恢复的时候, 应该从注册表中读取恢复信息, 并在创建命令带控件的时候使用 Restore configuration to a command band. COMMANDBANDSRESTOREINFO cbr[numbands]; REBARBANDINFO rbi; Initialize size field. rbi.cbsize = sizeof (REBARBANDINFO); Set only style and size fields. rbi.fmask = RBBIM_STYLE RBBIM_SIZE; Set the size and style for all bands. for (i = 0; i < NUMBANDS; i++) { rbi.cx = cbr[i].cxrestored; rbi.fstyle = cbr[i].fstyle; nband = SendMessage (hwndcb, RB_IDTOINDEX, cbr[i].wid, 0); SendMessage (hwndcb, RB_SETBANDINFO, nband, (LPARAM)&rbi); Only after the size is set for all bands can the bands needing maximizing be maximized. for (i = 0; i < NUMBANDS; i++) { if (cbr[i].fmaximized) {
nband = SendMessage (hwndcb, RB_IDTOINDEX, cbr[i].wid, 0); SendMessage (hwndcb, RB_MAXIMIZEBAND, nband, TRUE); 上面的代码假设命令带控件已经按照默认配置创建 在实际应用中, 尺寸和风格的恢复信息用于最初创建控件的时候 在那种情况下, 剩余的将根据 COMMANDBANDSRESTOREINFO 结构中 fmaximized 域的状态来决定是否最大化带区 只有在所有的带区被创建和适当的调整尺寸后, 才做上面代码中的最后一步 该系统对保存和恢复带区的布局有一个限制, 就是您没有办法判断带区在控件中的顺序 带区索引不能提供可靠的信息, 因为经过用户几次重新安排带区后, 索引值既不连续也不是按任何定义的顺序 解决该问题的唯一方式是强制排列带区, 这样用户就不能重新排列带区 通过设置 RBS_FIXEDORDER 风格, 可以达到这个目的 这样可以解决这个问题, 但如果用户想要一个不同的顺序, 就不能够给用户提供帮助了 在本节最后的例子程序中, 我使用带区索引值来猜测顺序 但这个方法不能保证一定能有效 Windows CE 程序设计 (3rd 版 )--5.2 公共控件 ( 九 ) 处理命令带消息翻译 :tellmenow 同起命令条相比, 命令带控件需要更多一些维护 差别在于, 命令带控件可以改变高度, 这样就要求包含命令带控件的窗口必须监视控件, 并且在控件尺寸变化时重新绘制窗口, 很可能还要格式化其客户区 当用户重新排列控件的时候, 命令带控件会发送许多不同的 WM_NOTIFY 消息 为了监控控件高度, 应用程序需要检查 RBN_HEIGHCHANGE 通知消息并做相应回应 下面的代码演示这个过程 : This code is inside a WM_NOTIFY message handler. LPNMHDR pnmh; pnmh = (LPNMHDR)lParam; if (pnmh->code == RBN_HEIGHTCHANGE) { InvalidateRect (hwnd, NULL, TRUE); 如果检测到 RBN_HEIGHTCHANGE 消息通知, 例程就简单的使窗口客户区无效, 产生一个 WM_PAINT 消息 接下来在处理绘制消息的代码中调用 UINT CommandBands_Height (HWND hwndcmdbands) 来查询命令带控件的高度, 并从客户区矩形中减去该高度 和命令条一样, 使用 BOOL CommandBands_Show (HWND HwndCmdBands, BOOL fshow) 可以隐藏或者显示命令带控件 通过调用函数 BOOL CommandBands_IsVisible (HWND hwndcmdbands) 可以查询控件的可视状态 CmdBand 示例 CmdBand 程序演示了一个相当完整的命令带控件 示例中创建了三个带区, 一个是固定菜单带区, 一个是包含许多按钮的带区, 一个是包含编辑控件的带区 每个带区中的透明命令条和背景位图被用在创建带背景图的命令带控件中
通过选择 View 菜单中的 Command Bar 菜单项, 可以使用一个简单的命令条来替换命令带控件 通过选择 View 菜单中的 Command Bands, 可以将命令带控件重新创建并恢复到上次的配置状态 CmdBand 程序代码显示在清单 5-2 中 清单 5-2:CmdBand 程序 CmdBand.rc ================================================= ===================== Resource file Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include "windows.h" #include "CmdBand.h" Program-specific stuff ---------------------------------------------------------------------- Icons and bitmaps ID_ICON ICON "cmdband.ico" Program icon CmdBarBmps BITMAP "cbarbmps.bmp" Bmp used in cmdband image list CmdBarEditBmp BITMAP "cbarbmp2.bmp" Bmp used in cmdband image list CmdBarBack BITMAP "backg2.bmp" Bmp used for cmdband background ---------------------------------------------------------------------- Menu ID_MENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", IDM_EXIT END POPUP "&View" BEGIN MENUITEM "Command Bar", IDM_VIEWCMDBAR MENUITEM "Command Band", IDM_VIEWCMDBAND END POPUP "&Help" BEGIN MENUITEM "&About...", END END IDM_ABOUT
---------------------------------------------------------------------- About box dialog template aboutbox DIALOG discardable 10, 10, 160, 40 STYLE WS_POPUP WS_VISIBLE WS_CAPTION WS_SYSMENU DS_CENTER DS_MODALFRAME CAPTION "About" BEGIN ICON ID_ICON, -1, 5, 5, 10, 10 LTEXT "CmdBand - Written for the book Programming Windows \ CE Copyright 2003 Douglas Boling" -1, 40, 5, 110, 30 END CmdBand.h ================================================= ===================== Header file Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== Returns number of elements #define dim(x) (sizeof(x) / sizeof(x[0])) ---------------------------------------------------------------------- Generic defines and data types struct decodeuint { Structure associates UINT Code; messages with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); ; struct decodecmd { Structure associates UINT Code; menu IDs with a LRESULT (*Fxn)(HWND, WORD, HWND, WORD); function. ; ---------------------------------------------------------------------- Defines used by application #define IDC_CMDBAND 1 Command band ID #define IDC_CMDBAR 2 Command bar ID #define ID_ICON 10 Icon ID
#define ID_MENU 11 Main menu resource ID #define IDC_EDITCTL 12 #define IDB_CMDBAND 50 Base ID for bands #define IDB_CMDBANDMENU 50 Menu band ID #define IDB_CMDBANDBTN 51 Button band ID #define IDB_CMDBANDEDIT 52 Edit control band ID Menu item IDs #define IDM_EXIT 100 #define IDM_VIEWCMDBAR 110 #define IDM_VIEWCMDBAND 111 #define IDM_ABOUT 120 #define NUMBANDS 3 ---------------------------------------------------------------------- Function prototypes int CreateCommandBand (HWND hwnd, BOOL ffirst); int DestroyCommandBand (HWND hwnd); HWND InitInstance (HINSTANCE, LPWSTR, int); int TermInstance (HINSTANCE, int); Window procedures LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM); Message handlers LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoNotifyMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoCommandMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM); Command functions LPARAM DoMainCommandViewCmdBar (HWND, WORD, HWND, WORD); LPARAM DoMainCommandVCmdBand (HWND, WORD, HWND, WORD); LPARAM DoMainCommandExit (HWND, WORD, HWND, WORD); LPARAM DoMainCommandAbout (HWND, WORD, HWND, WORD); Dialog procedures BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM); CmdBand.cpp
================================================= ===================== CmdBand - Dialog box demonstration Written for the book Programming Windows CE Copyright (C) 2003 Douglas Boling ================================================= ===================== #include <windows.h> For all that Windows stuff #include <commctrl.h> Command bar includes #include "CmdBand.h" Program-specific stuff ---------------------------------------------------------------------- Global data const TCHAR szappname[] = TEXT ("CmdBand"); HINSTANCE hinst; Program instance handle Message dispatch table for MainWindowProc const struct decodeuint MainMessages[] = { WM_CREATE, DoCreateMain, WM_PAINT, DoPaintMain, WM_NOTIFY, DoNotifyMain, WM_COMMAND, DoCommandMain, WM_DESTROY, DoDestroyMain, ; Command message dispatch for MainWindowProc const struct decodecmd MainCommandItems[] = { IDM_VIEWCMDBAR, DoMainCommandViewCmdBar, IDM_VIEWCMDBAND, DoMainCommandVCmdBand, IDM_EXIT, DoMainCommandExit, IDM_ABOUT, DoMainCommandAbout, ; Command band button initialization structure const TBBUTTON tbcbstdbtns[] = { BitmapIndex Command State Style UserData String {STD_FILENEW, 210, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {STD_FILEOPEN, 211, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {STD_FILESAVE, 212, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, {STD_CUT, 213, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {STD_COPY, 214, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {STD_PASTE, 215, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0,
; {STD_PROPERTIES, 216, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, Command bar initialization structure const TBBUTTON tbcbviewbtns[] = { BitmapIndex Command State Style UserData String {0, 0, 0, TBSTYLE_SEP, 0, 0, {VIEW_LARGEICONS, 210, TBSTATE_ENABLED TBSTATE_CHECKED, TBSTYLE_CHECKGROUP, 0, 0, {VIEW_SMALLICONS, 211, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0, {VIEW_LIST, 212, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0, {VIEW_DETAILS, 213, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0, {0, 0, 0, TBSTYLE_SEP, 0, 0, {VIEW_SORTNAME, 214, TBSTATE_ENABLED TBSTATE_CHECKED, TBSTYLE_CHECKGROUP, 0, 0, {VIEW_SORTTYPE, 215, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0, {VIEW_SORTSIZE, 216, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0, {VIEW_SORTDATE, 217, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, 0, 0 ; Array that stores the band configuration COMMANDBANDSRESTOREINFO cbr[numbands]; INT nbandorder[numbands]; ================================================= ===================== Program entry point int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance, LPWSTR lpcmdline, int ncmdshow) { HWND hwndmain; MSG msg; Initialize application. hwndmain = InitInstance (hinstance, lpcmdline, ncmdshow); if (hwndmain == 0) return 0x10; Application message loop
while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); Instance cleanup return TermInstance (hinstance, msg.wparam); ---------------------------------------------------------------------- InitInstance - Instance initialization HWND InitInstance (HINSTANCE hinstance, LPWSTR lpcmdline, int ncmdshow){ HWND hwnd; WNDCLASS wc; INITCOMMONCONTROLSEX icex; Save program instance handle in global variable. hinst = hinstance; #if defined(win32_platform_pspc) If Pocket PC, allow only one instance of the application. hwnd = FindWindow (szappname, NULL); if (hwnd) { SetForegroundWindow ((HWND)(((DWORD)hWnd) 0x01)); return 0; #endif Register application main window class. wc.style = 0; Window style wc.lpfnwndproc = MainWndProc; Callback function wc.cbclsextra = 0; Extra class data wc.cbwndextra = 0; Extra window data wc.hinstance = hinstance; Owner handle wc.hicon = NULL, Application icon wc.hcursor = LoadCursor (NULL, IDC_ARROW); Default cursor wc.hbrbackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszmenuname = NULL; Menu name wc.lpszclassname = szappname; Window class name if (RegisterClass (&wc) == 0) return 0; Load the command bar common control class. icex.dwsize = sizeof (INITCOMMONCONTROLSEX); icex.dwicc = ICC_COOL_CLASSES; InitCommonControlsEx (&icex);
Create main window. hwnd = CreateWindow (szappname, TEXT ("CmdBand Demo"), WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL); Return fail code if window not created. if (!IsWindow (hwnd)) return 0; Standard show and update calls ShowWindow (hwnd, ncmdshow); UpdateWindow (hwnd); return hwnd; ---------------------------------------------------------------------- TermInstance - Program cleanup int TermInstance (HINSTANCE hinstance, int ndefrc) { return ndefrc; ================================================= ===================== Message handling procedures for MainWindow ---------------------------------------------------------------------- MainWndProc - Callback function for application window LRESULT CALLBACK MainWndProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { INT i; Search message list to see if we need to handle this message. If in list, call procedure. for (i = 0; i < dim(mainmessages); i++) { if (wmsg == MainMessages[i].Code) return (*MainMessages[i].Fxn)(hWnd, wmsg, wparam, lparam); return DefWindowProc (hwnd, wmsg, wparam, lparam); ---------------------------------------------------------------------- DoCreateMain - Process WM_CREATE message for window.
LRESULT DoCreateMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { CreateCommandBand (hwnd, TRUE); return 0; ---------------------------------------------------------------------- DoPaintMain - Process WM_PAINT message for window. LRESULT DoPaintMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PAINTSTRUCT ps; HWND hwndcb; RECT rect; HDC hdc; POINT ptarray[2]; Adjust the size of the client rect to take into account the command bar or command bands height. GetClientRect (hwnd, &rect); if (hwndcb = GetDlgItem (hwnd, IDC_CMDBAND)) rect.top += CommandBands_Height (hwndcb); else rect.top += CommandBar_Height (GetDlgItem (hwnd, IDC_CMDBAR)); hdc = BeginPaint (hwnd, &ps); ptarray[0].x = rect.left; ptarray[0].y = rect.top; ptarray[1].x = rect.right; ptarray[1].y = rect.bottom; Polyline (hdc, ptarray, 2); ptarray[0].x = rect.right; ptarray[1].x = rect.left; Polyline (hdc, ptarray, 2); EndPaint (hwnd, &ps); return 0; ---------------------------------------------------------------------- DoCommandMain - Process WM_COMMAND message for window. LRESULT DoCommandMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { WORD iditem, wnotifycode;
HWND hwndctl; INT i; Parse the parameters. iditem = (WORD) LOWORD (wparam); wnotifycode = (WORD) HIWORD (wparam); hwndctl = (HWND) lparam; Call routine to handle control message. for (i = 0; i < dim(maincommanditems); i++) { if (iditem == MainCommandItems[i].Code) return (*MainCommandItems[i].Fxn)(hWnd, iditem, hwndctl, wnotifycode); return 0; ---------------------------------------------------------------------- DoNotifyMain - Process WM_NOTIFY message for window. LRESULT DoNotifyMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { LPNMHDR pnmh; Parse the parameters. pnmh = (LPNMHDR)lParam; if (pnmh->code == RBN_HEIGHTCHANGE) { InvalidateRect (hwnd, NULL, TRUE); return 0; ---------------------------------------------------------------------- DoDestroyMain - Process WM_DESTROY message for window. LRESULT DoDestroyMain (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { PostQuitMessage (0); return 0; ================================================= ===================== Command handler routines
---------------------------------------------------------------------- DoMainCommandExit - Process Program Exit command. LPARAM DoMainCommandExit (HWND hwnd, WORD iditem, HWND hwndctl, WORD wnotifycode) { SendMessage (hwnd, WM_CLOSE, 0, 0); return 0; ---------------------------------------------------------------------- DoMainCommandVCmdBarStd - Process View Std Command bar command. LPARAM DoMainCommandViewCmdBar (HWND hwnd, WORD iditem, HWND hwndctl, WORD wnotifycode) { HWND hwndcb; hwndcb = GetDlgItem (hwnd, IDC_CMDBAND); if (hwndcb) DestroyCommandBand (hwnd); else return 0; Create a minimal command bar that has only a menu and an exit button. hwndcb = CommandBar_Create (hinst, hwnd, IDC_CMDBAR); Insert the menu. CommandBar_InsertMenubar (hwndcb, hinst, ID_MENU, 0); Add exit button to command bar. CommandBar_AddAdornments (hwndcb, 0, 0); InvalidateRect (hwnd, NULL, TRUE); return 0; ---------------------------------------------------------------------- DoMainCommandVCmdBand - Process View Command band command. LPARAM DoMainCommandVCmdBand (HWND hwnd, WORD iditem, HWND hwndctl, WORD wnotifycode) { HWND hwndcb; hwndcb = GetDlgItem (hwnd, IDC_CMDBAR); if (hwndcb) CommandBar_Destroy (hwndcb); else
return 0; CreateCommandBand (hwnd, FALSE); InvalidateRect (hwnd, NULL, TRUE); return 0; ---------------------------------------------------------------------- DoMainCommandAbout - Process the Help About menu command. LPARAM DoMainCommandAbout(HWND hwnd, WORD iditem, HWND hwndctl, WORD wnotifycode) { Use DialogBox to create modal dialog box. DialogBox (hinst, TEXT ("aboutbox"), hwnd, AboutDlgProc); return 0; ================================================= ===================== About Dialog procedure BOOL CALLBACK AboutDlgProc (HWND hwnd, UINT wmsg, WPARAM wparam, LPARAM lparam) { switch (wmsg) { case WM_COMMAND: switch (LOWORD (wparam)) { case IDOK: case IDCANCEL: EndDialog (hwnd, 0); return TRUE; break; return FALSE; ---------------------------------------------------------------------- DestroyCommandBand - Destroy command band control after saving the current configuration. int DestroyCommandBand (HWND hwnd) { HWND hwndcb; INT i, nband, nmaxband = 0; hwndcb = GetDlgItem (hwnd, IDC_CMDBAND); for (i = 0; i < NUMBANDS; i++) {
Get band index from ID value. nband = SendMessage (hwndcb, RB_IDTOINDEX, IDB_CMDBAND+i, 0); Save the band number to save order of bands. nbandorder[i] = nband; Get the restore information. cbr[i].cbsize = sizeof (COMMANDBANDSRESTOREINFO); CommandBands_GetRestoreInformation (hwndcb, nband, &cbr[i]); DestroyWindow (hwndcb); return 0; ---------------------------------------------------------------------- CreateCommandBand - Create a formatted command band control. int CreateCommandBand (HWND hwnd, BOOL ffirst) { HWND hwndcb, hwndband, hwndchild; INT i, nband, nbtnindex, neditindex; LONG lstyle; HBITMAP hbmp; HIMAGELIST himl; REBARBANDINFO rbi[numbands]; Create image list control for bitmaps for minimized bands. himl = ImageList_Create (16, 16, ILC_COLOR, 3, 0); Load first two images from one bitmap. hbmp = LoadBitmap (hinst, TEXT ("CmdBarBmps")); ImageList_Add (himl, hbmp, NULL); DeleteObject (hbmp); Load third image as a single bitmap. hbmp = LoadBitmap (hinst, TEXT ("CmdBarEditBmp")); ImageList_Add (himl, hbmp, NULL); DeleteObject (hbmp); Create a command band. hwndcb = CommandBands_Create (hinst, hwnd, IDC_CMDBAND, RBS_SMARTLABELS RBS_AUTOSIZE RBS_VARHEIGHT, himl); Load bitmap used as background for command bar. hbmp = LoadBitmap (hinst, TEXT ("CmdBarBack")); Initialize common REBARBANDINFO structure fields. for (i = 0; i < dim(rbi); i++) {
rbi[i].cbsize = sizeof (REBARBANDINFO); rbi[i].fmask = RBBIM_ID RBBIM_IMAGE RBBIM_SIZE RBBIM_BACKGROUND RBBIM_STYLE; rbi[i].wid = IDB_CMDBAND+i; rbi[i].hbmback = hbmp; If first time, initialize the restore structure since it is used to initialize the band size and style fields. if (ffirst) { nbtnindex = 1; neditindex = 2; cbr[0].cxrestored = 130; cbr[1].cxrestored = 210; cbr[1].fstyle = RBBS_FIXEDBMP; cbr[2].cxrestored = 130; cbr[2].fstyle = RBBS_FIXEDBMP RBBS_CHILDEDGE; else { If not first time, set order of bands depending on the last order. if (nbandorder[1] < nbandorder[2]) { nbtnindex = 1; neditindex = 2; else { nbtnindex = 2; neditindex = 1; Initialize REBARBANDINFO structure for each band. 1. Menu band rbi[0].fstyle = RBBS_FIXEDBMP RBBS_NOGRIPPER; rbi[0].cx = cbr[0].cxrestored; rbi[0].iimage = 0; 2. Standard button band rbi[nbtnindex].fmask = RBBIM_TEXT; rbi[nbtnindex].iimage = 1; rbi[nbtnindex].lptext = TEXT ("Std Btns"); The next two parameters are initialized from saved data. rbi[nbtnindex].cx = cbr[1].cxrestored; rbi[nbtnindex].fstyle = cbr[1].fstyle; 3. Edit control band hwndchild = CreateWindow (TEXT ("edit"), TEXT ("edit ctl"),
WS_VISIBLE WS_CHILD ES_MULTILINE WS_BORDER, 0, 0, 10, 5, hwnd, (HMENU)IDC_EDITCTL, hinst, NULL); rbi[neditindex].fmask = RBBIM_TEXT RBBIM_STYLE RBBIM_CHILDSIZE RBBIM_CHILD; rbi[neditindex].hwndchild = hwndchild; rbi[neditindex].cxminchild = 0; rbi[neditindex].cyminchild = 23; rbi[neditindex].cychild = 55; rbi[neditindex].iimage = 2; rbi[neditindex].lptext = TEXT ("Edit field"); The next two parameters are initialized from saved data. rbi[neditindex].cx = cbr[2].cxrestored; rbi[neditindex].fstyle = cbr[2].fstyle; Add bands. CommandBands_AddBands (hwndcb, hinst, 3, rbi); Add menu to first band. hwndband = CommandBands_GetCommandBar (hwndcb, 0); CommandBar_InsertMenubar (hwndband, hinst, ID_MENU, 0); Add standard buttons to second band. hwndband = CommandBands_GetCommandBar (hwndcb, nbtnindex); Insert buttons CommandBar_AddBitmap (hwndband, HINST_COMMCTRL, IDB_STD_SMALL_COLOR, 16, 0, 0); CommandBar_AddButtons (hwndband, dim(tbcbstdbtns), tbcbstdbtns); Modify the style flags of each command bar to make transparent. for (i = 0; i < NUMBANDS; i++) { hwndband = CommandBands_GetCommandBar (hwndcb, i); lstyle = SendMessage (hwndband, TB_GETSTYLE, 0, 0); lstyle = TBSTYLE_TRANSPARENT; SendMessage (hwndband, TB_SETSTYLE, 0, lstyle); If not the first time the command band has been created, restore the user's last configuration. if (!ffirst) { for (i = 0; i < NUMBANDS; i++) { if (cbr[i].fmaximized) { nband = SendMessage (hwndcb, RB_IDTOINDEX, cbr[i].wid, 0); SendMessage (hwndcb, RB_MAXIMIZEBAND, nband, TRUE);
Add exit button to command band. CommandBands_AddAdornments (hwndcb, hinst, 0, NULL); return 0; CmdBand 示例中, 在 CreateCommandBand 例程里创建了命令带 该例程最初在 DoCreateMain 中被调用, 随后又在菜单处理函数 DoMain-CommandVCmdBand 中被调用 程序使用 RBS_SMARTLABELS 风格创建了命令带控件, 在最小化和被还原或最大化时使用图像列表和文本标签来标记每个带区 图像列表被创建, 并使用位图初始化, 当带区被最小化时需要使用这些位图 REBARBANDINFO 结构数组被初始化, 用来定义三个带区 如果控件之前被销毁, 那么来自 COMMANDBANDSRESTOREINFO 结构的数据被用来初始化风格域以及 cx 域 CreateCommandBand 例程中, 通过查看控件最后一次被销毁时保存的带区索引, 对按钮和编辑带区的顺序做了一个假设 虽然用这个方法来判断带区之前的顺序不完全可靠, 但它为您提供了一个很好的参考评估 当命令带控件创建以后, 会修改每个带区中的命令条来设置 TBS_TRANSPARENT 风格 这个过程中, 通过使用每个带区的背景位图, 演示了如何使用背景位图来使命令带控件有合适的外观 当程序 CmdBand 使用命令条来替换命令带区控件时, 应用程序首先调用 DestroyCommandBand 函数来保存当前配置信息, 并销毁命令带控件 该函数使用 CommandBands_GetRestoreInformation 来查询每个带区的尺寸和风格 该函数还保存了每个带区的索引, 作为将来推测按钮和编辑带区当前顺序的数据 第一个带区 -- 菜单带区 -- 使用的是 RBBS_NOGRIPPER 风格, 所以不存在位置问题