Shell扩展编程完全指南(8)如何使用信息栏扩展

Shell扩展编程完全指南

dedecms.com

linghuye著
第八节-如何使用信息栏扩展添加定制的信息栏到资源浏览器详细资料列表中

织梦内容管理系统

下载示例工程 - 28 Kb

内容来自dedecms

读者要求继续这份指南! 在本节, 我将处理添加定制的信息栏到Windows2000资源浏览器详细资料列表中. 这种类型的扩展在NT 4 或Win 9x上不能用, 所以你必须在Win 2K 运行本文的示例程序. copyright dedecms

Windows 2000中的详细资料列表

Windows 2000 添加了许多自定义选择到浏览器的详细资料列表. 共有37 种不同的信息栏可以使用! 你可以用两个方法来开关这些信息栏. 首先, 当你右击栏标题弹出的菜单里有8个可选栏: dedecms.com

 [Header control context menu - 3K]

dedecms.com

如果你选择More...项, 浏览器显示一对话框,在其中你可以选择所有可获取的栏:

织梦内容管理系统

 [Default column settings dlg - 10K]

dedecms.com

浏览器让我们可以在一些栏里显示自己的数据, 甚至可以使用栏处理器扩展添加信息栏. 但浏览器好像不让添加的栏显示在上下文菜单中. 织梦内容管理系统

本文的示例工程是为MP3 文件设计的栏处理器,显示MP3文件的ID3 标签的各资料. 内容来自dedecms

使用AppWizard 开始

运行AppWizard 并生成一个名为MP3TagViewer的ATL工程. 由于这次我们要使用MFC,所以要选中Support MFC设置, 点击”完成”. 然后,在ClassView树中右击MP3TagViewer classes项,在弹出的菜单中选择New ATL Object,添加一个COM 对象类到DLL中.

copyright dedecms

在ATL Object Wizard 中, 第一页面已经选中了Simple Object, 因此, 单击Next. 在第二页面中, 在Short Name编辑框中输入MP3ColExt点击OK. (其余编辑框中的内容将自动填写.) 这样就创建了一个名为CMP3ColExt 的已实现基本的COM接口的新COM对象类. 我们将在这个类中添加实现代码.

copyright dedecms

扩展接口

一个栏扩展只需实现一个接口: IColumnProvider. 它没有使用分离的IShellExtInitIPersistFile接口. 这是因为栏扩展作用于文件夹对象, 与当前选中的文件项目无关. 而IShellExtInitIPersistFile接口处理有项目选择时的情况. 该扩展确实需要初始化, 但它是通过IcolumnProvider 的一个方法.

织梦好,好织梦

要添加IColumnProvider接口到我们的COM 对象, 打开MP3ColExt.h并添加下面标红的代码:

copyright dedecms

#include <comdef.h>  
dedecms.com
#include <shlobj.h>  本文来自织梦 
#include <shlguid.h>  

dedecms.com

 织梦内容管理系统 
struct __declspec(uuid("E8025004-1C42-11d2-BE2C-00A0C9A83DA1")) IColumnProvider; dedecms.com 
 

内容来自dedecms

///////////////////////////////////////////////////////////////////////////// 织梦好,好织梦 
// CMP3ColExt 本文来自织梦 
 
dedecms.com
class ATL_NO_VTABLE CMP3ColExt :  内容来自dedecms 
public CComObjectRootEx<CComSingleThreadModel>, 织梦内容管理系统 
public CComCoClass<CMP3ColExt, &CLSID_MP3ColExt>, 
内容来自dedecms
public IMP3ColExt, 内容来自dedecms 
public IColumnProvider 织梦内容管理系统 
{ 
织梦内容管理系统
BEGIN_COM_MAP(CMP3ColExt) 

内容来自dedecms

COM_INTERFACE_ENTRY(IMP3ColExt) copyright dedecms 
COM_INTERFACE_ENTRY(IColumnProvider) 

织梦内容管理系统

END_COM_MAP() 

内容来自dedecms

 本文来自织梦 
public: 
dedecms.com
// IColumnProvider  织梦内容管理系统 
STDMETHOD (Initialize)(LPCSHCOLUMNINIT psci) { return S_OK; }  

copyright dedecms

STDMETHOD (GetColumnInfo)(DWORD dwIndex, SHCOLUMNINFO* psci);  dedecms.com 
STDMETHOD (GetItemData)(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT* pvarData);  织梦好,好织梦 
}; 本文来自织梦 

