本篇文章讲解的是开发 Windows Mobile 上的今日插件。关于是今日插件,在 PPC 或者 SP SDK 的帮助文档中有相关的章节介绍,在网络上也有一些帖子和资源讲解。在这里简要回顾一下。今日插件就是在windows mobile的桌面上显示的条目,例如系统提供的“日历”(Calendar),主人信息,以及许多第三方开发的今日插件等等。由于桌面是开机后的第一个 屏幕,所以插件显示在屏幕上将会得到用户最多的浏览时间。这里我们主要讲解的是用户自定义插件的开发。自定义插件如下图所示:

一 个插件就是一个位于屏幕上的窗口,每个插件负责自己的绘制和对用户输入的响应。插件可以通过给自己的父窗口(桌面窗口)发送 TODAYM_DRAWWATERMARK 消息,委托父窗口为自己绘制背景,也就是通过把自己的HDC传递给shell来完成的,这样插件就看起来好像是“透明”的效果。同时,shell 也负责在相邻的插件之间绘制一条分割线。
      通常,PPC 最多允许加载 12 个插件。最大插件数量是由 K_cTodayItemsMax 定义的。
      对于自定义的插件,要求开发者提供一个DLL函数并注册到注册表: HKLM/Softeware/Microsoft/Today/Items;
      如下图,我们使用远程注册表查看工具打开一个插件的在注册表中的位置:

在下面包含了所有今日插件的键。每个插件将含有下列的值:

◆Type (DWORD);
      对自定义插件来说,等于4。它是 SDK 中的插件类型枚举中的一个值( tlitCustom)。 
      typedef enum _TODAYLISTITEMTYPE { 
            tlitOwnerInfo = 0, 
            tlitAppointments, 
            tlitMail,
            tlitTasks,
            tlitCustom, //自定义插件 = 4
            tlitNil
            } TODAYLISTITEMTYPE;

◆Enabled;
      插件是否启用。用户能够在设置-今日-项目中进行启用或禁用。

◆Options;
      是否含有设置对话框。也就是设置-今日-项目中插件被选中时的 “选项按钮” 的 Enabled 状态。

◆DLL:
      插件dll的路径。

◆Selectability;
      可选项,插件是否可以被选中(用户在屏幕上按导航键时)。通常为1,表示允许被选中。当允许选中时,用户按上下方向键,被选中的插件背景会高亮。如果不能选中,就会跳过该插件。

◆Order;
      可选项,插件显示循序的排序值。缺省时由系统自动设置。

下面我们再介绍插件的协议,也就是插件的DLL应当满足以下要求。

(1)要求 dll 导出序号为 240 的以下函数,以初始化和创建插件窗口;
      #define ORDINAL_INITIALIZEITEM      240
      typedef HWND (*PFNCUSTOMINITIALIZEITEM)(TODAYLISTITEM *, HWND);
      参数1:TODAYLISTITEM 结构的指针,包含了该插件在系统中注册的相关信息。
      参数2:桌面窗口的句柄,它将成为插件窗口的父窗口。

(2)如果插件具有设置对话框,则要求dll导出序号为 241 的以下函数,作为设置对话框的窗口过程;
      #define ORDINAL_OPTIONSDIALOGPROC   241
      typedef BOOL (*PFNCUSTOMOPTIONSDLGPROC)(HWND, UINT, UINT, LONG);
      同时要求dll 提供资源ID为 500的一个对话框资源作为设置对话框的模板。(可以通过手工修改 resource.h 中的定义)

(3)shell 将向插件窗口发送以下信息,要求插件处理这些消息;
      WM_TODAYCUSTOM_CLEARCACHE
      告知插件正在被从显示中卸载,要求插件清理自己所维护的数据缓存。

WM_TODAYCUSTOM_QUERYREFRESHCACHE
      此消息在桌面显示期间以每2秒钟一次的频率周期性对所有插件发送。询问插件是否需要进行更新。
      在插件首次加载时,还要求插件告知系统插件的高度以对插件窗口进行布局。由于插件窗口被上下垂直分布,所以宽度对于系统属于已知的。如果返回TRUE,表示要求进行更新。如果不需要更新返回FALSE。

