第十二章 脚本Shell对象
新的Windows Shell 包含了丰富的新对象,这些新对象通过自动化体系提供了对Shell主要特征的完全访问能力。IE4.0在新版的shdocvw.dll中引进了这些COM新对象,并把它们作为核心部件。这些对象可以使你能编程驱动Shell和它的文件夹,并且在Internet客户SDK资料中(现在已经集成到平台SDK资料中)对这些对象有详细说明。由于这些对象是自动化服务器,因此使用VB、delphi、VC++ 所写的程序很容易使用它们。也可以使用脚本代码包括来自Windows脚本环境(WSH)的代码调用这些对象,下一章我们将介绍WSH。
在这一章中,我们将调查Shell对象模型,并且重写在第5章中我们给出的示例,这是测试脚本化Shell对象真实用途的一次机会:它们提供了简化访问Shell特征以及Shell文件夹内容的方法。顺着这条线索,我们进一步探讨:
Shell对象模型
‘文件夹’和‘文件夹项’对象
管理动词和favorites的辅助对象
用C++ 和 VB 写的示例代码
我们将要描述的对象为编程访问Windows Shell和其所有特征提供了一种更容易的方法。我们已经能枚举文件夹的内容,但是需要相当深的C++编程知识。使用新对象,这个操作就象调用自动服务器那么简单。不幸地是,为了使VB和脚本编程更容易,其操作对 C和 C++ 程序员更困难了,现在程序员们必须应付VARINT类型,并且,由于没有内置的帮助,必须通过VB收集帮助信息。
Shell编程最好的语言
访问Shell命令和属性——要么容易,要么困难,没有中间道路可走。我们在第5章中已经看到了一些试图枚举文件夹内容的示例,必须取得文件夹的PIDL和正确的IShellFolder接口指针,然后通过这两项的组合最终获得IEnumIDList接口,使用这个接口才能允许我们枚举文件夹的项。这并不是一个满意的解决方案,因为在获得每一个元素的PIDL时,必须把它转换成可读的名字才行。
这是一种困难的底层操作方法,并且也是唯一一种象C 和 C++ 这样支持指针的语言可能使用的方法。脚本Shell对象解决了这个问题,这使得VB和脚本语言可以对Shell进行编程。即使这就是全部,我们也从中获得了实惠,然而,新对象向我们提供了比自动化接口更多的好处。
没有资料的Shell特性
实际使C++程序员对Shell对象感兴趣的是它们提供了有资料说明的访问某些Shell特征和对话框的方法。另外,还提供了对所有Shell特征一致的编程接口。无论有无资料说明,许多Shell对象输出的方法都具有我们已经知道的功能,而且可以有几种不同的访问方式。例如,浏览文件夹,有一个特殊的API函数,在借助ShellExecute()函数运行‘查找’对话框的同时可以手写枚举文件夹内容的例程。这些现在都是通过Shell对象实现的。
这里是只能通过Shell对象模型访问的对话框和函数:
从‘开始’菜单通过点击‘任务条设置’弹出的对话框
所有打开窗口的最小化或恢复函数
所有打开窗口的重叠或并列函数
挂起或恢复函数
‘运行’对话框
‘查找计算机’对话框
添加文件夹或文件到‘Favorites’列表的系统对话框
对象模型输出的可以用其它方式访问的函数:
浏览文件夹对话框
打开或探测文件夹
日期时间设置对话框
运行‘控制面板’小程序
‘查找’对话框
访问任何系统文件夹
Shell对象模型
实际形成Shell对象模型的所有对象都是在shdocvw.dll中实现的。这些对象的一个快速但有些不完整的资料说明来源于VB的对象浏览器。
如果使用VB,这个对象浏览器实际可以帮助你发现新特征。用这个浏览器围绕shdocvw.dll进行探查,我们找到了Shell的对象模型,下图说明了它的布局:
在VB中建立Shell对象实例的最容易的方法是:
Dim o As Object
Set o = CreateObject("Shell.Application")
在C++ 中的等价方法是:
#include <comdef.h>
#include <exdisp.h>
CoInitialize(NULL);
IShellDispatch* pShellDisp = NULL;
HRESULT hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_SERVER,
IID_IShellDispatch, reinterpret_cast<LPVOID*>(&pShellDisp));
if(SUCCEEDED(hr))
{
...
}
CoUninitialize();
在进一步的讨论编程方法之前,先让我们浏览一下Shell对象输出的方法:
方法 |
描述 |
BrowseForFolder() |
SHBrowseForFolder() API函数的简化版。显示基于树的窗口,使你能选择文件夹。与API函数的区别是不支持回调机理。 |
CascadeWindows() |
重叠方式排列所有顶层窗口。 |
ControlPanelItem() |
导出控制面板小程序。这个方法接受一个存在的CPL文件并调用Control_RunDLL()函数,这是我们前一章中调查过的函数。 |
EjectPC() |
将计算机从它所泊靠的站点脱开(‘弹出’)。仅在开始菜单中有‘Eject’命令的计算机上,此方法才能工作。 |
Explore() |
打开基于指定文件夹的类探测器窗口。 |
FileRun() |
导出‘运行’对话框,就象在开始菜单中点击‘运行’一样。 |
FindComputer() |
导出‘查找计算机’对话框,就如同在‘开始’菜单中点击‘查找 | 计算机’。 |
FindFiles() |
导出‘查找’对话框,就象在开始菜单中点击‘查找| 文件或文件夹’。 |
Help() |
显示帮助窗口,如同在‘开始’菜单中点击‘帮助’一样。 |
MinimizeAll() |
清除桌面,最小化所有打开的窗口(不仅是顶层窗口)。这个方法与任务条上的‘显示桌面’按钮(Shell 4.71以上版),右击任务条并选择‘最小化所有窗口’,或按‘Windows-M’键有同样的效果。 |
NameSpace() |
这接受路径名或常量作为输入,并建立一个文件夹对象。后面我们将揭示‘文件夹’对象。 |
Open() |
使用不同窗口打开指定的文件夹,没有类探测器的左边窗框。 |
RefreshMenu() |
刷新‘开始’菜单,响应可能的变化。 |
SetTime() |
显示设置当前日期时间对话框。调用这个方法就如同双击托盘区的时钟图标。 |
ShutdownWindows() |
导出退出窗口过程,就象在‘开始’菜单中点击‘关闭’命令。 |
Suspend() |
挂起计算机。仅在开始菜单中有‘挂起’命令的那些计算机上,这个方法才能工作。 |
TileHorizontally() |
水平方向并排列出当前所有打开的顶层窗口。 |
TileVertically() |
垂直方向并排列出所有当前打开的顶层窗口。 |
TrayProperties() |
导出任务条属性对话框,就象在‘开始’菜单上点击‘设置 | 任务条’一样。 |
UndoMinimizeALL() |
回滚任何自前一次调用UndoMinimizeALL()以来所执行的任何变化,恢复桌面窗口,这个方法与点击任务条的桌面按钮,右击任务条并选择‘Ubdo 最小化’或按Shift-Windows-M键有相同效果。注意,名字中的两个‘L’是大写的。 |
Windows() |
资料中说,这个函数建立和返回一个ShellWindows对象,这个对象采集所有属于Shell的打开窗口,然而,这个行为已经产生了问题。 |
正像我们所看到的,许多方法都等价于在‘开始’菜单和任务条关联菜单中出现的命令,这正好说明Shell对象提供了Windows Shell的功能,许多方法都是极其简单的,不要求参数,下面让我们进一步探查这些方法。
Shell对象的方法
在详细探查带有输入输出参数的方法之前,要注意,这些任务中使用的所有串都是BSTRs,而不是LPSTRs。你可以在Internet客户端SDK中找到VB的资料,但是,对于纯IDL-导出的C++头文件定义的所有函数,要查看exdisp.h头文件,这是最近随Internet客户端SDK一起安装的。
与通常的COM接口一样,所有函数都返回HRESULT值,标识错误码。VB对程序员隐藏了这些,所以VB资料中定义的函数返回值实际是一个[out]参数。在VB中,任何错误都激起一个异常,这要求用户自己使用On Error Goto 语句进行处理。
BrowseForFolder()方法
这个函数返回一个文件夹对象,它接收如下变量:
父窗口Handle
用于对话框的标题串
一些选项,与SHBrowseForFolder()函数中使用的选项相同
一个用作浏览操作根的可选文件夹
用作根的文件夹必须指定为VARIANT类型,可以包含串或CLSID_XXX常量(在第5章中遇到过的)。其原型如下:
HRESULT BrowseForFolder(long Hwnd, BSTR Title, long Options,
VARIANT RootFolder, Folder** ppsdf);
下面的代码说明在C++中使用CLSID_XXX和串标识根文件夹时怎样调用这个方法。在原文件中添加#include atlbase.h,你可以使用ATL封装的类CComBSTR和CComVariant,这使得在C++中使用BSTRs 和VARIANTs类型的变量容易多了。
如果想要测试这段代码,你可以直接将其插入到我们前面给出的建立Shell对象实例的代码中,注意,因为总是带有COM,因此,如果不使用CComPtr<>类,你就必须Release()任何在调用CoUninitialize()之前请求的指针。
#include <atlbase.h>
// 设置指针, VARIANT 和 BSTR
Folder* pFolder = NULL;
CComVariant vRoot(CSIDL_DRIVES); // 我的计算机文件夹作为根
CComBSTR bstrTitle(__TEXT("My Computer:")); // 对话框标题
// 调用方法
HRESULT hr = pShellDisp->BrowseForFolder(reinterpret_cast<long>(hDlg),
bstrTitle, 0, vRoot, &pFolder);
// 释放指针
pFolder->Release();
或… …
// 设置指针, VARIANT 和 BSTR
Folder* pFolder = NULL;
CComVariant vRoot(__TEXT("c://")); // C 驱动器
CComBSTR bstrTitle(__TEXT("My Disk C:")); // 对话框标题
ControlPanelItem()方法
这个函数接收要运行的.cpl文件名串作为输入,而且这个串必须是BSTR。其原型为:
HRESULT ControlPanelItem(BSTR szDir);
下面是一个C++的例子,它再次使用CComBSTR类构造建立BSTR:
CComBSTR bstr(__TEXT("desk.cpl"));
HRESULT hr = pShellDisp->ControlPanelItem(bstr);
Explore()方法
这个方法接收VARIANT类型的变量指定要打开的文件夹。这个VARIANT可以包含路径名以及CLSID_XXX常量。其原型为:
HRESULT Explore(VARIANT vDir);
下面是一个例子:
// 设置 VARIANT
CComVariant vDir(CSIDL_HISTORY); // 历史文件夹
// 调用方法
HRESULT hr = pShellDisp->Explore(vDir);
NameSpace()方法
这个函数接收两个参数,头一个是VARIANT类型的,可以是路径名或指定特殊系统文件夹的预定义常量。第二个参数是一个输出变量,它由这个方法填充——指向文件夹对象指针的地址:
HRESULT NameSpace(VARIANT vDir, Folder** ppsdf);
下面代码显示了它的活动:
Folder* pFolder = NULL;
CComVariant vDir(CSIDL_STARTMENU); // 开始菜单
pShellDisp->NameSpace(vDir, &pFolder);
// 使用 pFolder...
pFolder->Release();
Open()方法
除了语法,这个函数与Explore()相同。其原型为:
HRESULT Open(VARIANT vDir);
Windows()方法
这个函数没有输入参数,但是提供指向IDispatch接口的指针作为它的输出,这个参数使你能访问当前打开的窗口集。其原型为:
HRESULT Windows(IDispatch** ppid);
Shell对象的属性
Shell对象有两个属性:‘Parent’和‘Application’。通过调用get_Parent()和get_Application()方法分别实现这两个属性。它们都输出IDispatch指针并返回HRESULT。
唤醒Shell对象
从编程角度考虑,Shell对象的重要性在于它提供了访问任何文件夹的快捷而容易的方法。此外我们上面列出的所有功能提供了有趣的命令集,然而对于大多数实际应用,它们并没趋近Shell对象的本质。因此现在我们解释怎样调用Shell对象及其关联的四个问题。我们要讨论的是:
使指针指向正确的接口
使用VARIANT类型
使用Unicode串
访问和使用聚集
我们将从解释怎样使用VB和脚本语言工作开始,然后进入C++,这使工作更复杂一点。这些例子显示怎样调用:
BrowseForFolder()
FindComputer()
NameSpace()
使用这些作为例子能完全表述调用Shell对象方法要求的技术。
使用VB
在VB中,你可以使用早绑定或晚绑定。即,乘员名可以在运行时绑定到指派标识符上(晚绑定)或编译时绑定(早绑定),后者使调用进入接口更快。在早绑定中,提前使用了适当的类型声明变量,因而告知编译器使用它们支持的方法,属性和事件。例如:
Dim s As New Shell
通过声明 s 为 Shell 而不是作为对象,我们选择了早绑定。这要求你添加引用库到工程(project)中,所以编译器可以容易地检查所有对这个对象的调用。此时的Shell对象库是shdocvw.dll,下面是一段代码显示怎样存储对库的引用,说明了VB的早绑定。
Dim s As New Shell
s.FindComputer
相反,晚绑定是指在运行时绑定库,在源码中我们声明一个一般的对象变量,它动态地连接到库的一个特定对象,代码如下:
Dim o As Object
Set o = CreateObject("Shell.Application")
o.FindComputer
此时,我们并不需要包含任何引用,下图显示了一个用VB编写的示范应用:
其中有三个按钮调用上述提到的三个函数。‘查找计算机’连接到代码:
' 查找计算机
Private Sub btnFindComputer_Click()
Dim s As New Shell
s.FindComputer
End Sub
它导出下面的对话框:
‘BrowseForFolder’显示我们在第5章中测试过细节的对话框。在这个例子中,我们安排它浏览‘我的计算机’文件夹,如下:
' 浏览文件夹
Private Sub btnBrowseForFolder_Click()
On Error Resume Next
Dim s As New Shell
Dim f As Folder
Dim fi As FolderItem
Set f = s.BrowseForFolder(Me.hWnd, "My Computer:", 0, ssfDRIVES)
Set fi = f.Items.Item
' 在文本框中显示选择的路径
Text1.Text = fi.Path
End Sub
这个方法返回一个文件夹对象的引用,但是,如果想要选择元素的路径,文件夹就不充分了,代之你必须有一个FolderItem对象,这个对象输出路径属性(后面我们将进一步解释Folder 和FolderItem)。
在上面程序中ssfDRIVES常量是一个预定义的枚举类型ShellSpecialFolderConstants。其值等价于CSIDL_XXX常量(在第5章中遇到过的),但是在我们的讨论中并没有包含所有shlobj.h中定义的常量,缺少了不经常使用的,它们是:
CSIDL_HISTORY
CSIDL_COOKIES
CSIDL_INTERNET
CSIDL_INTERNET_CACHE
CSIDL_APPDATA
CSIDL_ALTSTARTUP
CSIDL_PRINTHOOD
同时添加下面的常量作为附加的支持也是必要的:
Const ssfHISTORY = &H22
Const ssfCOOKIES = &H21
Const ssfINTERNET = &H1
Const ssfINTERNETCACHE = &H20
Const ssfAPPDATA = &H1A
Const ssfALTSTARTUP = &H1D
Const ssfPRINTHOOD = &H1B
这些常量的值直接来自shlobj.h头文件,你可以自由采用其它常量名。
三个按钮中的最后一个是‘NameSpace’,打开和返回一个文件夹对象,它是基于特定路径名或ID的。而后枚举了这个文件夹的项,把它们显示在左侧的列表框中:
' 枚举开始菜单文件夹的内容
Private Sub btnNameSpace_Click()
Dim s As New Shell
Dim f As Folder
Dim i As Integer
Dim Item As FolderItem
Set f = s.NameSpace(ssfSTARTMENU)
For Each Item In f.Items
List1.AddItem Item.Path
Next
End Sub
上面代码使用了ssfSTARTMENU常量,因而恢复的是开始菜单文件夹。应该注意到引用了文件夹对象后,遍历它的内容是多么的容易——只需设置一个FOR循环即可。
下面是这个VB应用余下的代码,显示了其它控件的处理器函数:
Option Explicit
' Add btnFindComputer_Click()
' Add btnBrowseForFolder_Click()
' Add btnNameSpace_Click()
' 显示每一个文件夹项的动词
Private Sub List1_Click()
Dim s As New Shell
Dim f As Folder
Dim fi As FolderItem
Dim fiv As FolderItemVerb
Dim i As Integer
Set f = s.NameSpace(ssfSTARTMENU)
i = List1.ListIndex
Set fi = f.Items.Item(i)
List2.Clear
For Each fiv In fi.Verbs
List2.AddItem fiv.Name
Next
End Sub
这段源代码的后面部分处理左侧列表框的点击操作,涉及到使用的动词,我们将在后面加以解释。
使用C++
用C++ 作相同的事情需要多做一点工作,增加代码行,但是使用的方法基本是一样的。我们所遇到的头一个问题是确保包含所有头文件,这是我们在编译时所需要的,以及适当声明CLSID,这些都并不太费劲。
使用Shell对象模型需要的的所有声明都可以在exdisp.h中找到。要正确地编译一段使用Shell对象的代码,下面几行是充分的:
#include <windows.h>
#include <comdef.h>
#include <exdisp.h>
一定要保证版本是最新的,因为与编译器和其它工具一起的这些头文件可能并不包含你需要的每一样东西。例如VC++ 5.0安装的exdisp.h版本在Include子目录下并不包含Shell对象模型要求的任何定义。正确的exdisp.h版本是与Internet客户端SDK一同安装的,也在VS6.0中发布。
第二个问题是克服VARIANTs,这增加了C++ 的COM编程复杂等级。在VB中可以使用串或数字标识文件夹——这种灵活性依赖于VARIANTs可以持有不同的数据,而总输出相同的接口。然而,使用ATL封装的类CComVariant走了很长一段路才使得在C++中使用VARIANT变得较比容易。
第三个困难是底层COM编程枚举聚集。这项支持是内置于VB中的,例如下面代码:
Dim pF As Folder
Dim pFI As FolderItem
For i = 0 to pF.Items.Count - 1
Set pFI = pF.Items.Item(i)
' 做一些操作
Next
一般在VB中FOR…Each结构快于For…Next,这是因为它使用了Hash算法定位第 I 项,而不是顺序扫描项。而我们之所以在这里使用For…Next是因为它与C++编程方法更接近。
在C++中这样做要困难一些,但是实际上也并不太复杂。要想转换这段代码到C++,我们需要考虑三件事:
获取Items聚集
获取Count属性
获取聚集的第 i 个元素
为了解决这些问题,我们考虑一个典型的例子:枚举文件夹的内容。 NameSpace()方法为我们提供了文件夹对象的引用:
Folder* pFolder = NULL;
CComVariant vDir(CSIDL_STARTMENU);
pShellDisp->NameSpace(vDir, &pFolder);
从文件夹对象的说明资料中我们知道它有一个Items属性,其类型为FolderItems。本质上FolderItems是一个类型为FolderItem的元素集合。
因而我们所需要做的就是获得指向Items的指针,然后访问其中的每一项,在每一项上执行活动,如添加图标到列表观察控件。
long nLength;
FolderItems* pFIColl = NULL;
pFolder->Items(&pFIColl); // VB: Folder.Items
pFIColl->get_Count(&nLength); // VB: Folder.Items.Count
上面代码行说明怎样取得FolderItems聚集的指针,和它的长度。因此我们可以安排循环:
for(int i = 0 ; i < nLength ; i++)
{
// 从pFIColl 中获取第i个元素
// 使用这个元素
}
FolderItems对象是一个辅助对象,它输出让我们浏览一个聚集的接口。尤其是它有Item()成员函数,这个函数接收两个变量:一个VARIANT和一个指向FolderItem对象的指针。
for(int i = 0 ; i < nLength ; i++
{
CComVariant varIndex(i);
FolderItem* pFI = NULL;
pFIColl->Item(varIndex, &pFI);
// 使用这个对象
pFI->Release();
}
你可能奇怪,为什么Item()这样的函数使用VARIANT变量来返回对聚集中第i个元素的引用,而不使用int,UNIT或long类型的变量。答案是聚集通常允许使用名或索引访问其中的元素,这显然说明Item既准备接收数字也准备接收串,因此决定使用VARIANT。
有了FolderItem指针之后,我们就有了指向逻辑对象的指针,这就能告诉我们文件夹中包含的文件。我们可以请求文件的路径,尺寸或日期,例如:
CComBSTR bstr;
TCHAR szFile[MAX_PATH] = {0};
pFI->get_Path(&bstr);
wcstombs(szFile, bstr, MAX_PATH);
文件名可以用于恢复文档类的图标并添加图标到列表观察控件,这就是我们要在C++示例中所要做的。如此,我们需要进一步弄清Folder和FolderItem。
文件夹对象
文件夹对象表述包含文件或其它类型对象引用的Shell文件夹。通常,你并不直接建立文件夹,而是借助NameSpace()函数从路径名或虚拟文件夹ID建立它们。
文件夹对象输出四个属性,其中两个是已知的Application和Parent,另外两个是ParentFolder 和Title,它们的意义是显然的下表列出了这个对象的方法:
方法 |
描述 |
CopyHere() |
拷贝一个或多个文件对象到这个文件夹 |
GetDetailsOf() |
返回指定文件夹项的列信息。与Shell观察中显示的相同 |
Items() |
文件夹中FolderItem元素的集合。这个集合的类型是FolderItems |
MoveHere() |
与CopyHere相同,只是移动文件 |
NewFolder() |
在给定文件夹上建立新文件夹 |
ParseName() |
使用名字建立FolderItem对象 |
正如所见,文件夹对象给出与探测器处理文件夹一样的基本功能。
文件夹对象方法详解
让我们进一步详细的解释文件夹对象输出的方法。我们已经说明了Items(),它返回指向FolderItem对象聚集的指针。并且说明这个集合怎样输出属性Count和方法Item()来辅助枚举其中的元素,然而,其余的方法会是如何?
CopyHere()方法
这个方法可以看作为围绕API函数SHFileOperation()的一类封装。它拷贝一个或多个文件(或文件对象)从原始位置到当前文件夹。原文件可以是串、FolderItem对象,或FolderItem对象集合。这个操作可以通过类是于SHFileOperation()函数控制的标志进行控制(见第3章)。CopyHere()方法的原型为:
HRESULT CopyHere(VARIANT vItem, VARIANT vOptions);
GetDetailsOf()方法
这个方法试图向程序员提供一些信息,这是用户可以在探测器右边窗口获得的信息。每一个文件夹都有几个数据列,对于文件型文件夹,列包括:
名字
尺寸
最近修改日期
类型
属性
这个函数恢复给定文件夹项的这个基于列索引号的信息。唯一的例外是信息标签(Shell中某些元素的文字标签)它的ID号是-1。列ID从0开始,所以对正常的文件型文件夹,例如,列1的信息是‘尺寸’。信息总是以串的形式返回。这个方法的原型为:
HRESULT GetDetailsOf(VARIANT vItem, int iColumn, BSTR* pbs);
Items()方法
恢复文件夹中包含的所有文件夹项的集合。原型为:
HRESULT Items(FolderItems** ppid);
FolderItems聚集有下面的接口:
方法 |
描述 |
Item() |
允许遍历集合的各个元素。一个元素是一个FolderItem对象 |
这个聚集还有_NewEnum()方法,这个方法有特殊的意义。事实上每一个聚集对象都必须输出_NewEnum方法,以u使客户了解它所提供的循环能力。_NewEnum方法返回一个支持IEnumVARIANT接口对象的指针。
MoveHere()方法
这个方法与CopyHere()相同,唯一的差别是MoveHere()方法移动文件而不是拷贝它们。其原型为:
HRESULT MoveHere(VARIANT vItem, VARIANT vOptions);
NewFolder()方法
这个方法在指定的文件夹上建立新的子文件夹。它接收两个变量:要建立的文件夹名,以及一个当前没有使用的VARIANT。其原型为:
HRESULT NewFolder(BSTR bName, VARIANT vUnused);
ParseName()方法
这个方法是用第一个变量传递的名字建立一个新的FolderItem对象,然后返回这个对象。其原型为:
HRESULT ParseName(BSTR bName, FolderItem** ppid);
FolderItem对象
FolderItem对象表述Shell文件夹中的一个元素。它输出两个方法和一堆属性,使你能够了解这个项的特征。我们就从这个属性表开始。
属性 |
描述 |
Application |
恢复对象的IDispatch接口 |
GetFolder |
如果项是文件夹,恢复其文件夹对象。 |
IsBrowsable |
返回布尔值表示是否这个文件夹项是可以浏览的 |
IsFileSystem |
表明是否这个文件夹项是文件系统对象 |
IsFolder |
表明是否这个文件夹项是子文件夹 |
IsLink |
表明是否这个文件夹项是快捷方式 |
ModifyDate |
返回DATE类型值表明这个项最后更新的日期时间。DATE类型是8字节浮点数 |
Name |
返回项的名字串 |
Parent |
恢复这个项的父对象的IDispatch接口 |
Path |
返回这个项的全路径串 |
Size |
返回无符号长值,表示项的字节尺寸 |
Type |
返回项的类型串 |
所有这些属性都是只读的,并且通过get_XXX()方法实现,此处的XXX表示属性名。所有这些函数都返回HRESULTs并接收指向输出变量的指针,这由返回数据进行充填。
FolderItem输出的方法是:
InvokeVerb()
Verbs()
二者都与项所支持的动词有关。
唤醒项的动词
我们在第8章中提到过动词,InvokeVerb()函数执行文件夹项上的动词。这个方法的声明是:
HRESULT InvokeVerb(VARIANT vVerb);
而Verbs()则有下面的原型:
HRESULT Verbs(FolderItemVerbs** ppfic);
可以传递给InvokeVerb()的VARIANT应该是一个由FolderItemVerbs聚集返回的串,经由Verbs()方法可以访问这个聚集。
FolderItemVerbs聚集
这里是FolderItemVerbs聚集的编程接口:
方法 |
描述 |
Item() |
允许遍历其元素是FolderItemVerb对象的聚集 |
除此之外还有三个属性:Application, Parent 和 Count。最后一个就是我们期望的,它返回聚集中的项数。
FolderItemVerb对象
FolderItemVerb对象的接口是极其有限的,仅包含一个DoIt()方法,没有参数。
方法 |
描述 |
DoIt() |
执行文件夹项上的动词 |
此外FolderItemVerb一般还有Application 和 Parent 属性,以及一个Name属性,它返回这个项的实际动词名:
HRESULT get_Name(BSTR* pbs);
这里返回的串可以包含&号,用以表示菜单项的加速键。这个串就是实际出现在关联菜单上的串。毕竟这个编程接口还不太灵活。FolderItemVerbs聚集不能给出动词绝对真实的名字,而只是出现在关联菜单中的串。换言之,FolderItemVerbs聚集提供的是类似&Open的串而不是Open。对于本地版本的Windows 有些事情可能是错误的,因为传递给InvokeVerb()来执行一个给定命令的串是显示给用户的串,不是存储在注册表中的动词。例如,在意大利版的Windows中,你应该调用这个来打开文档:
InvokeVerb("&Apri");
我们在第8章中讨论过动词是应用于一定文件类的命令名。它可是静态(存储在注册表中),或动态(由Shell扩展添加)的。动词是通用串,它既不依赖于本地化,也不包含&号。所以在这里我们所调用的‘动词’稍微不同于我们在第8章中定义的。
附属对象
到目前为止,我们已经探讨了Shell对象模型中的主要对象(最通用的对象)。然而还有一些次要对象也是我们特别感兴趣的,比如ShellUIHelper对象,它实现了从IDispatch导出的IShellUIHelper接口,这个接口可以使你能添加目录或文件到‘Favorites’文件夹,这在第6章已经说明过了——毕竟,添加一个新‘Favorite’只是在特定路径上建立一个新的快捷方式。ShellUIHelper所能做的也是调用系统对话框添加‘Favorites’:
此外,它还允许你处理频道,订阅和桌面部件。
ShellUIHelper对象
ShellUIHelper对象也定义在exdisp.h头文件中,这个服务器由CLSID_ShellUIHelper标识,实现了IShellUIHelper接口,它输出四个方法:
方法 |
描述 |
AddChannel() |
添加频道到本地列表,它接收URL格式频道定义文件(.cdf)作为输入。 |
AddFavorite() |
添加文件或文件夹到favorite文件夹列表。它接收两个变量,URL指到 文件夹或文件,VARIANT描述favorite。 |
AddDesktopComponent() |
通过指定URL、类型(图象或web站点)、和屏幕的初始位置添加一个新的桌面项。 |
IsSubscribed() |
验证我们是否订阅一个指定的URL。 |
AddChannel()接收URL格式的CDF文件,并把它存储在本地。它的原型是:
HRESULT AddChannel(BSTR URL);
AddFavorite()显示默认的添加新文件或文件夹到你的favorites列表对话框。它以这样一种方式进行描述,其中VARIANT变量是项的描述名:
HRESULT AddFavorite(BSTR URL, VARIANT* Title);
AddDesktopComponent()注册了一个新的桌面项。它接收URL和表示部件类型的串。这个类型可以是‘图像’或‘Web站点’而随后的四个VARIANTs指定了项的初始位置:
HRESULT AddDesktopComponent(BSTR URL, BSTR Type,
VARIANT* Left, VARIANT* Top, VARIANT* Width, VARIANT* Height);
桌面项不是放在桌面文件夹中的文件,而是驻留在一个浮动框架上或嵌入到HTML页中可以设置为桌面背景的web页面。这个页的内容就是指定的URL的内容。通常这些URLs是提供头标题或连接到实际数据源的特定页。每一个通过桌面引用的URL都是自动订阅的。
最后IsSubscribed()方法根据我们是否订阅指定的URL返回布尔值。
HRESULT IsSubscribed(BSTR URL, VARIANT_BOOL* pBool);
添加到Favorites
下面代码段显示怎样用C++ 唤醒‘添加到Favorites’的系统对话框。作为提示和参考,等价的VB代码是:
Dim s As New ShellUIHelper
s.AddFavorite "c:/"
对C++ 程序员,添加C盘的根到‘Favorites’文件夹的代码是:
void AddDiskCToFavorites()
{
IShellUIHelper* pShellUI = NULL;
// 建立Shell UI 辅助对象
HRESULT hr = CoCreateInstance(CLSID_ShellUIHelper, NULL, CLSCTX_SERVER,
IID_IShellUIHelper, reinterpret_cast<LPVOID*>(&pShellUI));
if(FAILED(hr))
return;
// 设置添加项的标题
CComVariant vTitle(__TEXT("My C Drive"));
// 用默认设置导出对话框
CComBSTR bstrPath(__TEXT("c://"));
pShellUI->AddFavorite(bstrPath, &vTitle);
// 清理
pShellUI->Release();
}
程序示例
到现在为止,我们调查了IE4.x 动态库shdocvw.dll实现的几个对象。这些对象可以使我们用以前不能的方法编程驱动Shell。当前所有例子都是在VB环境下的,现在我们打算用纯C++代码给出涉及这些底层抽象Shell对象模型的例子。
现在首先建立基于对话框的工程(project)。命名为CppShell。下面是这个对话框的界面:
下表显示六个按钮的描述
按钮 |
活动 |
Find Computer |
使‘Find Computer’对话框显示。 |
Taskbar Properties |
使‘Taskbar Properties’对话框显示 |
BrowseForFolder |
浏览‘History’文件夹。选择的项显示在下面的文本框中。 |
NameSpace |
取得对‘开始’菜单文件夹的引用并枚举其内容到下面的列表观察中。 |
Minimize All / Undo Minimize All |
这个按钮使所有打开的窗口最小化,就象按下Windows-M组合键一样,而后标题变为‘Undo Minimize All’,其效果等价于Shift-Windows-M——即,恢复窗口。 |
Add to Favorites |
导致‘Add to Favorites’对话框显示 |
通常实现这个应用仅需写一些按钮处理器。下面的源代码包含了一些前面讨论过的内容。
首先要保证在对话框初始运行前初始化COM库。因此在WinMain()中加了两行:
// 允许通用控件
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&iccex);
// 初始化COM库
CoInitialize(NULL);
// 运行主对话框
BOOL b = DialogBox(hInstance, "DLG_MAIN", NULL, APP_DlgProc);
// 终止COM
CoUninitialize();
对话框上的每一个按钮都有自己的处理器函数,因此APP_DlgProc()可以有如下形式:
case WM_COMMAND:
switch(wParam)
{
case IDC_FINDCOMPUTER:
OnFindComputer();
return FALSE;
case IDC_PROPERTIES:
OnTaskbarProperties();
return FALSE;
case IDC_BROWSEFOLDER:
OnBrowseForFolder(hDlg);
return FALSE;
case IDC_NAMESPACE:
OnNameSpace(hDlg);
return FALSE;
case IDC_MINIMIZE:
OnMinimizeAll(hDlg);
return FALSE;
case IDC_FAVORITES:
OnAddFavorites();
return FALSE;
case IDCANCEL:
EndDialog(hDlg, FALSE);
return FALSE;
}
break;
下面是第一个处理器函数(最简单的一个)OnFindComputer():
void OnFindComputer()
{
IShellDispatch* pShellDisp = NULL;
HRESULT hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_SERVER,
IID_IShellDispatch, reinterpret_cast<LPVOID*>(&pShellDisp));
if(FAILED(hr))
return;
pShellDisp->FindComputer();
pShellDisp->Release();
}
同样容易的是OnTaskbarProperties()——事实上它只是调用Shell对象的不同方法:
void OnTaskbarProperties()
{
IShellDispatch* pShellDisp = NULL;
HRESULT hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_SERVER,
IID_IShellDispatch, reinterpret_cast<LPVOID*>(&pShellDisp));
if(FAILED(hr))
return;
pShellDisp->TrayProperties();
pShellDisp->Release();
}
相对复杂的是OnBrowseForFolder()函数,它首先取得Shell对象的指针,然后调用BrowseForFolder()方法,它恢复一个文件夹对象的指针:
void OnBrowseForFolder(HWND hDlg)
{
TCHAR szTitle[MAX_PATH] = {0};
IShellDispatch* pShellDisp = NULL;
Folder* pFolder = NULL;
HRESULT hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_SERVER,
IID_IShellDispatch, reinterpret_cast<LPVOID*>(&pShellDisp));
if(FAILED(hr))
return;
// 设置显示命名空间的根
CComVariant vRoot(CSIDL_HISTORY);
// 显示对话框
CComBSTR bstrFolder(__TEXT("History Folder:"));
hr = pShellDisp->BrowseForFolder(
reinterpret_cast<long>(hDlg), bstrFolder, 0, vRoot, &pFolder);
if(pFolder)
{
// 取得选择项的显示名
CComBSTR bstr;
pFolder->get_Title(&bstr);
// 转换成ANSI 和显示之
wcstombs(szTitle, bstr, MAX_PATH);
SetDlgItemText(hDlg, IDC_FOLDER, szTitle);
}
// 清理
pFolder->Release();
pShellDisp->Release();
}
如果获得了一个可用的文件夹对象,我们就调用get_Title()方法显示其名字到对话框。
OnNameSpace()是一个大得多的函数,但是其实际是输出图标所必需的代码而不是处理COM的代码。操作是相当直接的,调用模式也十分类似:
void OnNameSpace(HWND hDlg)
{
IShellDispatch* pShellDisp = NULL;
Folder* pFolder = NULL;
HRESULT hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_SERVER,
IID_IShellDispatch, reinterpret_cast<LPVOID*>(&pShellDisp));
if(FAILED(hr))
return;
// 设置工作文件夹
CComVariant vDir(CSIDL_STARTMENU);
// 取得文件夹对象
pShellDisp->NameSpace(vDir, &pFolder);
// 准备枚举文件夹内容
long nLength;
FolderItems* pFIColl = NULL;
pFolder->Items(&pFIColl);
pFIColl->get_Count(&nLength);
// 准备填充列表观察
HIMAGELIST himl = ImageList_Create(32, 32, ILC_MASK, 1, 1);
HWND hwndList = GetDlgItem(hDlg, IDC_LIST);
ListView_SetImageList(hwndList, himl, LVSIL_NORMAL);
// 枚举文件夹项
for(int i = 0 ; i < nLength ; i++)
{
// 取得第i个文件夹项
CComVariant varIndex(i);
FolderItem* pFI;
hr = pFIColl->Item(varIndex, &pFI);
if(SUCCEEDED(hr))
{
CComBSTR bstr;
TCHAR szFile[MAX_PATH] = {0};
// 取得ANSI版的第i项路径
pFI->get_Path(&bstr);
wcstombs(szFile, bstr, MAX_PATH);
// 添加项到列表观察
LV_ITEM lvi;
ZeroMemory(&lvi, sizeof(LV_ITEM));
lvi.mask = LVIF_TEXT | LVIF_IMAGE;
lvi.pszText = szFile;
lvi.cchTextMax = lstrlen(szFile);
// 取得图标并加入列表观察
SHFILEINFO sfi;
SHGetFileInfo(szFile, 0, &sfi, sizeof(SHFILEINFO), SHGFI_ICON);
int iIconPos = ImageList_AddIcon(himl, sfi.hIcon);
lvi.iImage = iIconPos;
ListView_InsertItem(hwndList, &lvi);
}
pFI->Release();
}
pFIColl->Release();
pFolder->Release();
pShellDisp->Release();
}
我们从建立Shell对象指针开始,然后调用NameSpace()函数取得了‘开始’菜单文件夹的指针,从这个指针的Items()方法,我们取得指向FolderItems对象的指针,最后得到FolderItem指针,从这个指针处取得图标和路径名。这实际是通过对象层次的方法取得信息的过程。
下面的两个函数仍然是小函数,它们与一个全程布尔标志相关,OnMinimizeAll()调用Shell对象的两个方法,并置换按钮的标题:
void OnMinimizeAll(HWND hDlg)
{
IShellDispatch* pShellDisp = NULL;
HRESULT hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_SERVER,
IID_IShellDispatch, reinterpret_cast<LPVOID*>(&pShellDisp));
if(FAILED(hr))
return;
// 使用全程标志记住当前状态
if(!g_bMinimized)
{
pShellDisp->MinimizeAll();
// 改变按钮标题
SetDlgItemText(hDlg, IDC_MINIMIZE, "&Undo Minimize All");
g_bMinimized = TRUE;
}else{
pShellDisp->UndoMinimizeALL();
// 改变按钮标题
SetDlgItemText(hDlg, IDC_MINIMIZE, "&Minimize All");
g_bMinimized = FALSE;
}
pShellDisp->Release();
}
最后的OnAddFavorites()函数也没有什么新工作,因为我们重新使用了AddDiskCToFavorites()函数体代码:
void OnAddFavorites()
{
IShellUIHelper* pShellUI = NULL;
// 建立Shell UI 辅助对象
HRESULT hr = CoCreateInstance(CLSID_ShellUIHelper, NULL, CLSCTX_SERVER,
IID_IShellUIHelper, reinterpret_cast<LPVOID*>(&pShellUI));
if(FAILED(hr))
return;
// 设置添加项的标题
CComVariant vTitle(__TEXT("My C Drive"));
// 使用指定的默认设置串导出对话框
CComBSTR bstrPath(__TEXT("c://"));
pShellUI->AddFavorite(bstrPath, &vTitle);
// 清理
pShellUI->Release();
}
这就是全部代码,但是需要包含几个头文件才能编译通过——#include comdef.h, exdisp.h, atlbase.h, shlobj.h 和resource.h。
小结
这一章我们从VB和C++的角度讨论了脚本Shell对象。如果在Windows上安装了IE4.x和活动桌面,或运行Windows98 以上版本,你就可以开发这些功能。这些对象的资料文档在Internet客户端SDK中(现在已经作为平台SDK),主要是对VB程序员有好处。我们也试图补足对C++的资料说明,特别是在如下方面:
Shell对象模型的函数
Folder 和 FolderItem对象
VARIANT 和 Unicode 串