注意在类声明之前的IColumnProvider接口声明. 这是为了让COM_INTERFACE_ENTRY宏正确工作的必须条件. 微软忘了在comdef.hIColumnProvider接口定义一个UUID, 所以我们需要自己来定义. ATL 有个COM_INTERFACE_ENTRY_IID宏专门适用这种没有用__declspec(uuid())语法给接口赋予一个符号标志的情况, 但当我使用该宏时, 浏览器会传给IDispatch::GetTypeInfo() 一个错误的指针使得扩展运行崩溃.

织梦好,好织梦

我们同时要修改一下stdafx.h. 因为我们使用Win 2000 的特性功能, 我们需要用#define定义一些值好让我们获取使用该特性的函数及结构: 织梦内容管理系统

#define WINVER0x0500// W2K/98 织梦内容管理系统 
#define _WIN32_WINNT 0x0500// W2K 织梦好,好织梦 
#define _WIN32_IE0x0500// IE 5+ 

本文来自织梦

初始化

IColumnProvider接口有三个方法. 头一个是Initialize(), 其原型为: dedecms.com

HRESULT IColumnProvider::Initialize ( LPCSHCOLUMNINIT psci ); copyright dedecms 

Shell传递给我们一个SHCOLUMNINIT结构, 此时它仅包含少量的信息, 即浏览器窗口中所所查看得文件夹的全路径. 由于我们的扩展不需要这个信息, 所以只要返回S_OK. dedecms.com

列举新信息栏

当浏览器看到我们的注册的栏扩展处理器, 它将调用扩展获取添加的栏的数据. 这些工作在GetColumnInfo()中完成, 其原型为: dedecms.com

HRESULT IColumnProvider::GetColumnInfo ( DWORD dwIndex, SHCOLUMNINFO* psci ); 
本文来自织梦

dwIndex是个从0开始的计数指出浏览器要获取数据的栏. 另一个参数SHCOLUMNINFO结构需要我们往里填充数据. dedecms.com

SHCOLUMNINFO结构的第一个成员是另一个结构:SHCOLUMNID. SHCOLUMNID结构是GUID/DWORD 对, 这里GUID 称为"格式ID" 而DWORD 值称为"属性ID." 这对值唯一地标识系统中的任一栏. 可以重用已存在的栏(如, Author), 这时格式ID 和属性ID 都是预设的值. 如果扩展要添加新的栏, 可以使用它的CLSID 作为属性ID (因为CLSID 也是唯一的), 并使用一个简单的计数作为属性ID. 内容来自dedecms

我们的扩展将同时使用这两种方法. 我们将重用Author, Title,和Comments 栏, 并添加另外三个: MP3 唱片名, MP3 年份, 和MP3 歌曲类型. 本文来自织梦

下面是GetColumnInfo()方法的开头部分: dedecms.com

