2023年12月11日发(作者:)

如何让特定格式的文件在Windows下显示缩略图及预览

深圳市中科数码技术有限公司–研发部余浩2011-10-23 yh@

概述:

在Windows中系统默认提供了对很多常见格式的文件的缩略图或预览,最典型的就是如jpg、bmp等格式的文件,让我们不用其他工具打开文件就可以知道文件的内容。对于其他特定格式的文件是不是也可以让其有这样的缩略图或预览功能呢,答案当然是肯定的。本文描述了在Window XP下和Windows 7下实现缩略图或预览功能的方法。下图为在Windows

7下实现缩略图和预览功能的效果图:

一、Windows XP下实现缩略图功能

1.实现的原理

在Windows XP下实现缩略图功能根本上就是实现IExtractImage、IExtractImage2和IPersistFile中的各种方法。上述接口中的各种方法都必须有相应的实现函数,函数如果没有具体内容可以返回E_NOTIMPL,代码如下:

// IExtractImage STDMETHOD(GetLocation)(LPWSTRpszPathBuffer,

DWORDcchMax,

DWORD *pdwPriority,

constSIZE *prgSize,

DWORDdwRecClrDepth,

DWORD *pdwFlags);

STDMETHOD(Extract)(HBITMAP*);

// IExtractImage2

STDMETHOD(GetDateStamp)(FILETIME *pDateStamp);

// IPersistFile

STDMETHOD(Load)(LPCOLESTRwszFile, DWORDdwMode);

STDMETHOD(GetClassID)(LPCLSIDclsid){returnE_NOTIMPL;}

STDMETHOD(IsDirty)(VOID) {returnE_NOTIMPL;}

STDMETHOD(Save)(LPCOLESTR, BOOL){returnE_NOTIMPL;}

STDMETHOD(SaveCompleted)(LPCOLESTR){returnE_NOTIMPL;}

STDMETHOD(GetCurFile)(LPOLESTRFAR*){returnE_NOTIMPL;}

上述即只实现了四个方法GetLocation、Extract、GetDateStamp和Load,其他方法均返回E_NOTIMPL。

2.实现的方法

在codeproject网上有一个例子,而且有一个VC6的工程向导。利用该向导并添加适当的代码就可以显示特定格式文件的缩略图。上文是对该例子的简化,原例子在Load方法中先在DC上绘图然后在保存HBITMAP,本文直接用GDI+的方法生成了一个HBITMAP(主要不同请查看Load方法具体实现的不同之处)。各方法的具体实现如下:

HRESULT CGydTydPreviewExtractor::GetLocation(LPWSTR pszPathBuffer,

DWORD cchMax, DWORD *pdwPriority,

const SIZE *prgSize, DWORD dwRecClrDepth,

DWORD *pdwFlags)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

m_bmSize = *prgSize;

if (*pdwFlags & IEIFLAG_ASYNC) return E_PENDING;

return NOERROR;

}

HRESULT CGydTydPreviewExtractor::Load(LPCOLESTR wszFile, DWORD dwMode)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

USES_CONVERSION; _tcscpy(m_szFile, OLE2T((WCHAR*)wszFile));

BOOL bLoad = FALSE;

GdiplusStartupInput startupInput;

ULONG_PTR gdiplusToken;

Status ret = GdiplusStartup(&gdiplusToken, &startupInput, NULL);

CFile file;

if((m_szFile, CFile::modeRead))

{

(26, CFile::begin);

WORD wBitCounts;

DWORD dwVersion;

(&dwVersion, 4);

if(dwVersion >= 20090612)

{

(&wBitCounts, 2);

BYTE *pBits = (BYTE*)GlobalAlloc(GMEM_ZEROINIT, 450000);

BYTE *pt = (BYTE*)GlobalAlloc(GMEM_ZEROINIT, wBitCounts+5000);

(pt, wBitCounts);

short nWidth = 100;

long len = uncode_expand(pt, pBits, wBitCounts);

GlobalFree(pt);

if(len == 40000)

nWidth = 100;

else

nWidth = 256;

Bitmap *pBitmap = new Bitmap(nWidth, nWidth, nWidth*4,

PixelFormat32bppRGB, pBits);

if(pBitmap)

{

bLoad = TRUE;

ret = pBitmap->GetHBITMAP(Color(0, 0, 0), &m_hPreview);

ASSERT(ret == Ok);

delete pBitmap;

}

GlobalFree(pBits);

}

();

}

