开发Windows Mobile今日插件 -- 内存电量,桌面便笺,桌面记单词(转自hoodlum1980 ( 發發 ) 的技术博客)
2010-01-21 17:57
453 查看
本篇文章讲解的是开发 Windows Mobile 上的今日插件。关于是今日插件,在 PPC 或者 SP SDK
的帮助文档中有相关的章节介绍,在网络上也有一些帖子和资源讲解。在这里简要回顾一下。今日插件就是在windows
mobile的桌面上显示的条目,例如系统提供的“日历”(Calendar),主人信息,以及许多第三方开发的今日插件等等。由于桌面是开机后的第一个
屏幕,所以插件显示在屏幕上将会得到用户最多的浏览时间。这里我们主要讲解的是用户自定义插件的开发。自定义插件如下图所示:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/TodayPlugIn.jpg)
一
个插件就是一个位于屏幕上的窗口,每个插件负责自己的绘制和对用户输入的响应。插件可以通过给自己的父窗口(桌面窗口)发送
TODAYM_DRAWWATERMARK
消息,委托父窗口为自己绘制背景,也就是通过把自己的HDC传递给shell来完成的,这样插件就看起来好像是“透明”的效果。同时,shell
也负责在相邻的插件之间绘制一条分割线。
通常,PPC 最多允许加载 12 个插件。最大插件数量是由 K_cTodayItemsMax 定义的。
对于自定义的插件,要求开发者提供一个DLL函数并注册到注册表: HKLM/Softeware/Microsoft/Today/Items;
如下图,我们使用远程注册表查看工具打开一个插件的在注册表中的位置:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/ToayPlugIn02.jpg)
在下面包含了所有今日插件的键。每个插件将含有下列的值:
◆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 插件 和 “桌面便笺”。效果如下:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/memWatcher01.jpg)
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/CEZoom5.jpg)
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/CEZoom2.jpg)
在
左图是SDK中的 memWatcher 范例在模拟器中的显示效果,右图是经过我适当改写后,在实际HTC
S1中运行的效果。SDK范例显示了程序和存储的百分比,并且创建了两个进度条窗口显示。经过我的改写,我把进度条去掉了,从而可以把信息压缩到一行以
内,这样可以节省屏幕空间,并且增加了电池电量的显示。
获取这些信息的相关API函数是:
GlobalMemoryStatus,GetStoreInformation,GetSystemPowerStatusEx;
下
面我们看下桌面便笺插件,这个插件发布在 pdafans
论坛后,很快就有网友向我反馈了备忘录的内容在重启后消失,这是因为我做的这个插件也仅仅是个范例来使用,测试了我的想法是可行的,所以并没有考虑那么
多。便笺的内容被放到了内存里,声明周期和DLL一样,这也一旦DLL被卸载,存储在内存中的内容也就失去了。这也提醒了我们一点,我们的插件应该将数据
持久化。所以我又修改了这个插件,把备忘信息和图标索引存储到了注册表中,也就是插件注册的键下面新增了两个值。这样我们就可以保证每次插件启动时都会从
注册表中读取出上次的用户记录的内容。
当用鼠标点击桌面便笺时,就会弹出一个对话框用于设置新的备忘内容,如下图所示:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/CEZoom4.jpg)
这
个对话框中具有一点难度和技巧性的是上面的图标选择反馈,全部是通过鼠标点击事件来完成的。我们在对话框的 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秒钟接收到的消息去滚动词条。效果如下图所示:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/recite01.jpg)
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/recite02.jpg)
开
发这个插件时,我忽然发现 Pocket PC 的操作系统是不支持读写 ini
文件的相关API函数的。我去网络上找了下相关代码,但是没有看到特别满意的。因此我自己用C语言写了几个和API函数功能相同的读 ini
文件的函数。函数命名也是完全相同的,为了在 PC上进行测试,我在每个函数名前面加了 Ce
,以和系统的API函数区分开。我这里仅仅为了插件功能写了有限的几个函数,这里以 CeGetPrivateProfileString
为例给出代码。
在PC上,这个函数负责读取 ini 文件某个 section 中某个 key 的值。为了同时在 unicode 和 多字节字符串环境中适用,我又把相关的文件和字符串操作函数进行了宏定义,并且以这种方式命名:
t_多字节版本函数名
;
这是因为对多字节版本的函数我们通常更加熟悉它的命名。例如对于 t_strcpy 等等。下面是这个函数的代码:
![](http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
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
![](http://www.cnblogs.com/Images/dot.gif)
//
读取section前求出 Key 的长度
keyLength
=
t_strlen(lpKeyName);
bFindSection
=
true
;
continue
;
}
//
查找Key, Section End?
if
(line[
0
]
==
'
[
'
)
break
;
//
遇到了下一个
if
(lineLength
<
keyLength
+
1
||
line[keyLength]
!=
'
=
'
)
continue
;
//
"KeyName=
![](http://www.cnblogs.com/Images/dot.gif)
"
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;
}
下面是我提供了一个演示程序,由于我们知道了插件的协议,所以我们也可以显示出其他插件的选项对话框,为了更具可读性,代码经过了精简。
![](http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
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;
运行效果如图所示:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/ShowOptions.jpg)
最
后我们开发好插件以后,可以利用SDK提供的打包工具,把插件制作成 cab
包,这样复制到设备上即可自动安装。打包是使用SDK提供的工具完成,但是我们首先需要自己为我们的软件编写一个 inf
文件,描述软件的发装过程。inf文件详细描述了需要拷贝的文件清单,源目录,目标目录,要添加的注册表信息等内容。这里可以参考
SDK中的范例,细节就不再描述了。这里我使用 mymemo 的 inf 文件做一个例子说明:为了更具可读性,文件内容经过了精简。
![](http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
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
![](http://www.cnblogs.com/Images/dot.gif)
"
打包工具是一个命令行程序,我们执行以下命令:
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
运行效果截图:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/Tetris01.jpg)
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/Tetris02.jpg)
(4)最后我们给出本文提及所有源代码的合集下载连接,全部使用EVC4.0使用C++开发。每个插件包含了用于打包的 inf 文件。
http://files.cnblogs.com/hoodlum1980/TodayPlugins.rar
Tag标签: 今日插件
,Window Mobile
,Pocket PC
的帮助文档中有相关的章节介绍,在网络上也有一些帖子和资源讲解。在这里简要回顾一下。今日插件就是在windows
mobile的桌面上显示的条目,例如系统提供的“日历”(Calendar),主人信息,以及许多第三方开发的今日插件等等。由于桌面是开机后的第一个
屏幕,所以插件显示在屏幕上将会得到用户最多的浏览时间。这里我们主要讲解的是用户自定义插件的开发。自定义插件如下图所示:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/TodayPlugIn.jpg)
一
个插件就是一个位于屏幕上的窗口,每个插件负责自己的绘制和对用户输入的响应。插件可以通过给自己的父窗口(桌面窗口)发送
TODAYM_DRAWWATERMARK
消息,委托父窗口为自己绘制背景,也就是通过把自己的HDC传递给shell来完成的,这样插件就看起来好像是“透明”的效果。同时,shell
也负责在相邻的插件之间绘制一条分割线。
通常,PPC 最多允许加载 12 个插件。最大插件数量是由 K_cTodayItemsMax 定义的。
对于自定义的插件,要求开发者提供一个DLL函数并注册到注册表: HKLM/Softeware/Microsoft/Today/Items;
如下图,我们使用远程注册表查看工具打开一个插件的在注册表中的位置:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/ToayPlugIn02.jpg)
在下面包含了所有今日插件的键。每个插件将含有下列的值:
◆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 插件 和 “桌面便笺”。效果如下:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/memWatcher01.jpg)
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/CEZoom5.jpg)
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/CEZoom2.jpg)
在
左图是SDK中的 memWatcher 范例在模拟器中的显示效果,右图是经过我适当改写后,在实际HTC
S1中运行的效果。SDK范例显示了程序和存储的百分比,并且创建了两个进度条窗口显示。经过我的改写,我把进度条去掉了,从而可以把信息压缩到一行以
内,这样可以节省屏幕空间,并且增加了电池电量的显示。
获取这些信息的相关API函数是:
GlobalMemoryStatus,GetStoreInformation,GetSystemPowerStatusEx;
下
面我们看下桌面便笺插件,这个插件发布在 pdafans
论坛后,很快就有网友向我反馈了备忘录的内容在重启后消失,这是因为我做的这个插件也仅仅是个范例来使用,测试了我的想法是可行的,所以并没有考虑那么
多。便笺的内容被放到了内存里,声明周期和DLL一样,这也一旦DLL被卸载,存储在内存中的内容也就失去了。这也提醒了我们一点,我们的插件应该将数据
持久化。所以我又修改了这个插件,把备忘信息和图标索引存储到了注册表中,也就是插件注册的键下面新增了两个值。这样我们就可以保证每次插件启动时都会从
注册表中读取出上次的用户记录的内容。
当用鼠标点击桌面便笺时,就会弹出一个对话框用于设置新的备忘内容,如下图所示:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/CEZoom4.jpg)
这
个对话框中具有一点难度和技巧性的是上面的图标选择反馈,全部是通过鼠标点击事件来完成的。我们在对话框的 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秒钟接收到的消息去滚动词条。效果如下图所示:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/recite01.jpg)
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/recite02.jpg)
开
发这个插件时,我忽然发现 Pocket PC 的操作系统是不支持读写 ini
文件的相关API函数的。我去网络上找了下相关代码,但是没有看到特别满意的。因此我自己用C语言写了几个和API函数功能相同的读 ini
文件的函数。函数命名也是完全相同的,为了在 PC上进行测试,我在每个函数名前面加了 Ce
,以和系统的API函数区分开。我这里仅仅为了插件功能写了有限的几个函数,这里以 CeGetPrivateProfileString
为例给出代码。
在PC上,这个函数负责读取 ini 文件某个 section 中某个 key 的值。为了同时在 unicode 和 多字节字符串环境中适用,我又把相关的文件和字符串操作函数进行了宏定义,并且以这种方式命名:
t_多字节版本函数名
;
这是因为对多字节版本的函数我们通常更加熟悉它的命名。例如对于 t_strcpy 等等。下面是这个函数的代码:
![](http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
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
![](http://www.cnblogs.com/Images/dot.gif)
//
读取section前求出 Key 的长度
keyLength
=
t_strlen(lpKeyName);
bFindSection
=
true
;
continue
;
}
//
查找Key, Section End?
if
(line[
0
]
==
'
[
'
)
break
;
//
遇到了下一个
if
(lineLength
<
keyLength
+
1
||
line[keyLength]
!=
'
=
'
)
continue
;
//
"KeyName=
![](http://www.cnblogs.com/Images/dot.gif)
"
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;
}
下面是我提供了一个演示程序,由于我们知道了插件的协议,所以我们也可以显示出其他插件的选项对话框,为了更具可读性,代码经过了精简。
![](http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
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;
运行效果如图所示:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/ShowOptions.jpg)
最
后我们开发好插件以后,可以利用SDK提供的打包工具,把插件制作成 cab
包,这样复制到设备上即可自动安装。打包是使用SDK提供的工具完成,但是我们首先需要自己为我们的软件编写一个 inf
文件,描述软件的发装过程。inf文件详细描述了需要拷贝的文件清单,源目录,目标目录,要添加的注册表信息等内容。这里可以参考
SDK中的范例,细节就不再描述了。这里我使用 mymemo 的 inf 文件做一个例子说明:为了更具可读性,文件内容经过了精简。
![](http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
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
![](http://www.cnblogs.com/Images/dot.gif)
"
打包工具是一个命令行程序,我们执行以下命令:
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
运行效果截图:
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/Tetris01.jpg)
![](http://images.cnblogs.com/cnblogs_com/hoodlum1980/Tetris02.jpg)
(4)最后我们给出本文提及所有源代码的合集下载连接,全部使用EVC4.0使用C++开发。每个插件包含了用于打包的 inf 文件。
http://files.cnblogs.com/hoodlum1980/TodayPlugins.rar
Tag标签: 今日插件
,Window Mobile
,Pocket PC
相关文章推荐
- 开发Windows Mobile今日插件 -- 内存电量,桌面便笺,桌面记单词
- 用C#开发Windows Mobile开发今日插件附源代码
- Windows Mobile 今日插件开发
- Windows Mobile 今日插件开发
- Windows Mobile 今日插件开发
- Windows Mobile 今日插件开发
- Windows Mobile智能手机今日插件编程开发初探(使用C++, vs2008, Pocket PC 5.0内核)
- 金旭亮博客之“桌面应用程序开发技术”资源主页
- Windows Mobile 今日插件开发
- 关于windows mobile 今日插件开发的记录
- 插件开发技术说明(12)---面向业务对象编程:2种风格的对比
- Windows Mobile 开发黄金周(1):移动设备应用程序开发-平台,工具和技术
- MS IDE RAD技术入门----VC6插件开发入门(4)----安装插件
- (转)Android桌面小插件的开发详解
- 插件开发技术说明(11)---修改数据
- 关于android Widgets桌面小插件的开发大概流程