Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2095791
  • 博文数量: 909
  • 博客积分: 4000
  • 博客等级: 上校
  • 技术积分: 12260
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-06 20:50
文章分类

全部博文(909)

文章存档

2008年(909)

我的朋友

分类:

2008-05-06 21:36:15

一起学习
Windows 2000 UI 新特点之二
增强的文件夹特性:自定义文件夹栏目

原文参见:MSDN Magazine March 2000


编译/赵湘宁

第一部分 Windows 2000 UI 新特点之一:信息条提示(Infotip)


本文源代码

本文假设您熟悉windows的外壳编程及ATL。

摘要:
本文是关于Windows 2000 UI 新特点系列文章的第二篇,本文将创建一个定制的栏目管理器扩展(column handler extension),它使我们能通过Windows的资源管理器在“查看”菜单的“详细资料”栏的视图中看到新创建的文件属性栏目。

在Windows 2000中,有五种可能的方式来浏览一个文件夹的内容。(虚拟文件夹和名字空间扩展除外)。传统的方式有四种:大图标、小图标、列表和详细资料。第五种方式为:缩略图方式(thumbnail mode)。缩略图方式使用正方形的象素区显示常规的文件对象图标。这种视图模式对于图像文件夹和包含可预览文档的文件夹最有用,如:元文件、图形文件和预览开关打开的MS Office文档。
在所有的浏览方式中,详细资料模式是提供信息最丰富的一种方式。这种方式是通过栏目来组织实现的。在Windows 2000 以前,典型的栏目是:名字、大小、类型和修改日期,可不可以为文件再添加更多的栏目呢?象隐藏、只读或版本号等。答案是肯定的。Windows 2000不但引入了许多可以预定义的栏目,甚至还可以定制和创建自己的栏目。如果您在任何栏目的说明上单击右键,会弹出上下文菜单,包含的菜单项是所有可获得的栏目的一个子集。然后您可以打开和关闭任何栏目,但名字栏除外。(参见图五)

图五 详细资料视图中的栏目

通过选择“更多选项(More)”,还可以显示出整个栏目列表。有 、三个栏目是让人耳目一新的:即创建日期、作者和模块版本。创建日期显示的是文件或文件夹最初创建的日期。作者栏则显示的是按照复合文件的摘要信息(SummaryInformation)头内容指定的文档签名者的名字。尤其是在一个全是Office文档的文件夹中,一眼就能区分出某人写的文档。而且,一旦显示了一个新的栏目,同时也能按这个栏目来对文件排序。注意作者信息只在文档被存为嵌入了摘要信息头的复合文档时才能获得。除了微软的Office文档格式以外(Word、Execel 或PowerPoint),输出摘要信息块的文档类型不多,除了Office文档以外,还有FlashPix图像文件也输出摘要信息块。
在栏目中包含可执行文件版本号“Module Version” (模块版本栏目)是人们期待已久的事情了。图六中显示的就是一个在system32文件夹中使能的模块版本栏目。

图六 Module Version 栏目

通过栏目选择对话框,您可以设置一个缺省的栏目宽度并选择它显示的位置。栏目重新排序使每个文件夹的基本特性,在某个文件夹上通过对属性的调整,总是能使所有文件夹的有相同的外观,方法是在文件夹选项对话框的“视图(View)”标签中按“与当前文件夹一样(Like Current Folder)”按钮。还有一个关于文件夹的设置是“记忆每个文件夹的设置(Remember each folder’s setting)”,这个特性允许您控制是否将全程文件夹选项应用到每一个单个的文件夹。
上面所讲的文件夹是针对相应的文件系统目录而言的。其他类型的文件夹,如名字空间扩展(namespace extensions),由其自己定义非栏目视图。但也有例外,那就是“我的文档”和“Favorites”。它们是名字空间扩展,但它们的内容映象到常规文件夹目录,提供标准的表格式视图并对应到选中的当前文件夹设置。
想要编写基于栏目的名字空间扩展应用,应该提供一种允许从说明栏上单击右键弹出的上下文菜里定制栏目。

如何定义定制栏目

Windows 2000 允许您定制栏目并将它加到列表中(见图五)。为了定义一个新栏目,必须要编写并注册一个新的外壳类型:栏目处理器(Column Hander)。大多数情况下,一个栏目为一个文件类提供特殊信息----例如,BMP文件的尺寸和调色板大小。然而栏目处理器并不绑定到文件类,而是文件夹对象的一个特性。换句话说,您不用在位图文件类子树中注册一个栏目处理器,而要在文件夹子树下注册。用扩展的代码来区分文件,它知道如何有针对性地处理和返回文件的信息。
一个栏目处理器扩展需要实现单接口:IColumnProvider。这样就使得在初始化时与其它需要IPersistFile 或 IShellExtInit 接口的扩展有些轻微的差别(这是因为实际上栏目处理器不适用于文件类)。