if(!bLoad)

{

Bitmap *pBitmap = Bitmap::FromResource(AfxGetInstanceHandle(),

A2OLE(MAKEINTRESOURCE(IDB_DEMO)));

if(pBitmap)

{

ret = pBitmap->GetHBITMAP(Color(0, 0, 0), &m_hPreview);

ASSERT(ret == Ok);

delete pBitmap;

}

}

return S_OK;

};

上述Load函数是实现对特定文件进行缩略图的关键,在这里你可以对特定格式的文件进行解析,然后生成HBITMAP保存为成员变量。本示例中的*.GYD或*.TYD文件在文件头部保存有一个BMP格式缩略图文件,在Load中将其读取,用GDI+的Bitmap生成HBITMAP。

// IExtractImage::Extract

HRESULTCGydTydPreviewExtractor::Extract(HBITMAP* phBmpThumbnail)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

if(m_hPreview != NULL)

{

*phBmpThumbnail = m_hPreview;

returnNOERROR;

}

else

returnE_FAIL;

}

上述Extract函数是缩略图的实现,它告诉系统用你所生成的HBITMAP去显示缩略图。

HRESULTCGydTydPreviewExtractor::GetDateStamp(FILETIME *pDateStamp)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

FILETIMEftCreationTime,ftLastAccessTime,ftLastWriteTime;

// open the file and get last write time

HANDLEhFile = CreateFile(m_szFile,GENERIC_READ,FILE_SHARE_READ,NULL,

OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

if(!hFile) returnE_FAIL;

}

GetFileTime(hFile,&ftCreationTime,&ftLastAccessTime,&ftLastWriteTime);

CloseHandle(hFile);

*pDateStamp = ftLastWriteTime;

returnNOERROR;

3.如何调试

codeproject网上的例子提供了一个vb编写的测试程序,使用该程序可以对使用向导生成的VC6工程进行调试。

二、Windows 7下实现缩略图功能

1.实现原理

在Vista、Win7下MS提供了新的接口来显示缩略图,尽管MS宣称旧的IExtractImage、IExtractImage2接口依然支持,但是我的缩略图程序在Windows

XP下运行良好而在Windows 7上却没有什么反应。新的接口名字为:IThumbnailProvider,在该接口中只有一个方法需要实现,实现该方法便可以显示特定文件的缩略图,该方法定义如下:

// IThumbnailProvider

IFACEMETHODIMPGetThumbnail(UINTcx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha);

2.实现方法

在SDK中有例子RecipeThumbnailProvider,照本宣科修改为我所用即可。SDK的例子中使用了WIC的CODEC中imagefactory和decoder的接口及方法实现缩略图,本文使用GDI+的方法实现,对其进行了简化。

同在Windows XP下一样在该方法中加载并解析特定的文件即可,本文是在IStream中解析文件的。如果使用IInitializeWithFile,则可以直接获取文件路径,然后对文件进行解析。在MSDN中说明如果没有什么特殊情况,最好使用IInitializeWithStream接口。GetThumbnail的具体内容如下:

// IThumbnailProvider

IFACEMETHODIMPCGydTydThumbProvider::GetThumbnail(UINTcx, HBITMAP *phbmp,

WTS_ALPHATYPE *pdwAlpha)