(4)同时插件还能够向父窗口发送以下消息,以辅助绘制。
      TODAYM_GETCOLOR
      询问系统当前使用的前景色,背景色,高亮前景色等信息。以便绘制时,和系统使用的主题风格保持一致。

TODAYM_DRAWWATERMARK
      要求shell 为插件绘制背景。也就是把背景位图复制到插件窗口的背景。

好了,关于插件开发规则我们就简要介绍到此。在开发插件时,开发者的主要任务是编写插件窗口/设置对话框的窗口过程,完成属于自己的功能。这里要求具有的是windows开发的一些基础。我们不细作介绍了。下面是我在这几天制作的插件。

(1)根据 SDK 中 范例改编而成的 memWatcher 插件 和 “桌面便笺”。效果如下:

在 左图是SDK中的 memWatcher 范例在模拟器中的显示效果,右图是经过我适当改写后,在实际HTC S1中运行的效果。SDK范例显示了程序和存储的百分比,并且创建了两个进度条窗口显示。经过我的改写,我把进度条去掉了,从而可以把信息压缩到一行以 内,这样可以节省屏幕空间,并且增加了电池电量的显示。
      获取这些信息的相关API函数是:
      GlobalMemoryStatus,GetStoreInformation,GetSystemPowerStatusEx;

下 面我们看下桌面便笺插件,这个插件发布在 pdafans 论坛后,很快就有网友向我反馈了备忘录的内容在重启后消失,这是因为我做的这个插件也仅仅是个范例来使用,测试了我的想法是可行的,所以并没有考虑那么 多。便笺的内容被放到了内存里,声明周期和DLL一样,这也一旦DLL被卸载,存储在内存中的内容也就失去了。这也提醒了我们一点,我们的插件应该将数据 持久化。所以我又修改了这个插件,把备忘信息和图标索引存储到了注册表中,也就是插件注册的键下面新增了两个值。这样我们就可以保证每次插件启动时都会从 注册表中读取出上次的用户记录的内容。
      当用鼠标点击桌面便笺时,就会弹出一个对话框用于设置新的备忘内容,如下图所示:

这 个对话框中具有一点难度和技巧性的是上面的图标选择反馈,全部是通过鼠标点击事件来完成的。我们在对话框的 WM_ONPAINT 消息处理中,在对话框上绘制了所有可选图标,每个图标实际上是16*16像素大小,所以我指定的网格是20*20像素,在每个网格中绘制一个图标,并对被 选中图标绘制了一个蓝色矩形框表示选中状态。当鼠标点击到其他图标时,我们就要更新这个蓝色矩形。同时我们也要根据鼠标位置在网格中正确的定位要当前位置 选中的鼠标索引。这里的处理并不算非常难,但是需要少许的耐心。
      显示和隐藏输入面板,在 .NET CF中,有一个inputPanel控件,我们 可以方便的设置它的Visible属性去控制。而在EVC中,我们是通过下面的API函数去显示或者隐藏SIP的。
      SipShowIM(SIPF_ON) 和 SipShowIM(SIPF_OFF);
      或者我们也可以使用: SHSipPreference(hDlg, SIP_UP) , 去要求 Shell 浮出输入面板。

(2)桌面记单词插件。
      桌 面记单词插件的灵感是来自桌面上的类似工具,即有一个顶层窗口,以一个固定的频率切换词条显示,以帮助用户背单词。我这里就是模拟这种软件的效果做的一个 今日插件。当然它不仅仅可以背单词,也可以显示其他字典内容,例如唐诗宋词,名言名句等等。用户可以自定义字典文件,本质上就是一个文本文件,并通过修改 配置文件把字典添加进来。