STDMETHODIMP CMP3ColExt::GetColumnInfo ( DWORD dwIndex, SHCOLUMNINFO* psci ) 
织梦好,好织梦
{ 

dedecms.com

// 我们有6个栏, 所以如果dwIndex 大等于6, 就返回S_FALSE表明我们列举完所有的列了. dedecms.com 
if ( dwIndex > 5 ) 织梦好,好织梦 
return S_FALSE; 本文来自织梦 

如果dwIndex 大等于6, 我们返回S_FALSE以停止列举. 否则, 我们填充SHCOLUMNINFO结构. 对0到2的dwIndex 值, 我们返回我们新栏的数据. 对3到5的值, 我们返回重用的内建栏数据. 下面是我们对第一列的数据设定, 其显示ID3标签的唱片名: 织梦内容管理系统

switch ( dwIndex ) 织梦内容管理系统 
{ 
织梦好,好织梦
case 0:// MP3唱片名 
织梦内容管理系统
{ 
dedecms.com
psci->scid.fmtid = *_Module.pguidVer;// 好用的 GUID 

织梦内容管理系统

psci->scid.pid= 0;// 任一ID都可以用, 但使用序数最简单 dedecms.com 
psci->vt= VT_LPSTR;// 将返回字符串数据 dedecms.com 
psci->fmt= LVCFMT_LEFT;// 文本左对齐 
织梦好,好织梦
psci->csFlags= SHCOLSTATE_TYPE_STR; // 数据按字符串顺序排列 

copyright dedecms

psci->cChars= 32;// 默认的栏宽度 

内容来自dedecms

 
织梦好,好织梦
lstrcpynW ( psci->wszTitle, L"MP3 Album", MAX_COLUMN_NAME_LEN ); copyright dedecms 
lstrcpynW ( psci->wszDescription, L"Album name of an MP3", MAX_COLUMN_DESC_LEN ); 
内容来自dedecms
} 

dedecms.com

break; 织梦好,好织梦 

我们使用自己的模块的GUID 作为格式ID, 并用栏序数为属性ID.SHCOLUMNINIT结构的Vt成员变量标识我们返回给浏览器的数据类型. VT_LPSTR表示一个C风格的字符串. fmt成员变量是LVCFMT_*常数之一, 表示文本在栏里的对齐方式. 在这个例子里文本将左对齐. 织梦内容管理系统

csFlags成员包含一些关于栏信息的标志. 但是, 并非所有的标志都为Shell所实现. 下面解释了设置标志所产生效果:

copyright dedecms

SHCOLSTATE_TYPE_STR, SHCOLSTATE_TYPE_INT, 和SHCOLSTATE_TYPE_DATE 织梦好,好织梦

表示当浏览器进行排序时应如何处理栏数据. 三个可能值为字符串, 整数, 和日期. 内容来自dedecms

SHCOLSTATE_ONBYDEFAULT

copyright dedecms

文档中说包含该标志会使该栏默认显示在文件夹窗口中, 直到用户禁用该栏. 可是我没能做出这种效果. copyright dedecms

SHCOLSTATE_SLOW

织梦内容管理系统

根据文档上说, 包含该标志表明获取栏数据要费一点时间, 这样浏览器会运行后台线程来使浏览器界面保持反映. 但我在测试中发现没什么区别. 浏览器总是只使用一个线程来收集扩展栏的数据.

copyright dedecms

SHCOLSTATE_SECONDARYUI 织梦好,好织梦

文档中说设置该标志可以防止栏显示在标题的右击上下文菜单中. 这意味着如果你不包含该标志, 栏将会显示在上下文菜单中. 但是, 额外添加的栏总不会显示上下文菜单中, 所以该标志无效.

织梦好,好织梦

SHCOLSTATE_HIDDEN

织梦内容管理系统

传递该标志防止该栏显示在Column Settings 对话框中. 因为目前没有一种方法能隐藏一个栏,该标志会使一个栏无效.

织梦内容管理系统

char szComment[30]; 
织梦内容管理系统
char byGenre; 
本文来自织梦
}; dedecms.com 

所有的字段都是简单的字符, 并且字符串都不需要以null为结束符, 这需要一些特殊处理. 第一个字段, szTag, 包含字符"TAG" 来标识ID3 标签. byGenre是标识歌曲类型的数字. (歌曲类型和ID都是预设好的值,可以从ID3.org 上获取.)

织梦好,好织梦

我们也需要另一个结构保存ID3 标签和标签来源的文件名. 该结构的使用我稍后介绍.

内容来自dedecms

#include <string> 织梦内容管理系统 
#include <list> 织梦内容管理系统 
typedef std::basic_string<TCHAR> tstring;// TCHAR 字符串 

本文来自织梦

 内容来自dedecms 
struct CID3CacheEntry 织梦好,好织梦 
{ 

本文来自织梦

tstringsFilename; copyright dedecms 
CID3v1Tag rTag; 织梦好,好织梦 
}; copyright dedecms 
 

织梦内容管理系统

typedef std::list<CID3CacheEntry> list_ID3Cache; 

本文来自织梦

CID3CacheEntry对象保存文件名和ID3 标签. list_ID3Cache是一个CID3CacheEntry结构的列表. dedecms.com

OK, 回到扩展的话题上. 下面是GetItemData()函数的开始. 我们首先检查SHCOLUMNID结构以确认是我们定制的栏所激发的调用.

织梦内容管理系统

#include <atlconv.h> 

copyright dedecms

 
内容来自dedecms
STDMETHODIMP CMP3ColExt::GetItemData ( 内容来自dedecms 
LPCSHCOLUMNIDpscid, 

织梦内容管理系统

LPCSHCOLUMNDATA pscd, copyright dedecms 
VARIANT*pvarData ) 

织梦好,好织梦

{ 
本文来自织梦
USES_CONVERSION; 

本文来自织梦

LPCTSTRszFilename = OLE2CT(pscd->wszFile); 织梦好,好织梦 
charszField[31]; 
本文来自织梦
TCHARszDisplayStr[31]; 本文来自织梦 
boolbUsingBuiltinCol = false; dedecms.com 
CID3v1Tag rTag; 

织梦好,好织梦

boolbCacheHit = false; 织梦好,好织梦 
 dedecms.com 
// 检查是不是我们希望的格式ID和栏数字. 织梦内容管理系统 
if ( pscid->fmtid == *_Module.pguidVer ) 

dedecms.com

{ 

copyright dedecms

if ( pscid->pid > 2 ) dedecms.com 
return S_FALSE; 内容来自dedecms 
} 
织梦好,好织梦

如果格式ID 是我们的GUID, 属性ID 必须是0, 1, or 2, 因为这些ID我们要在GetColumnInfo() 中使用. 如果, 因为某些原因, ID超出这个范围, 我们返回S_FALSE告诉Shell 我们没有相应的数据, 该栏将显示为空. 内容来自dedecms

我们接着比较该格式ID 与FMTID_SummaryInformation, 并检查属性ID看是否是我们提供的属性.

本文来自织梦

else if ( pscid->fmtid == FMTID_SummaryInformation ) 织梦好,好织梦 
{ 
织梦好,好织梦
bUsingBuiltinCol = true; 
内容来自dedecms
 

本文来自织梦

if ( pscid->pid != 2&&pscid->pid != 4&&pscid->pid != 6 ) 
内容来自dedecms
return S_FALSE; 内容来自dedecms 
} 

本文来自织梦

else copyright dedecms 
{ 
dedecms.com
return S_FALSE; 本文来自织梦 
} 

织梦内容管理系统

接着, 我们检查传进的文件的属性. 如果是个目录, 或其为离线文件(即, 它被移到另一存储介质上), 退出. 同时我们检查其文件扩展名,如果不是.MP3 就退出. 织梦内容管理系统

// 如果是文件夹,退出. 内容来自dedecms 
// 如果是离线文件,退出。 
织梦内容管理系统
if ( pscd->dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_OFFLINE) ) copyright dedecms 
return S_FALSE; dedecms.com 
 
copyright dedecms
// 检查扩展名. 如果不是 .MP3, 退出. 

织梦好,好织梦

if ( 0 != lstrcmpiW ( pscd->pwszExt, L".mp3" )) 

copyright dedecms

return S_FALSE; 
本文来自织梦

此时,我们要开始操作该文件. 下面是我们的ID3 标签的使用.MSDN 文档说Shell以文件为组调用GetItemData(), 意思是它将使用相同的文件名连续调用GetItemData()(就是一组处理完一个文件的栏数据的显示,再接着处理下一个文件). 我们利用这一点缓存特殊文件的ID3 标签, 因此我们不用反复读取文件的标签. dedecms.com

我们首先轮循缓冲(保存为一个变量, m_ID3Cache), 并对比缓冲的文件名和传进来的文件名. 如果我们发现我们的文件名已在缓冲中, 我们直接获取关联的ID3 标签. 织梦好,好织梦

// 在缓冲中查找文件名. 本文来自织梦 
list_ID3Cache::const_iterator it, itEnd; 

本文来自织梦

 内容来自dedecms 
for ( it = m_ID3Cache.begin(), itEnd = m_ID3Cache.end(); 织梦好,好织梦 
!bCacheHit && it != itEnd; it++ ) 
本文来自织梦
{ 
本文来自织梦
if ( 0 == lstrcmpi ( szFilename, it->sFilename.c_str() )) 
dedecms.com
{ 

织梦好,好织梦

CopyMemory ( &rTag, &it->rTag, sizeof(CID3v1Tag) ); 
内容来自dedecms
bCacheHit = true; 织梦内容管理系统 
} 

织梦好,好织梦

} copyright dedecms 

在循环后如果bCacheHit为假, 我们需要读取文件再看一下它是否有ID3 标签. 该帮助函数ReadTagFromFile()读取该文件的最后128 个字节, 并返回TRUE表示成功或FALSE 表示失败. 注意ReadTagFromFile()返回最后128 字节而不管它们是否真是ID3 标签. 织梦内容管理系统

// 如果文件标签不再缓冲中, 从文件中读取. 

本文来自织梦

if ( !bCacheHit ) 织梦内容管理系统 
{ 织梦内容管理系统 
if ( !ReadTagFromFile ( szFilename, &rTag )) 

织梦内容管理系统

return S_FALSE; 
本文来自织梦

所以现在我们有了ID3 标签. 我们检查缓冲的大小, 它包含5项, 最旧将被移出缓冲以腾出空间给新的项. 我们创建了一个新的CID3CacheEntry对象并将之添加进列表.

织梦好,好织梦

// 我们将保留最近5个文件的 – 移出最旧的 
dedecms.com
// entries if the cache is bigger than 4 entries. 
内容来自dedecms
while ( m_ID3Cache.size() > 4 ) 

织梦内容管理系统

{ 
织梦好,好织梦
m_ID3Cache.pop_back(); 
dedecms.com
} 织梦好,好织梦 
 织梦好,好织梦 
// 添加新的 ID3到缓冲. dedecms.com 
CID3CacheEntry entry; dedecms.com 
 
织梦好,好织梦
entry.sFilename = szFilename; 内容来自dedecms 
CopyMemory ( &entry.rTag, &rTag, sizeof(CID3v1Tag) ); 

dedecms.com

 
织梦好,好织梦
m_ID3Cache.push_front ( entry ); 本文来自织梦 
}// end if(!bCacheHit) 

本文来自织梦

下一步要测试头三个标志字节以决定有无ID3 标签数据. 如果没有, 立即返回.

dedecms.com

// 测试头三个标志字节以决定有无ID3 标签数据 

织梦内容管理系统

if ( 0 != StrCmpNA ( rTag.szTag, "TAG", 3 )) 本文来自织梦 
return S_FALSE; 

本文来自织梦

接着,我们读取ID3 标签中的Shell要求的属性字段. 这涉及到属性ID的测试. 下面是一个例子, 获取歌曲标题字段: 内容来自dedecms

// 格式化数据字符. copyright dedecms 
if ( bUsingBuiltinCol ) 织梦好,好织梦 
{ 
copyright dedecms
switch ( pscid->pid ) copyright dedecms 
{ 织梦内容管理系统 
case 2:// 歌曲标题 dedecms.com 
CopyMemory ( szField, rTag.szTitle, countof(rTag.szTitle) ); 
内容来自dedecms
szField[30] = '\0'; copyright dedecms 
break; 织梦内容管理系统 
... 本文来自织梦 
} 
织梦好,好织梦

注意我们的szField缓冲是31 个字符长, 比最长的ID3v1 字段大1. 这样做可以保证以null为字符串的结束符. The bUsingBuiltinCol标志在先前我们测试FMTID/PID 对时已设置. 我们需要该标志因为只有PID 不足以标识一个栏– 标题及MP3 类型栏的属性ID均为2. 织梦好,好织梦

此时, szField包含我们从ID3 标签中读出的字符串. WinAmp 的ID3 标签编辑器用空格隔开字符串而不用null 字符串, 所以我们通过删除尾部空格来修正这一点: 内容来自dedecms

// // WinAmp 用空格隔开字符串而不用null 字符串, 我们通过删除尾部空格来修正这一点. 
织梦内容管理系统
StrTrimA ( szField, " " ); 
织梦好,好织梦

最后, 我们创建一个CComVariant对象并储存szDisplayStr字符串. 接着我们调用CComVariant::Detach()将数据从CComVariant拷贝到浏览器给出的VARIANT变量中. 织梦内容管理系统

// 创建一个详细字符串的 VARIANT, 并将之返回给shell. copyright dedecms 
CComVariant vData ( szField ); 
织梦内容管理系统
 dedecms.com 
vData.Detach ( pvarData ); 
内容来自dedecms
 
copyright dedecms
return S_OK; 本文来自织梦 
} 
织梦内容管理系统

它是什么样的?

我们添加的新栏出现在Column Settings对话框列表的末尾: 内容来自dedecms

 [Column settings dlg with new columns - 10K]

copyright dedecms

下面是栏的样子. 文件根据我们自定义Author字段进行排序.

dedecms.com

织梦好,好织梦

注册Shell扩展

因为栏扩展处理器扩展了文件夹, 其应该在HKCR\Folders键下进行注册. 下面是注册所需的RGS 脚本文件: 织梦内容管理系统

HKCR 
dedecms.com
{ 织梦好,好织梦 
NoRemove Folder 本文来自织梦 
{ 
内容来自dedecms
NoRemove Shellex copyright dedecms 
{ 

织梦好,好织梦

NoRemove ColumnHandlers copyright dedecms 
{ 

织梦好,好织梦

ForceRemove {AC146E80-3679-4BCA-9BE4-E36512573E6C} = s 'MP3 ID3v1 viewer column ext' copyright dedecms 
} 

织梦内容管理系统

} dedecms.com 
} 
织梦内容管理系统
} 

copyright dedecms

另一个好东西- InfoTips

栏扩展处理器能完成的另一个有趣的东西是为一种文件类型自定义InfoTip. 下面的RGS 脚本文件为MP3文件类型创建一个自定义InfoTip:

copyright dedecms

HKCR 
织梦内容管理系统
{ 织梦好,好织梦 
NoRemove .mp3 

内容来自dedecms

{ 
本文来自织梦
val InfoTip = s 'prop:Type;Author;Title;Comment;{AC146E80-3679-4BCA-9BE4-E36512573E6C},0; 织梦好,好织梦 
{AC146E80-3679-4BCA-9BE4-E36512573E6C},1;{AC146E80-3679-4BCA-9BE4-E36512573E6C},2;Size' 织梦好,好织梦 
(注意:上两行实际为一行)  织梦好,好织梦 
} 
织梦好,好织梦
} copyright dedecms 

注意Author, Title, 和Comment 字段出现在prop 中:当你的鼠标盘旋于MP3 文件之上时, 浏览器将调用我们的扩展获取这些字段的显示字符串. MSDN 文档上说我们添加的定制字段也会出现在InfoTips 上(这是为什么我们的GUID 和属性ID出现在上面的字符串中), 然而在Win2K 下这不能工作. 只有内建的属性才出现在InfoTips 中. 下面是InfoTip 的样子: 织梦好,好织梦

 [Custom InfoTip - 3K]

dedecms.com