
Introduction
Thumbnail views is a nice feature of Windows Explorer. However, little is
known about how to create shell thumbnail extensions for custom documents. I was
developing a medical image visualisation software and wanted this feature for
the DICOM (medical) images that the software can load. After searching the Web i
finally found a relevant article in the MSDN magazine: More Windows 2000 UI Goodies: Extending Explorer Views by Customizing Hypertext Template Files.
The articles covers this topic and includes a simple image extractor for icon
files. After creating my DICOM image extractor (i can give it upon
request) i created also an image extractor shell extension for Scribble (MFC
tutorial) documents
and specifically for Scribble Step 5. I tried to write the code in an objected
oriented way which promotes reuse (i am a "fun" of the OO
"guru" Paul Dilascia, author of
the MSDN mag). Finally, i converted the scribble image extractor project to a Custom AppWizard so that you can easily generate the
sceleton code of an image extractor for your MFC documents. The figure above shows the
thumbnail view of a folder containing a medical file and also my precious scribbles
:)
CScribbleExtractor COM object
The MFC-based thumbnail extension for Scribble (ThumbScb project) has been created as an MFC
regular DLL. After the AppWizard i added an ATL object to the project. The
object was coded to implement the two interfaces needed: IPersistFile to know about the file currently selected in the
shell, and IExtractImage2 (derived from IExtractImage) to access the document and return a bitmap that renders
its content.
class ATL_NO_VTABLE CScribbleExtractor :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CScribbleExtractor, &CLSID_ScribbleExtractor>,
public IPersistFile,
public IExtractImage2
{
public: ...
HRESULT CScribbleExtractor::Extract(HBITMAP* phBmpThumbnail)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
theApp.LoadDoc(m_szFile);
m_hPreview = theApp.CreateThumbnail(m_bmSize);
*phBmpThumbnail = m_hPreview;
return NOERROR;
}
...
CExtractImageApp generic application class
As you can see, the COM object delegates document loading and thumbnail
generation to the main application object. This is convenient because the
CWinApp-derived class can determine the supported document types and create corresponding
document objects via the document manager mechanism. In order to
reuse as much code as possible i implemented the LoadDoc and CreateThumbnail
functions in a generic CWinApp-derived class named CExtractImageApp.
The first is implemented with generic MFC code. The helper function CanOpenDocument
returns an appropriate document template that can create a document
object to serve the file. The document object is dynamically created and then
the content of the file is loaded from disk. Loading code is mainly copy-pasted from
the MFC OnOpenDocument function.
BOOL CExtractImageApp::LoadDoc(LPCTSTR lpFileName)
{
ASSERT(lpFileName!=NULL);
CString sFileName = lpFileName;
CDocTemplate* pDocTemplate = CanOpenDocument(sFileName); if (pDocTemplate) {
if(!m_pDoc) {
m_pDoc = pDocTemplate->CreateNewDocument();
m_pDoc->m_bAutoDelete = TRUE;
}
if (m_pDoc)
{
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); loadArchive.Close();
m_pDoc->ReleaseFile(pFile, FALSE);
}
catch(CException *e)
{
m_pDoc->ReleaseFile(pFile, TRUE);
m_pDoc->DeleteContents(); e->Delete();
return FALSE;
}
return TRUE;
}
}
return FALSE;
}
The CreateThumbnail function creates the thumbnail bitmap and
draws the document content on it. It is essentially a template function because it calls
the GetDocSize and OnDraw functions that are pure
virtual (must be implemented by the derived class). Drawing is done in isotropic mapping to match document dimensions with thumbnail dimensions.
The minus sign in SetViewportExt is used to define a cartesian
coordinate system (y-axis values increase from bottom to top).
HBITMAP CExtractImageApp::CreateThumbnail(const SIZE bmSize)
{
HBITMAP hThumb; CBitmap bmpThumb;
if(!m_pDoc) return NULL;
CSize bmDocSize = GetDocSize(); 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); memdc.SelectObject(pOldBm);
hThumb = (HBITMAP)bmpThumb.Detach();
return hThumb;
}
Derived application class
In the main application class derived from CExtractImageApp, the
above mentioned pure virtual functions are implemented by calling the respective
functions of the already loaded document. Before this, I have to downcast the CDocument
m_pDoc pointer to a CScribbleDoc pointer. If you have
multiple document classes you can use the CObject::IsKindOf function
to find the kind of document class m_pDoc points to. In the InitInstance
function i add a template for the scribble document type. CMDIChildWnd
and CView are MFC classes, while CScribbleDoc is a
simplified version of the original scribble document class. The only functions
that CScribbleDoc is required to implement for this project
are the Serialize function called during document loading and
the GetDocSize and OnDraw
functions called during thumbnail creation. Normally, OnDraw
function belongs to the CScribbleView class but here there is no
need for doc/view architecture. To simplify implementation i copied OnDraw
from CScribbleView. In the first line i replaced GetDocument()
with this.
BOOL CThumbScbApp::InitInstance()
{
if (!InitATL())
return FALSE;
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();
}
void CScribbleDoc::OnDraw(CDC* pDC)
{
CScribbleDoc* pDoc = this; ASSERT_VALID(pDoc);
...
How to debug
Debugging shell extensions is difficult. I have created a COM object (ThumbExtract
project)
that uses shell interfaces to get an IExtractImage for a file and then creates a
bitmap. In the same folder you 'll find small VB6 EXE project called TestThumbExtract that
references the ThumbExtract object. An instance of this test application
is shown below. Click the button to select a file and a large thumbnail for that
file will appear in the picture box. You can debug your shell extension by
setting the TestThumbExtract.exe as the 'Executable for debug session' in
Project/Settings/Debug.

Thumbnail Project Wizard
Since code changes for a new thumbnail shell extension project are few, i
made a custom AppWizard (ThumbWiz project) based on the ThumbScb
project. The compiled wizard DLL is
named ThumbWiz.awx and you must copy it to "C:\Program Files\Microsoft Visual Studio\Common\MSDev98\Template"
folder. An entry named "Thumbnail Project Wizard" appears
then in the New Project dialog. The wizard has a custom step which askes your
object's name (e.g. Scribble) and the file extensions supported (if you want to
support many extensions separate them with commas). The names of the application
class, the document class and the COM class created, are based on the object
name you give. Also, for each file extension a new document template is created
in InitInstance. New GUIDs are automatically generated. The
sceleton code includes TODO
comments indicating where to add the needed details. In CThumbWizAppWiz::CustomizeProject
i customize the project settings (specifically the MIDL settings) and also
create a post-build step to register the object. Registry entries under the file
extension keys are created so that the system can determine what object to load
for each file extension. Be careful not to replace the entries for well known
types (like .jpg) because no backup of registry entries is taken.
Conclusion
Creating thumbnail shell extensions for your MFC document types should be
very easy now (i hope :). With this article, my thumbnail-PhD comes to an end !!
Visit my homepage for other free tools and programs.
History
5 Oct 2002 - updated source code
22 Nov 2002 - updated source code
Software developer and Microsoft Trainer, Athens, Greece (MCT, MCSD.net, MCSE 2003, MCDBA 2000,MCTS, MCITP, MCIPD).