为了降低读文件的频率,我在插件内维护了一个词条缓存(缓存10个词 条),每次一次性尝试从文件中加载10个词条文件(每个词条也就是文本文件中的一行)到内存中。当词条正在滚动期间,文件保持打开状态。当暂停滚动时,将 会关闭文件。插件利用每2秒钟接收到的消息去滚动词条。效果如下图所示:

开 发这个插件时,我忽然发现 Pocket PC 的操作系统是不支持读写 ini 文件的相关API函数的。我去网络上找了下相关代码,但是没有看到特别满意的。因此我自己用C语言写了几个和API函数功能相同的读 ini 文件的函数。函数命名也是完全相同的,为了在 PC上进行测试,我在每个函数名前面加了 Ce ,以和系统的API函数区分开。我这里仅仅为了插件功能写了有限的几个函数,这里以 CeGetPrivateProfileString 为例给出代码。
在PC上,这个函数负责读取 ini 文件某个 section 中某个 key 的值。为了同时在 unicode 和 多字节字符串环境中适用,我又把相关的文件和字符串操作函数进行了宏定义,并且以这种方式命名: 
       t_多字节版本函数名
      这是因为对多字节版本的函数我们通常更加熟悉它的命名。例如对于 t_strcpy 等等。下面是这个函数的代码:

Code_CeGetPrivateProfileString
#ifdef  UNICODE    //  r_winnt

#define  t_fopen         _wfopen
     #define  t_fgets            fgetws
     #define  t_sprintf        swprintf     // 格式化文本
     #define  t_strcpy        wcscpy
     #define  t_strncpy        wcsncpy         // 拷贝指定个数的字符
     #define  t_strcat        wcscat         // append a string
     #define  t_strtol        wcstol
     #define  t_strlen        wcslen
     #define  t_strcmp        wcscmp
     #define  t_stricmp        _wcsicmp     // 忽略大小写的字符串比较
     #define  t_strncmp        wcsncmp         // 比较n个字符
     #define  t_strchr        wcschr         // find a character in a string
     #define  t_strrchr        wcsrchr         // 从结尾向前查找字符

#else    // ASCII CODE

#define  t_fopen         fopen
     #define  t_fgets            fgets         // 读取一行文本
     #define  t_sprintf        sprintf         // 格式化文本
     #define  t_strcpy        strcpy
     #define  t_strncpy        strncpy         // 拷贝指定个数的字符
     #define  t_strcat        strcat         // append a string
     #define  t_strtol        strtol         // 把字符串转换成long(int32)
     #define  t_strlen        strlen
     #define  t_strcmp        strcmp         // 比较字符串
     #define  t_stricmp        _stricmp     // 忽略大小写的字符串比较
     #define  t_strncmp        strncmp         // 比较n个字符
     #define  t_strchr        strchr         // 查找字符
     #define  t_strrchr        strrchr         // 从结尾向前查找字符

#endif

#define  LINESIZE    260     // 行缓冲区大小