{

*pdwAlpha = WTSAT_ARGB;

LARGE_INTEGERliSeek;

rt = 0; t = 26;

HRESULThr = _pStream->Seek(liSeek, STREAM_SEEK_SET, NULL);

if (SUCCEEDED(hr))

{

DWORDdwVersion = 0;

ULONGdwReaded = 0;

hr = _pStream->Read(&dwVersion, 4, &dwReaded);

if (SUCCEEDED(hr) &&dwVersion>= 20090612)

{

DWORDdwLen = 0;

hr = _pStream->Read(&dwLen, 2, &dwReaded);

if (SUCCEEDED(hr))

{

BYTE *bits = (BYTE*)GlobalAlloc(GMEM_ZEROINIT, 450000);//最大*256*4

BYTE *pt = (BYTE*)GlobalAlloc(GMEM_ZEROINIT,dwLen+5000);

hr = _pStream->Read(pt, dwLen, &dwReaded);

intlen = uncode_expand(pt, bits, dwLen);

GlobalFree(pt);

WORDwWid = 100;//每个像素用4个字节存储

if (len != 40000)//旧版本的缩略图为100*100,新版为256*256

wWid = 256;

GdiplusStartupInputstartupInput;

ULONG_PTRgdiplusToken;

Statusret = GdiplusStartup(&gdiplusToken, &startupInput, NULL);

Bitmap *pBitmap = newBitmap(wWid, wWid, wWid*4,

PixelFormat32bppRGB, bits);

if(pBitmap)

{

ret = pBitmap->GetHBITMAP(Color(0, 0, 0), phbmp);

if(ret == Ok)

hr = S_OK;

else

hr = S_FALSE;

deletepBitmap;

pBitmap = NULL;

}

GdiplusShutdown(gdiplusToken); // shut down GDIPlus

GlobalFree(bits);

} }

}

returnhr;

}

3.如何调试

SDK中UsingThumbnailProviders工程可以对你的工程进行调试,该工程的路径一般在X:Program FilesMicrosoft

SDKsWindowsv7.1SampleswinuishellappplatformUsingThumbnailProviders,中有详细的调试方法。VS2008调试设置见下图:

三、Windows 7下实现预览功能

1.实现原理

预览功能是Windows Vista和Windows 7的新功能,实现预览功能的接口为IPreviewHandler,该接口有下列方法:

// IPreviewHandler IFACEMETHODIMPSetWindow(HWNDhwnd, constRECT *prc);

IFACEMETHODIMPSetFocus();

IFACEMETHODIMPQueryFocus(HWND *phwnd);

IFACEMETHODIMPTranslateAccelerator(MSG *pmsg);

IFACEMETHODIMPSetRect(constRECT *prc);

IFACEMETHODIMPDoPreview();

IFACEMETHODIMPUnload();

上述方法都必须实现,其中DoPreview方法具体实现预览的功能。

2.实现方法

SDK中有例子RecipePreviewHandler,同样照本宣科改一改为我所用即可。该例子中使用RichEdit进行预览,这里用一个现成的oxc预览*.GYD或*.TYD文件。要实现ocx控件显示预览有如下步骤:

I.添加头文件及库

Ocx也是一种COM组件,要使用COM的接口和方法必须添加下来头文件和库文件。

//atl的头文件,调用ocx需要用

#include

CComModule_Module;

#include

#include

#pragmacomment(lib,"atl")

II.添加OCX类型库信息(type information)

添加OCX的tlb文件有两种方法:

第一种使用improt导入OCX类型库:

#import "...."

raw_interfaces_only, /* Don't add raw_ to method names */

raw_native_types, /* Don't map to DTC smart types */

named_guids, /* Named guids and declspecs */

no_namespace /* Don't wrap with C++ name space */

使用这种方法在Debug或Release目录会自动生成OCX同名的tlh文件,工程中不把再添加什么头文件或者库文件。

第二中是使用OLE/COM Object Viewer导出的类型库,步骤如下:

首先使用OLE/COM Object Viewer来找到GYard Control,点击右键“View Type

Information”,在弹出的界面中,点击“save as”将信息保存为 文件。

