原文: Create Thumbnail Extractor objects for your MFC documents types
虽然这时候发这个已经有点像个Joke,但还是发吧。。有些付出的东西,留下的东西,不是一篇两篇文章可以理清。
翻译很稚嫩,用词也不得体,甚至有些地方语意还有些模糊,那又如何?那便如何!!
【前注】这一阵子做东西,想给自已的软件加上这样的一个功能。找到此文,翻译如下。
【介绍】
缩略图显示是Windows资源管理器一个很不错的功能。但关于如何为用户的文件创建扩展缩略图的内容却比较少。我前一阵子在做可视化医学图像的软件时,就想为自己的DICOM(Digital Imaging and Communications in Medicine)软件添加这个功能。在网上搜索过后,我最后在MSDN杂志上找到了一篇相关文章: 更多Windows 2000 UI 技巧: 通过定制超文本模板文件扩展资源管理器视图(天啊,不知道是嘛意思) 这篇文件解决了这个问题,并包含了一个针对图标文件的简易图片提取器。在创建了我的DICOM软件的图片提取器之后(如果有人要,我可以提供),我也创建了一个可为涂鸦板(MFC指导)文档特别是涂鸦第五步,提取图片的框架扩展。我尝试着将这些代码以一个面向对象的方式来编写,以使得他们可以支持重用(我是面向对象思想的领导者 Paul Dilascia 的"Fans",他是MSDN杂志的作者)。 最后,我将这个涂鸦图像提取工程转换成了一个客户端程序,这样你们就可以为你自己的MFC文档容易地生成图像抽取。下面的图像就显示了一个包含了我的医药涂鸦文档的文件夹的截图:)
涂鸦板提取的COM对象
为涂鸦软件做的基于MFC的缩略图提取扩展已经被生成为一个规则的MFC动态链接库。顺着应用程序向导,我添加了一个ATL对象到工程中。 这个ATL对象是用来实现需要的两个接口:IPersistFile,用来确定当前框架中被选中的文件(译注:加载文档)。IExtractImage2(继承于IExtractImage),用来进入文件并返回能表示其内容的图像。
// ScribbleExtractor.h class ATL_NO_VTABLE CScribbleExtractor : public CComObjectRootEx , public CComCoClass , public IPersistFile, public IExtractImage2 { public: ... // ScribbleExtractor.cpp // IExtractImage::Extract HRESULT CScribbleExtractor::Extract(HBITMAP* phBmpThumbnail) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); theApp.LoadDoc(m_szFile); m_hPreview = theApp.CreateThumbnail(m_bmSize); *phBmpThumbnail = m_hPreview; return NOERROR; } ... // Code for other interface functions is omitted since it is boiterblate.CExtractImageApp 一般应用类
正如你看到的,COM对象为主程序对象做了加载文档和生成缩略图这两件事儿。因为从CWinApp继承下来的类可以决定支持的文件类型,并通过文档管理机制创建对应的文档对象,这将会很方便。为了能够将尽可能多的代码实现重用,我实现从CWinApp继承下来的类
ExtractImageApp
里面的LoadDoc
和CreateThumbnail
函数。 第一部分在通用MFC代码中实现的。 辅助函数CanOpenDocument
返回一个合适的文档模板,这个模板可以一个文件对象来提供给文件。 文件对象是被动态创建的,文档的内容是从硬盘中加载的。加载的代码大部分是从MFC的OnOpenDocument 函数里复制粘贴来的。// Load document. This function is responsible for opening the document
// and setting m_pOpenDoc to the document loaded.
BOOL CExtractImageApp::LoadDoc(LPCTSTR lpFileName)
{
ASSERT(lpFileName!=NULL);
CString sFileName = lpFileName;
CDocTemplate* pDocTemplate = CanOpenDocument(sFileName); // helper function
// defined above
if (pDocTemplate) {
if(!m_pDoc) {
m_pDoc = pDocTemplate->CreateNewDocument();
m_pDoc->m_bAutoDelete = TRUE;
}
if (m_pDoc)
{
// load content of file, code taken from MFC OnOpenDocument and modified
CFileException fe;
CFile* pFile = m_pDoc->GetFile(sFileName, CFile::modeRead, &fe);
if (pFile == NULL)
return FALSE;
m_pDoc->DeleteContents();
CArchive loadArchive(pFile, CArchive::load | CArchive::bNoFlushOnDelete);
loadArchive.m_pDocument = m_pDoc;
loadArchive.m_bForceFlat = FALSE;
try
{
if (pFile->GetLength() != 0)
m_pDoc->Serialize(loadArchive); // load me
loadArchive.Close();
m_pDoc->ReleaseFile(pFile, FALSE);
}
catch(CException *e)
{
//e->ReportError();
m_pDoc->ReleaseFile(pFile, TRUE);
m_pDoc->DeleteContents(); // remove failed contents
e->Delete();
return FALSE;
}
return TRUE;
//delete pDoc;
}
}
return FALSE;
}
CreateThumbnail
函数创建了缩略图的BMP图像,并将文档内容画在这个图像上。这个函数在本质上来说是一个模板函数,因为它调用了GetDocSize
和OnDraw
这两个纯虚函数(必须要在子类中实现的)。缩略图是依照缩略图的尺寸同比放缩文档内容尺寸(在维度上保持一致)。在SetViewportExt
函数里的负号是用来定义笛卡尔坐标系(y轴是自底向上的)。HBITMAP CExtractImageApp::CreateThumbnail(const SIZE bmSize) { HBITMAP hThumb; CBitmap bmpThumb; if(!m_pDoc) return NULL; CSize bmDocSize = GetDocSize(); // derived class knows it // Create memory DC, create a color bitmap, and draw on it CDC memdc; memdc.CreateCompatibleDC(NULL); bmpThumb.CreateBitmap(bmSize.cx,bmSize.cy,3,8,NULL); CBitmap* pOldBm = memdc.SelectObject(&bmpThumb); memdc.PatBlt(0,0,bmSize.cx,bmSize.cy,WHITENESS); memdc.SetMapMode(MM_ISOTROPIC); memdc.SetViewportExt(bmSize.cx,-bmSize.cy); memdc.SetWindowExt(bmDocSize.cx,bmDocSize.cy); OnDraw(&memdc); //let the derived class to handle it memdc.SelectObject(pOldBm); hThumb = (HBITMAP)bmpThumb.Detach(); return hThumb; }继承类
主要的应用程序类是从
CExtractImageApp
继承的,在子类里,通过调用已加载的文档各自的函数,实现了上面提到的两个纯虚函数。在这之前,我必须将CDocument
的m_pDoc 指针向下指向CScribbleDoc
指针。 如果你是多文档,有多个文档类,你可以使用CObject::IsKindOf
函数去定位文档类的m_pDoc 指针指向。在InitInstance
函数中,我添加了一个针对涂鸦板文档的文档模板。CMDIChildWnd
和CView
都是MFC的类,而CScribbleDoc
是原始涂鸦文档的一个简略版。The only functions that 工程里CScribbleDoc
类唯一需要被实现的就是Serialize
函数,此函数在文档加载时被调用。而GetDocSize
和OnDraw
函数在创建缩略图时被调用。一般来说,OnDraw
函数属于CScribbleView
类,但这里没有必要使用文档视图结构,为了简化实现,我就从CScribbleView
里拷了函数。在第一行里,把
OnDraw
GetDocument()
替换成了this
.//thumbscb.cpp BOOL CThumbScbApp::InitInstance() { if (!InitATL()) return FALSE; // Create document template AddDocTemplate(new CMultiDocTemplate(IDR_SCRIBBTYPE, RUNTIME_CLASS(CScribbleDoc),RUNTIME_CLASS(CMDIChildWnd), RUNTIME_CLASS(CView))); return CWinApp::InitInstance(); } void CThumbScbApp::OnDraw(CDC *pDC) { CScribbleDoc *mydoc = (CScribbleDoc *)m_pDoc; mydoc->OnDraw(pDC); } CSize CThumbScbApp::GetDocSize() { CScribbleDoc *mydoc = (CScribbleDoc *)m_pDoc; return mydoc->GetDocSize(); } // scribdoc.cpp void CScribbleDoc::OnDraw(CDC* pDC) { CScribbleDoc* pDoc = this; //GetDocument(); ASSERT_VALID(pDoc); ...怎样Debug
调试框架扩展挺难的。我创建了一个COM对象(ThumbExtract project) ,可以用框架接口得到文件的IExtractImage,然后创建一幅位图。在同一个文件夹下,你也可以找到小的VB6的Exe工程, 名字是TestThumbExtract ,这个工程引用了ThumbExtract 对象。 这个测试程序的一个实例在下面展示出来了。点击鼠标去选择一个文件,一个大的缩略图就会显示在图像框中。你也可以在Project/Settings/Debug 里设置TestThumbExtract.exe 的 'Executable for debug session' 来调试你你自己的框架扩展。
缩略图工程向导
一个新的缩略图提取工程需要修改的代码量比较少,我基于ThumbScb工程做了一个客户端程序(ThumbWiz 工程) 。编译后的Dll名称为ThumbWiz.awx ,你得将这个文件复制到"C:/Program Files/Microsoft Visual Studio/Common/MSDev98/Template" 文件夹。在新的工程对话框会出现一个新的条目,名字为"Thumbnail Project Wizard" 。这个向导有一个向导,会询问你的对象名(比如 Scribble) 和文件扩展名支持种类(如果你想支持多种文件扩展名,你要用逗号将他们隔开)。 App类,文件类和COM类的名字都是基于你提供的对象名字创建的。 针对每一种文件扩展名,在
InitInstance
中都会成生一个新的文档模板。而控件的ID就会被自动创建。自动生成的代码包含了TODO注释,这就是指名什么地方需要添加一些细节了。在CThumbWizAppWiz:: CustomizeProject
我定制了工程的一些设置(特别是MIDL设置)同时也创建了一个快速指南来注意对象。文件扩展名注册的键值也创建了,这样系统就可以确定针对每种文件格式需要加载哪个对象。不要粗心将已知的格式(如 .jpg)格式注册了,因为我没有做注册表的备份哦。总结
现在为你自己的文件格式创建扩展名的缩略图该很简单了(我希望:))。随着写这篇文章,我的缩略图提取程序也写完了!欢迎访问我的主页以获得更多的免费工具和程序。