// 从appname(section)中读取string类型key
DWORD CeGetPrivateProfileString(
    LPCTSTR lpAppName,                 // section name: [lpAppName]
    LPCTSTR lpKeyName,                 // lpKeyName=lpReturnedString
    LPCTSTR lpDefault,                 // 未找到时的默认值
    LPTSTR lpReturnedString,     // [out] 查找到的结果
    DWORD nSize,                             // [in]lpReturnedString的字符数,注意单位不是字节!
    LPCTSTR lpFileName
    )
{
    DWORD ret  =   0 ;
    FILE  * stream;
     bool  bFindVal  =   false ;
     bool  bFindSection  =   false ;
    TCHAR line[ LINESIZE ];
    size_t sectionLength, keyLength, lineLength;
    
    stream  =  t_fopen(lpFileName, _T( " r " ));
     if (stream  ==  NULL)
    {
         // 设置默认值
        t_strcpy(lpReturnedString, lpDefault);
        ret  =  t_strlen(lpReturnedString); 
         return  ret;
    }
    
    sectionLength  =  t_strlen(lpAppName);
    
     while (t_fgets(line, LINESIZE, stream)  !=  NULL)
    {
         // 忽略注释行和空行
         if (line[ 0 ]  ==   0   ||  line[ 0 ]  ==   ' ; ' )  continue ;
        lineLength  =  t_strlen(line);
         // 注意:把LF(0xa)字符替换成0,这在UNICODE环境下可能出现结尾是LF)
         if (line[ lineLength  -   1  ]  ==   0x0a )
        {
            line[ lineLength  -   1  ]  =   0 ;
            lineLength -- ;
             // 注意此时可能会成为空字符串
             if (lineLength  ==   0 )  continue ;
        }
        
         // 尝试寻找到 section
         if ( ! bFindSection)
        {
             if (line[ 0 ]  !=   ' [ ' )  continue ;  // 本行是否是 [section]
             // 这里是我们想要的Section吗?
             // 检查这一行的宽度是否正好是section长度加2, [lpAppName]
             if (line[sectionLength  +   1 ]  !=   ' ] ' )  continue ;
             if (t_strncmp(line + 1 , lpAppName, sectionLength)  !=   0 )  continue ;
             // Now Section will appear on next line  
             // 读取section前求出 Key 的长度
            keyLength  =  t_strlen(lpKeyName);
            bFindSection  =   true ;            
             continue ;
        }
        
         // 查找Key, Section End?
         if (line[ 0 ] == ' [ ' )  break ;  // 遇到了下一个
            
         if (lineLength  <  keyLength + 1   ||  line[keyLength]  !=   ' = ' )  continue ;  // "KeyName= "
         if (t_strncmp(line, lpKeyName, keyLength) != 0 )  continue ;
         // Now We Get the Key! 
        t_strcpy(lpReturnedString, line  +  keyLength  +   1 );
         // Now It's done.
        bFindVal  =   true ;
         break ;
    }
    
    fclose(stream);
     if ( ! bFindVal)
    {
         // 设置默认值
        t_strcpy(lpReturnedString, lpDefault); 
    }
    ret  =  t_strlen(lpReturnedString); 
     return  ret;
}

下面是我提供了一个演示程序,由于我们知道了插件的协议,所以我们也可以显示出其他插件的选项对话框,为了更具可读性,代码经过了精简。

Code_显示其他插件的选项对话框
TCHAR path[ 256 ];
GetDlgItemText(hDlg, IDC_DLLPATH, path,  256 );
//  load dll
g_PluginModule  =  LoadLibrary(path);
//  get dlgproc address 窗口过程函数的导出序号是241
g_PluginProc  =  (DLGPROC)GetProcAddress(g_PluginModule, (LPCTSTR) 241 );
//  create options dlg
g_PluginDlg  =  CreateDialog(g_PluginModule, (LPCTSTR)MAKEINTRESOURCE( 500 ), NULL, g_PluginProc);
ShowWindow(g_PluginDlg, SW_SHOW);
SetWindowPos(g_PluginDlg, NULL,  30 ,  80 ,  0 ,  0 , SWP_NOSIZE);
return  TRUE;

运行效果如图所示:

最 后我们开发好插件以后,可以利用SDK提供的打包工具,把插件制作成 cab 包,这样复制到设备上即可自动安装。打包是使用SDK提供的工具完成,但是我们首先需要自己为我们的软件编写一个 inf 文件,描述软件的发装过程。inf文件详细描述了需要拷贝的文件清单,源目录,目标目录,要添加的注册表信息等内容。这里可以参考 SDK中的范例,细节就不再描述了。这里我使用 mymemo 的 inf 文件做一个例子说明:为了更具可读性,文件内容经过了精简。

mymemo.inf
[Version]
Signature    =   " $Windows NT$ "
Provider     =   " Microsoft "
CESignature  =   " $Windows CE$ "

[CEStrings]
AppName      =   " MyMemo "
InstallDir   =  %CE2%             ;  " /Windows "