然后在打开VS提供的工具:“Visual Studio 2008 命令提示”,进入DOS界面,切换到所在的目录,执行如下命令:MIDL /tlb

,即可生成tlb文件。如果生成过程有错,提示“error MIDL2110 : end of file found in string”,可以这样做:将前面打开的界面中(“View Type

Information”)的内容拷贝,然后手动新建一个的文件,将拷贝的内容粘贴入,再次执行MIDL命令。

接下来在你的VC++项目中:#import "",编译之,即会在debug或者release目录下面生成tlh(头文件,header)和tli文件(实现文件,implementation)。注意,在tlh文件的末尾处已经包含了tli文件。

注意:如果你的OCX中有Release方法,在VS2008下编译可能报告方法与COM里面的方法有重定义的错误,这时可以先手工编辑一下文件,删除其中关于Release的定义。但是最好的解决办法是在你的OCX中尽量不用使用系统常使用的名称作为你的方法名,避免不必要的麻烦。

iew方法的具体实现

定义如下私有成员变量:

_bstr_t

CAxWindow

_DGYardControl

short

_filePath;

m_content_wnd;

*m_gcl;

m_nFileType;

//文件路径

//文件类型,0:*.GYD;1:*.TYD

DoPreview的具体实现如下:

// The main method that extracts relevant information from the file stream and

// draws content to the previewer window

HRESULTCGydTydPreviewHandler::DoPreview()

{

HRESULThr = E_FAIL;

if (_hwndPreview == NULL) // cannot call more than once (Unload should be called

before another DoPreview)

{

hr = _CreatePreviewWindow();

}

returnhr;

}

HRESULTCGydTydPreviewHandler::_CreatePreviewWindow()

{

if(m_nFileType< 0) returnE_FAIL;//文件类型不识别

assert(_hwndPreview == NULL);

HRESULThr = E_FAIL;

CoInitialize(NULL);

LPOLESTRpstocx;

CLSIDgocxCLSID;

CLSIDFromProgID(L"ontrolCtrl.1", &gocxCLSID);

}

StringFromCLSID(gocxCLSID, &pstocx);

if(m_content_ow())

m_content_yWindow();

_hwndPreview = m_content_(_hwndParent, _rcParent,

LPCTSTR(pstocx), WS_CHILD|WS_VISIBLE);//create a GyardControl

if(_hwndPreview)

{

hr = m_content_ontrol(&m_gcl);

if (SUCCEEDED(hr))

{

m_gcl->InstanceYard();

BOOLbLoad = FALSE;

if(m_nFileType == 0)//*.gyd文件调用LoadFromGFile

bLoad = m_gcl->LoadFromGFile(_filePath);

else

bLoad = m_gcl->LoadFromTFile(_filePath);

if(bLoad)

hr = S_OK;

}

}

else

hr = HRESULT_FROM_WIN32(GetLastError());

CoTaskMemFree(pstocx);

CoUninitialize();

returnhr;

3.如何调试

这类程序进行调试可以参考RecipePreviewHandler工程的中的描述。具体设置如下:

首先,设置可以附加到现有进程中进行调试,是Windows提供预览的服务进程,系统中可能同时有多个预览服务进程,因此可以在命令参数中添加如下语句进行识别:

{540D08F0-C59A-4ca1-AC92-D6A7F6E14865} –Embedding

前面一个参数就是你预览程序的CLSID。

VS2008的调试设置见下图:

四、总结

首先,感谢各位专家提供的各种示例程序,我也只是在他们的基础上修修改改,这里并非原创。

其次,本文实现了在Windows XP和Windows 7下缩略图和预览功能,这里仅仅实现了基本功能,性能等其他方面基本没有考虑,有兴趣的可以一起交流共同进步。

参考文献

1、 /en-us/library/cc144114(VS.85).aspx

2、 /zh-cn/magazine/

3、 /KB/shell/

4、 /zh-cn/magazine/#S4