建立BMP文件的尺寸栏目

先创建一个新的ATL工程并插入一个对象,将其progID指定为BmpCol.BmpColInfo 。下面是这个扩展的实现源代码:
栏目扩展实现代码



// IColumnProviderImpl.h



#include 

#include 



class ATL_NO_VTABLE IColumnProviderImpl : public IColumnProvider 

{

protected:

    TCHAR m_szFolder[MAX_PATH];

public:

    // IUnknown

    STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) = 0;

    _ATL_DEBUG_ADDREF_RELEASE_IMPL(IColumnProviderImpl)



    // IColumnProvider 

    // IColumnProvider::Initialize

    STDMETHOD(Initialize)(LPCSHCOLUMNINIT psci){

        USES_CONVERSION;

        _tcscpy(m_szFolder, OLE2T((WCHAR*)psci->wszFolder)); 

        return S_OK;

    }

    // IColumnProvider::GetColumnInfo

    STDMETHOD(GetColumnInfo)(DWORD dwIndex, 

                             SHCOLUMNINFO *psci){ 

        return S_FALSE; 

    }

    // IColumnProvider::GetItemData

    STDMETHOD(GetItemData)(LPCSHCOLUMNID pscid, 

                           LPCSHCOLUMNDATA pscd, 

                           VARIANT *pvarData){ 

        return S_FALSE; 

    }

};



// BmpColInfo.h : Declaration of the CBmpColInfo



#ifndef __BMPCOLINFO_H_

#define __BMPCOLINFO_H_



#include "resource.h"            // main symbols

#include              // GUID of IColumnProvider

#include "IColumnProviderImpl.h" // IColumnProvider base impl



const UINT BMPCH_DEFWIDTH = 16;// column default width in chars 

const UINT BMPCH_MAXSIZE = 80;  // max text size 

const DWORD BMPCH_NUMOFCOLS = 1;// number of columns handled 

                                // here



// CBmpColInfo

class ATL_NO_VTABLE CBmpColInfo : 

    public CComObjectRootEx,

    public CComCoClass,

    public IColumnProviderImpl,

    public IDispatchImpl{

public:

    CBmpColInfo(){}



DECLARE_REGISTRY_RESOURCEID(IDR_BMPCOLINFO)

DECLARE_PROTECT_FINAL_CONSTRUCT()



BEGIN_COM_MAP(CBmpColInfo)

    COM_INTERFACE_ENTRY(IBmpColInfo)

    COM_INTERFACE_ENTRY_IID(IID_IColumnProvider, CBmpColInfo)

    COM_INTERFACE_ENTRY(IDispatch)

END_COM_MAP()



// IColumnProvider

public:

    STDMETHOD(GetColumnInfo)(DWORD, SHCOLUMNINFO *);

    STDMETHOD(GetItemData)(LPCSHCOLUMNID, LPCSHCOLUMNDATA, 

                           VARIANT *);

private:

    STDMETHOD(GetBitmapInfo)(LPCWSTR, LPTSTR);

};



#endif //__BMPCOLINFO_H_



// BmpColInfo.cpp : Implementation of CBmpColInfo



#include "stdafx.h"

#include "BmpCol.h"

#include "BmpColInfo.h"



HRESULT CBmpColInfo::GetColumnInfo(DWORD dwIndex, 

                                   SHCOLUMNINFO *psci){ 



    // 因为此扩展可以提供多个栏目,用索引号列举。外壳用递增的

    // 索引号重复调用此方法,直到返回S_FALSE。只有当列举了所有 

    // 的栏目后才返回S_FALSE。dwIndex 是基于 0 的索引。注意必须

    // 对它进行检查,否则会进入无限循环!



    if (dwIndex >= BMPCH_NUMOFCOLS)

        return S_FALSE;



    // 先填写 SHCOLUMNINFO 结构,使外壳知道此栏目的常规特性



    // 用一对 FMTID/PID 值确定栏目。必须为每一个添加的栏目定义

    // 一对 FMTID/PID 值。

    

    psci->scid.fmtid = *_Module.pguidVer; // use object''s CLSID

    psci->scid.pid = 1;



    // Sets type, alignment and default width

    psci->vt = VT_LPSTR;              // data is LPSTR

    psci->fmt = LVCFMT_LEFT;          // left alignment

    psci->cChars = BMPCH_DEFWIDTH;    // default width in chars

    

    // Other flags

    psci->csFlags = SHCOLSTATE_TYPE_STR;

        

    // Caption and description

    wcsncpy(psci->wszTitle, L"Dimensions", MAX_COLUMN_NAME_LEN);

    wcsncpy(psci->wszDescription, 

            L"Provides dimensions and colors for BMPs", 

            MAX_COLUMN_DESC_LEN);



    return S_OK; 

}