[CEDevice]
UnsupportedPlatforms     =   " HPC " , " Jupiter " , " Palm PC2 "
VersionMin          =   3.0
VersionMax         =   6.0

[PPC2003_Device]
ProcessorType            =   2577       ; ARM CPU

[SourceDisksNames.PPC2003_Device]
1   =  , " ARM Files " ,,ARMV4Rel

[SourceDisksFiles]
" mymemo.dll "                      =   1      ; the Today plugin dll

[DestinationDirs]
Files.Windows        =   0 ,%CE2%           ;  " /Windows "  directory

[Files.Windows]
" mymemo.dll " , " mymemo.dll " ,,0x00000001

[Reg.Version1]
HKLM,Software / Microsoft / Today / Items / %AppName%,Enabled,0x00010001, 0
HKLM,Software / Microsoft / Today / Items / %AppName%,Type,0x00010001, 4
HKLM,Software / Microsoft / Today / Items / %AppName%,Options,0x00010001, 0
HKLM,Software / Micros oft / Today / Items / % AppName%,Selectability,0x00010001, 1
HKLM,Software / Micr os oft / Today / Items / % AppName%,IconIndex,0x00010001, 0

HKLM,Software / Microsoft / Today / Items / %AppName%,DLL,0x00000002, " %InstallDir%/mymemo.dll "
HKLM,Software / Microsoft / Today / Items / %AppName%,Memo,0x00000002, " Type here to input memo "

打包工具是一个命令行程序,我们执行以下命令:
       cabwiz mymemo.inf   /err errinfo.txt   /cpu PPC2003_Device

其中,/err选项指定错误输出文件,当打包失败时,这是诊断问题的重要信息。
      /cpu选项指定是inf文件中定义过的CPU类型,如果在inf文件中定义了多种CPU类型,可以同时为多种CPU打包,所以一个inf文件可以多用。

最后我们给出相关的下载连接:
      (1)程序存储电量百分比显示和桌面便笺插件的CAB包下载链接:
      http://files.cnblogs.com/hoodlum1980/PPCCAB_MyMemo_MemWatcher.rar
      (2)桌面记单词插件的CAB包下载链接:
      http://files.cnblogs.com/hoodlum1980/Recite_CAB_ARMV4.rar
      (3)然后在给出一个我以前写的C语言的俄罗斯方块(最早发表在编程论坛),移植到PPC上的版本:
      http://files.cnblogs.com/hoodlum1980/Tetris.rar
      运行效果截图:

(4)最后我们给出本文提及所有源代码的合集下载连接,全部使用EVC4.0使用C++开发。每个插件包含了用于打包的 inf 文件。
      http://files.cnblogs.com/hoodlum1980/TodayPlugins.rar

Tag标签: 今日插件 ,Window Mobile ,Pocket PC