// IColumnProvider::GetItemData

HRESULT CBmpColInfo::GetItemData(LPCSHCOLUMNID pscid,

                                 LPCSHCOLUMNDATA pscd, 

                                 VARIANT *pvarData){ 

    USES_CONVERSION;

    

    // 一旦选中栏目,则外壳便为先是在该文件夹中的每一个

    // 文件调用此方法。

    // 如果你处理多个栏目,则 SHCOLUMNID 结构对栏目进行

    // 具体确定。



    // 此例中由于只处理一个栏目,所以可以忽略 SHCOLUMNID 

    // 结构参数。



    // 具体的文件的信息包含在 SHCOLUMNDATA 结构中。

    // 在此只对 .BMP 感兴趣。

    

    if (wcsicmp(pscd->pwszExt, L".bmp")) return S_FALSE;



    // Reads dimensions and palette size from the BMP file

    TCHAR szBuf[BMPCH_MAXSIZE];

    GetBitmapInfo(pscd->wszFile, szBuf);



    // The return value (a string in this case) must be 

    // packed as a Variant.

    CComVariant cv(szBuf);

    cv.Detach(pvarData); 



    return S_OK; 

}



HRESULT CBmpColInfo::GetBitmapInfo(LPCWSTR wszFile, LPTSTR p){

    USES_CONVERSION;

    BITMAPFILEHEADER bf;

    BITMAPINFOHEADER bi;

    HFILE h;



    // Reads the file header

    h = _lopen(OLE2T(wszFile), OF_READ);

    if (h==HFILE_ERROR) return S_OK;



    _lread(h, (LPBITMAPFILEHEADER)&bf, sizeof(BITMAPFILEHEADER));

    _lread(h, (LPBITMAPINFOHEADER)&bi, sizeof(BITMAPINFOHEADER));

    _lclose(h);



    // Formats the string 

    wsprintf(p, _T("%d x %d x %d"), bi.biWidth, bi.biHeight, 

             bi.biBitCount);  



    return S_OK;

}        
IColumnProvider提供了三个方法:Initialize, GetColumnInfo, 和GetItemData。Initialize在外壳设置这个扩展作用于某个文件夹时被调用。文件夹的名字是在作为方法参数的SHCOLUMNINIT结构中被传递的。大多数情况下,您不用着这么做。本文源代码提供的标准ATL类(IProviderColumnImpl.h)已经将文件夹名存储在一个成员变量中。除非调用文件夹系统告诉您需要采取别的特别动作。您也可以不用这个方法,而是使用GetColumnInfo函数,它是一个很重要的函数:
        HRESULT GetColumnInfo(DWORD dwIndex, SHCOLUMNINFO *psci);        
GetColumnInfo要给外壳传递尽可能多的栏目信息,包括说明、描述、管理的数据类型,以及您想让用户如何与数据交互。这些信息都填写在SHCOLUMNINFO结构中。因为栏目管理器能管理一个以上的栏目,所以应该提供一对唯一的IDs来明确标识这个栏目。这对IDS一个称为格式ID(FMTID)和另外一个是属性ID(PID),一并形成SHCOLUMNINFO数据结构的内容。格式ID是标识属性集的GUID,而属性ID是在属性集中特定属性的索引。FMTID/PID是在COM结构化存储中实现持久稳固的属性集的更一般机制的一个实例。
典型的属性集是摘要信息的属性集(Summary Information Property Set),其PIDs包括作者、标题、关键字、版本号等等。在现有的诸多栏目中,每一个栏目都是由FMTID和PID来标识的。您既可以创建一个新的栏目,有一对新的FMTID/PID,也可以将自己的栏目处理器关联到现存的栏目。例如,假设有一个标题栏,系统提供的栏目处理器用它来显示Office文档的标题,您还想用相同的标题栏来显示HTML文档的标题。那就要创建一个栏目处理器,然后填写这个栏目的SHCOLUMNID 结构。
        psci->scid.fmtid = FMTID_SummaryInformation;

        psci->scid.pid = 2;  
这里 2 是标题属性的PID。现在可以在标准的标题栏下编写您的扩展。
为了从一个Web页中获得标题,您不必调用HTML文档对象模型(DOM)。只要对使用最简单的串搜索就足够了。结果,您可获得共享相同的栏目的两个或更多的栏目处理器,将栏目的概念增强到了文件夹的系统级属性。
如果在单独栏目上显示的信息与现存的属性集无关,您同样可以创建有一个或更多属性的新属性集。FMTID只是一个128位的GUID值,您需要自己产生获得到这个值。最容易的方式是使用您编写的相同COM对象的GUID。通过全程变量 _Module,ATL可以对它进行访问。
        psci->scid.fmtid = *_Module.pguidVer;

        psci->scid.pid = 1;  
可以使用任何数字来标识PID。这里,选择 1。
有几个栏目的特点可以选择,如:文本对齐,返回的数据类型(文本、数字或日期),缺省宽度。注意您必须指定字符宽度来代替数字表示的单位。当然,您也能指定栏目的说明(在本文例子中是位图文件的尺寸)以及描述。
请注意GetColumnInfo的参数dwIndex。它是一个基于零的步长指示,通过它外壳列举处理器提供的不同的栏目,或者说,外壳按以下的伪码来运行:
dwColIndex = 0;

while (true)

{

    SHCOLUMNINFO shci;

    hr = pColumnProvider->GetColumnInfo(dwColIndex  ,

         &shci);

    if (FAILED(hr))

        break;

    // other shell-specific code

}
应该用一个栏目数常量与dwIndex进行比较,当指定的栏目数完成时便跳出循环。如果只提供一个栏目处理器,就使用下列代码:
        if (dwIndex >= 1) return S_FALSE;  
否则,模块将陷入死循环。
如果想支持多个栏目,记住重复调用GetColumnInfo一直到返回S_FALSE为止。每次调用时,得到一个增加的索引来表示一个新的栏目,所以老式的开关语句能解决这个问题。
switch(dwIndex)

{

    case 0:

        InitColumn_Dimensions(pshci);

    case 1:

        InitColumn_Title(pshci);

    // other code

}
只要正确实现了GetColumnInfo,当您点击“More”上下文菜单项,新栏目就会出现在对话框的底部。使用资源管理器的“查看”=>“选择栏目”菜单项可以到达相同的对话框。

为了给栏目提供运行时文本,您还应该实现IColumnProvider的GetItemData方法。它带三个参数。第一个是SHCOLUMNID结构,通过FMTID 和 PID来标识指定栏目。第二个SHCOLUMNDATA结构,包含应该获取的指定的文件信息。第三个参数是一个输出VARIANT缓冲,包含外壳扩展的显示数据。

图八 BMP文件的尺寸栏目

图八是例子程序显示BMP文件尺寸栏,除了表示图像颜色深度的像素外,还包含位图文件的宽度和高度。

注册栏目处理器扩展

栏目处理器不绑定到指定的文件类。对此不应有什么疑问,因为栏目更多地与文件夹有关而不是与文件特性相关。无论是创建一种新的栏目,还是关联现有的栏目,都能够将它应用到一个或多个文件类。例如,在本系列文章第一部分中,只考虑了BMP文件,但也适用于PowerPoint文档或Word文件。快速过滤掉您不感兴趣的文件对象的唯一方法是检查在GetItemData中的文件扩展名(或全路径名)。示例请参考本文源代码。
栏目处理器是一个COM对象和外壳扩展,因此按照惯例,它需要对那些模块类型注册,包括Approved key下的入口。为了将扩展关联到所有的文件夹,在ATL注册脚本RGS文件中输入下列信息行:
HKCR

{

  NoRemove Folder

  {

    NoRemove Shellex

    {

      NoRemove ColumnHandlers

    {

      ForceRemove  = s ''description''

    }

    }

  }

}
不要忘了用实际的组件CLSID值替代
如果您用定制的FMTID定义栏目,请给出编程文档以便其他开发人员能在相同的栏目中编写自己显示信息的处理器。
栏目处理器是Windows 2000 中最令人兴奋的新特点之一,有太多的东西值得进一步探索。如果您琢麽出什么新的应用,VC知识库欢迎您投稿。 下载本文示例代码


阅读(232) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~