开发Windows Mobile今日插件 -- 内存电量,桌面便笺,桌面记单词(转自hoodlum1980 ( 發發 ) 的技术博客)相关推荐

  1. 开发Windows Mobile今日插件 -- 内存电量,桌面便笺,桌面记单词

    本篇文章讲解的是开发 Windows Mobile 上的今日插件.关于是今日插件,在 PPC 或者 SP SDK 的帮助文档中有相关的章节介绍,在网络上也有一些帖子和资源讲解.在这里简要回顾一下.今日 ...

  2. 解决VS2008 开发Windows Mobile 项目生成速度慢的问题

    EnjoyIt.Zwg 体验精细管理 享受工作乐趣 解决VS2008 开发Windows Mobile 项目生成速度慢的问题 最近用VS2008开发Windows Mobile程序,使用C#..NET ...

  3. Visual Studio开发Windows Mobile程序

    转自:http://developer.51cto.com/art/200908/144102.htm Visual Studio 2005无疑是开发Windows Mobile的最佳工具,其本身就集 ...

  4. 前端开发桌面软件、技术博客整理等

    整理桌面 渐渐的发现,很多东西存储在本地,永远不如放在云端,很多需要记录的东西,换台电脑,原来的东西可能找不到了,整理的过程是繁琐的,但是结果却是很方便. 记录来盈嘉互联后电脑安装的软件 马克飞象.t ...

  5. 51CTO移动客户端可以在线下载安装啦 - 51CTO博客开发 - 51CTO技术博客

    51CTO移动客户端可以在线下载安装啦 - 51CTO博客开发 - 51CTO技术博客 51CTO移动客户端可以在线下载安装啦 - 51CTO博客开发 - 51CTO技术博客 51CTO移动客户端可以 ...

  6. c语言中shmget相关函数,共享内存函数(shmget、shmat、shmdt、shmctl)及其范例 - guoping16的专栏 - 博客频道 - CSDN...

    2014年4月2日共享内存函数(shmget.shmat.shmdt.shmctl)及其范例 - guoping16的专栏 - 博客频道 - http://doc.xuehai.net 登录 | 注册 ...

  7. 使用.NET 框架压缩版开发Windows Mobile 2003 for Smartphone

    发布日期: 11/30/2004 | 更新日期: 11/30/2004 Andreas Sjöström, Christian Forsberg businessanyplace.net 适用于: M ...

  8. 开发 Windows Mobile 应用程序: FAQ

    常见问题 问:什么是 Windows Mobile? 答:Windows Mobile 是 Microsoft 用于 Pocket PC 和 Smartphone 的软件平台.Windows Mobi ...

  9. 开发Windows Mobile和Wince(Windows Embedded CE)的部署项目(Deploy Project)时,如何修改注册表...

    由于fele问我一个问题:在进行Windows Mobile部署项目的开发,也就是打安装包,如何修改注册表?我把自己的经验blog下来. 1.打开Deploy项目的注册表编辑器,如下图 2.根据需求修 ...

最新文章

  1. eclipse for C/C++
  2. let/var的使用详解
  3. SPARK:作业基本运行原理
  4. 国际智商测试皮肤软件,爆火的口服玻尿酸,是美容神器还是智商税?
  5. 当深度学习走进高考考场,会发生什么!!
  6. 使用PL/SQL Developer给Oracle生成漂亮的数据库说明文档
  7. 西数硬盘固件刷新工具_一个1TB移动硬盘的数据恢复过程,含分析问题与解决方式...
  8. Blockchair首席开发者质疑闪电网络能够扩展比特币:可锁定大部分闪电网络的流动性来破坏闪电网络
  9. c语言实现单链表数据结构,数据结构与算法-C语言实现单链表,以及单链表的常用方法...
  10. 多项logistic回归系数解释_逻辑回归logistic(含python代码)
  11. pb 系统托盘实例(定时任务管理)
  12. 软件测试的概念与过程
  13. 计算机linux二级试题,计算机二级考试题及答案
  14. 联通沃云联手阿里云推混合云解决方案 打造共赢云生态
  15. 6个不为人知的高质量APP推荐:知乎3万人点赞,2万人收藏!
  16. win7-32位系统,不能运行flash,解决方法。
  17. TDS210示波器的应用
  18. 基于阿里云的块存储介绍
  19. C语言单元测试框架——CUnit
  20. 北航计算机学院编译技术,北航编译技术在线作业一二三

热门文章

  1. 如何限制一个账号同时只能一个人登录
  2. 常见面试题------Redis为什么可以做缓存?
  3. 网络知识入门,用户如何连接互联网,ADSL调制解调器的妙用,PPP上网的三种方式(十二)
  4. 教你一招黑掉别人电脑,让电脑关机
  5. VSS写入缓存失败的解决方法
  6. XTransfer 1号技术员工卡乐:从普通程序猿到技术专家
  7. 朴素贝叶斯分类算法介绍及实现
  8. Matlab遗传算法求函数最大值
  9. java支持wingdings吗,Java PhysicalFonts類代碼示例
  10. js获取时间的年,月,日,季度,年份的代码收集和改编